using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; namespace NationalInstruments.Tdms { public class File : IEnumerable, IDisposable { private readonly Lazy _stream; private File() { Properties = new Dictionary(); Groups = new Dictionary(); } public File(Stream stream) : this() { if (!stream.CanSeek) throw new ArgumentException("Only seekable streams supported.", "stream"); _stream = new Lazy(() => stream); } public File(string path) : this() { _stream = new Lazy(() => new FileStream(path, FileMode.Open, FileAccess.Read)); } public IDictionary Properties { get; private set; } public IDictionary Groups { get; private set; } public File Open() { var reader = new Reader(_stream.Value); var metadata = LoadMetadata(reader).ToList(); LoadFile(metadata); LoadGroups(Groups, metadata); LoadChannels(Groups, metadata, reader); return this; } /// /// This will re-write the TDMS file. Mostly used for write demonstration. Although, this will also defragment the file. /// /// public void ReWrite(string path) { using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write)) ReWrite(stream); } /// /// This will re-write the TDMS file. Mostly used for write demonstration. Although, this will also defragment the file. /// /// public void ReWrite(Stream stream) { WriteSegment segment = new WriteSegment(stream); segment.Header.TableOfContents.HasRawData = Groups.SelectMany(g => g.Value.Channels.Values, (g, c) => c.HasData).Any(); //when we re-write the file, no data shall be interleaved. (It's an all or nothing situation, with only 1 segment) //segment.Header.TableOfContents.RawDataIsInterleaved = Groups.SelectMany(g => g.Value.Channels.Values, (g, c) => c.RawData.First().IsInterleaved).Any(); //Top level Reader.Metadata m = new Reader.Metadata(); m.Path = new string[0]; m.Properties = Properties; segment.MetaData.Add(m); //Groups foreach(KeyValuePair group in Groups) { m = new Reader.Metadata(); m.Path = new string[] { group.Key }; m.Properties = group.Value.Properties; segment.MetaData.Add(m); //Channels foreach (KeyValuePair channel in group.Value.Channels) { Reader.RawData[] raws = channel.Value.RawData.ToArray(); //Add first part m = new Reader.Metadata(); m.Path = new string[] { group.Key, channel.Key }; m.Properties = channel.Value.Properties; m.RawData = raws?[0]; segment.MetaData.Add(m); //Add the other parts (if any) for(int i = 1; i < raws?.Length; i++) { m = new Reader.Metadata(); m.Path = new string[] { group.Key, channel.Key }; m.RawData = raws[i]; segment.MetaData.Add(m); } } } //Write all raw data Writer writer = segment.Open(); var reader = new Reader(_stream.Value); foreach (KeyValuePair group in Groups) foreach (KeyValuePair channel in group.Value.Channels) foreach (Reader.RawData raw in channel.Value.RawData) { var data = reader.ReadRawData(raw); raw.IsInterleaved = false; //when we re-write the file, no data shall be interleaved writer.WriteRawData(raw, data); } //close up segment.Close(); } private void LoadFile(IEnumerable metadata) { metadata.Where(x => x.Path.Length == 0) .SelectMany(m => m.Properties).ToList() .ForEach(x => Properties[x.Key] = x.Value); } private static void LoadGroups(IDictionary groups, IEnumerable metadata) { var groupMetadata = metadata.Where(x => x.Path.Length == 1). GroupBy(x => x.Path[0], (k, r) => r.OrderByDescending(y => y.Properties.Count).First()); foreach (var group in groupMetadata) groups.Add(group.Path[0], new Group(group.Path[0], group.Properties)); // add implicit groups foreach (var groupName in metadata.Where(x => x.Path.Length > 1 && !groups.ContainsKey(x.Path[0])).Select(x => x.Path[0])) groups.Add(groupName, new Group(groupName, new Dictionary())); } private static void LoadChannels(IDictionary groups, IEnumerable metadata, Reader reader) { var channelMetadata = metadata .Where(x => x.Path.Length == 2) .GroupBy(x => Tuple.Create(x.Path[0], x.Path[1])) .Join(groups, x => x.First().Path[0], x => x.Key, (c, g) => Tuple.Create(g.Value, c)); foreach (var channel in channelMetadata) channel.Item1.Channels.Add(channel.Item2.First().Path[1], new Channel(channel.Item2.First().Path[1], channel.Item2.OrderByDescending(y => y.Properties.Count).First().Properties, channel.Item2.Where(x => x.RawData.Count > 0).Select(x => x.RawData), reader)); } private static IEnumerable LoadMetadata(Reader reader) { var segments = GetSegments(reader).ToList(); var prevMetaDataLookup = new Dictionary>(); foreach (var segment in segments) { if (!(segment.TableOfContents.ContainsNewObjects || segment.TableOfContents.HasDaqMxData || segment.TableOfContents.HasMetaData || segment.TableOfContents.HasRawData)) { continue; } var metadatas = reader.ReadMetadata(segment); long rawDataSize = 0; long nextOffset = segment.RawDataOffset; foreach (var m in metadatas) { if (m.RawData.Count == 0 && m.Path.Length > 1) { // apply previous metadata if available if (prevMetaDataLookup.ContainsKey(m.Path[0]) && prevMetaDataLookup[m.Path[0]].ContainsKey(m.Path[1])) { var prevMetaData = prevMetaDataLookup[m.Path[0]][m.Path[1]]; if (prevMetaData != null) { m.RawData.Count = segment.TableOfContents.HasRawData ? prevMetaData.RawData.Count : 0; m.RawData.DataType = prevMetaData.RawData.DataType; m.RawData.ClrDataType = prevMetaData.RawData.ClrDataType; m.RawData.Offset = segment.RawDataOffset + rawDataSize; m.RawData.IsInterleaved = prevMetaData.RawData.IsInterleaved; m.RawData.InterleaveStride = prevMetaData.RawData.InterleaveStride; m.RawData.Size = prevMetaData.RawData.Size; m.RawData.Dimension = prevMetaData.RawData.Dimension; } } } if (m.RawData.IsInterleaved && segment.NextSegmentOffset <= 0) { m.RawData.Count = segment.NextSegmentOffset > 0 ? (segment.NextSegmentOffset - m.RawData.Offset + m.RawData.InterleaveStride - 1)/ m.RawData.InterleaveStride : (reader.FileSize - m.RawData.Offset + m.RawData.InterleaveStride - 1)/ m.RawData.InterleaveStride; } if (m.Path.Length > 1) { rawDataSize += m.RawData.Size; nextOffset += m.RawData.Size; } } var implicitMetadatas = new List(); if (metadatas.All(m => !m.RawData.IsInterleaved && m.RawData.Size > 0)) { while (nextOffset < segment.NextSegmentOffset || (segment.NextSegmentOffset == -1 && nextOffset < reader.FileSize)) { // Incremental Meta Data see http://www.ni.com/white-paper/5696/en/#toc1 foreach (var m in metadatas) { if (m.Path.Length > 1) { var implicitMetadata = new Reader.Metadata() { Path = m.Path, RawData = new Reader.RawData() { Count = m.RawData.Count, DataType = m.RawData.DataType, ClrDataType = m.RawData.ClrDataType, Offset = nextOffset, IsInterleaved = m.RawData.IsInterleaved, Size = m.RawData.Size, Dimension = m.RawData.Dimension }, Properties = m.Properties }; implicitMetadatas.Add(implicitMetadata); nextOffset += implicitMetadata.RawData.Size; } } } } var metadataWithImplicit = metadatas.Concat(implicitMetadatas).ToList(); foreach (var metadata in metadataWithImplicit) { if (metadata.Path.Length == 2) { if (!prevMetaDataLookup.ContainsKey(metadata.Path[0])) { prevMetaDataLookup[metadata.Path[0]] = new Dictionary(); } prevMetaDataLookup[metadata.Path[0]][metadata.Path[1]] = metadata; } yield return metadata; } } } private static IEnumerable GetSegments(Reader reader) { var segment = reader.ReadFirstSegment(); while (segment != null) { yield return segment; segment = reader.ReadSegment(segment.NextSegmentOffset); } } public IEnumerator GetEnumerator() { return Groups.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Dispose() { _stream.Value.Dispose(); } } }