Writer.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. namespace NationalInstruments.Tdms
  6. {
  7. public class Writer
  8. {
  9. private readonly BinaryWriter _writer;
  10. public Stream BaseStream { get { return _writer.BaseStream; } }
  11. public Writer(Stream stream)
  12. {
  13. _writer = new BinaryWriter(stream);
  14. }
  15. public void WriteSegment(long offset, Reader.Segment leadin)
  16. {
  17. _writer.BaseStream.Seek(offset, SeekOrigin.Begin);
  18. _writer.Write(Encoding.ASCII.GetBytes(leadin.Identifier), 0, 4);
  19. Int32 tableOfContentsMask = 0;
  20. if (leadin.TableOfContents.ContainsNewObjects) tableOfContentsMask |= 1 << 2;
  21. if (leadin.TableOfContents.HasDaqMxData) tableOfContentsMask |= 1 << 7;
  22. if (leadin.TableOfContents.HasMetaData) tableOfContentsMask |= 1 << 1;
  23. if (leadin.TableOfContents.HasRawData) tableOfContentsMask |= 1 << 3;
  24. if (leadin.TableOfContents.NumbersAreBigEndian) tableOfContentsMask |= 1 << 6;
  25. if (leadin.TableOfContents.RawDataIsInterleaved) tableOfContentsMask |= 1 << 5;
  26. _writer.Write(tableOfContentsMask);
  27. _writer.Write(leadin.Version);
  28. _writer.Write((Int64)leadin.NextSegmentOffset);
  29. _writer.Write((Int64)leadin.RawDataOffset);
  30. }
  31. public static string GetEncodePath(params string[] path)
  32. {
  33. return path == null || path.Length == 0 ? "/" : path.Length == 1 ? "/'" + path[0] + "'" : "/'" + path[0] + "'/'" + path[1] + "'";
  34. }
  35. public void WriteMetadata(IList<Reader.Metadata> metadatas)
  36. {
  37. _writer.Write((Int32)metadatas.Count);
  38. foreach(var metadata in metadatas)
  39. {
  40. _writer.Write(DataType.String, GetEncodePath(metadata.Path));
  41. //DAQmx Format Changing scaler 0x1269, DAQmx Digital Line scalar 0x1369
  42. /* If the raw data index of this object in this segment exactly matches the index the same object had in the previous segment, an unsigned 32-bit integer (0x0000000) will be stored instead of the index information. */
  43. if (metadata.RawData == null) _writer.Write((Int32)(-1));
  44. else
  45. {
  46. _writer.Write((Int32)20 + (metadata.RawData.DataType == DataType.String ? 8 : 0));
  47. _writer.Write((Int32)metadata.RawData.DataType);
  48. _writer.Write((Int32)metadata.RawData.Dimension); //must be 1
  49. _writer.Write((UInt64)metadata.RawData.Count);
  50. if (metadata.RawData.DataType == DataType.String) _writer.Write((UInt64)metadata.RawData.Size);
  51. }
  52. _writer.Write((Int32)(metadata.Properties?.Count ?? 0));
  53. foreach(KeyValuePair<string, object> entry in (metadata.Properties ?? new Dictionary<string, object>()))
  54. {
  55. _writer.Write(DataType.String, entry.Key);
  56. int dataType = DataType.GetDataType(entry.Value);
  57. _writer.Write((Int32)dataType);
  58. _writer.Write((Int32)dataType, entry.Value);
  59. }
  60. }
  61. }
  62. public void WriteRawData(Reader.RawData rawData, IEnumerable<object> data)
  63. {
  64. if (rawData.IsInterleaved) WriteRawInterleaved(rawData.DataType, rawData.Size, rawData.InterleaveStride, data);
  65. else if (rawData.DataType == DataType.String) WriteRawStrings(rawData.Count, data);
  66. else WriteRawFixed(rawData.DataType, data);
  67. }
  68. public void WriteRawInterleaved(int dataType, long dataSize, int interleaveStride, IEnumerable<object> data)
  69. {
  70. long offset = _writer.BaseStream.Position;
  71. foreach (object o in data)
  72. {
  73. _writer.Write(dataType, o);
  74. _writer.BaseStream.Seek(interleaveStride - dataSize, SeekOrigin.Current);
  75. }
  76. _writer.BaseStream.Seek(offset + dataSize, SeekOrigin.Begin); //move cursor to next channel
  77. }
  78. public void WriteRawFixed(int dataType, IEnumerable<object> data)
  79. {
  80. foreach(object o in data) _writer.Write(dataType, o);
  81. }
  82. public void WriteRawStrings(long count, IEnumerable<object> data)
  83. {
  84. long indexPosition = _writer.BaseStream.Position;
  85. long dataOffset = indexPosition + (count * 4);
  86. long baseOffset = dataOffset;
  87. foreach (object o in data)
  88. {
  89. //write string
  90. _writer.BaseStream.Seek(dataOffset, SeekOrigin.Begin);
  91. _writer.Write(Encoding.UTF8.GetBytes((string)o));
  92. int relative_offset = (int)(_writer.BaseStream.Position - baseOffset);
  93. int length = (int)(_writer.BaseStream.Position - dataOffset);
  94. //write index
  95. _writer.BaseStream.Seek(indexPosition, SeekOrigin.Begin);
  96. _writer.Write((Int32)relative_offset);
  97. //increment
  98. indexPosition += 4;
  99. dataOffset += length;
  100. }
  101. _writer.BaseStream.Seek(dataOffset, SeekOrigin.Begin); //move cursor to end
  102. }
  103. }
  104. /// <summary>
  105. /// Small helper class for the writer. This will serialize the binary segment proper.
  106. /// </summary>
  107. public class WriteSegment : IDisposable
  108. {
  109. private Writer _writer;
  110. private bool _isOpen = false;
  111. private long _startOffset;
  112. private bool _ownsStream = false;
  113. public Stream BaseStream { get { return _writer.BaseStream; } }
  114. public Reader.Segment Header { get; set; }
  115. public List<Reader.Metadata> MetaData { get; set; }
  116. public WriteSegment(Stream stream)
  117. {
  118. _writer = new Writer(stream);
  119. //default header
  120. Header = new Reader.Segment();
  121. Header.Identifier = "TDSm";
  122. Header.Version = 4713;
  123. Header.TableOfContents = new Reader.TableOfContents();
  124. Header.TableOfContents.HasMetaData = true; //it would be rather odd with a TDMS file with no meta data. (Then there would be no raw data either)
  125. Header.NextSegmentOffset = -1; //no more segments
  126. Header.RawDataOffset = 0; //for now
  127. MetaData = new List<Reader.Metadata>();
  128. }
  129. public WriteSegment(string path) : this(new FileStream(path, FileMode.Create, FileAccess.Write))
  130. {
  131. _ownsStream = true;
  132. }
  133. public Writer Open()
  134. {
  135. if (_isOpen) throw new Exception("The TDMS segment is already open. It cannot be re-opened");
  136. if (MetaData.Count == 0) throw new Exception("First insert some meta data");
  137. //write header
  138. _startOffset = BaseStream.Position;
  139. _writer.WriteSegment(_startOffset, Header);
  140. _writer.WriteMetadata(MetaData);
  141. Header.RawDataOffset = BaseStream.Position - Reader.Segment.Length - _startOffset;
  142. _isOpen = true;
  143. return _writer;
  144. }
  145. public void Close()
  146. {
  147. if (!_isOpen) return;
  148. //Re-write raw and next offset
  149. long end_offset = BaseStream.Position;
  150. Header.NextSegmentOffset = end_offset - _startOffset - Reader.Segment.Length;
  151. _writer.WriteSegment(_startOffset, Header);
  152. _writer.WriteMetadata(MetaData); //meta data contains byte count. These should be updated before 'closing'
  153. BaseStream.Seek(end_offset, SeekOrigin.Begin); //set cursor at end
  154. //close stream
  155. if (_ownsStream) BaseStream.Close();
  156. _isOpen = false;
  157. }
  158. public void Dispose()
  159. {
  160. Close();
  161. }
  162. #region " Helper functions for creating new TDMS files (with properties) "
  163. public static Reader.Metadata GenerateStandardProperties(string description, params string[] path)
  164. {
  165. Reader.Metadata meta = new Reader.Metadata();
  166. meta.Path = path;
  167. meta.Properties = new Dictionary<string, object>();
  168. if (!string.IsNullOrEmpty(description)) meta.Properties.Add("description", description);
  169. return meta;
  170. }
  171. public static Reader.Metadata GenerateStandardRoot(string name, string author, string description, DateTime datetime)
  172. {
  173. Reader.Metadata meta = GenerateStandardProperties(description, new string[0]);
  174. if (!string.IsNullOrEmpty(name)) meta.Properties.Add("name", name);
  175. if (!string.IsNullOrEmpty(author)) meta.Properties.Add("author", author);
  176. if (datetime != new DateTime(1, 1, 1)) meta.Properties.Add("datetime", datetime);
  177. return meta;
  178. }
  179. public static Reader.Metadata GenerateStandardGroup(string name, string description)
  180. {
  181. Reader.Metadata meta = GenerateStandardProperties( description, name);
  182. return meta;
  183. }
  184. public static Reader.Metadata GenerateStandardChannel(string groupName, string name, string description, string yUnitString, string xUnitString, string xName, DateTime startTime, double increment, Type dataType, int dataCount, int stringBlobLength = 0)
  185. {
  186. Reader.Metadata meta = GenerateStandardProperties( description, groupName, name);
  187. if(!string.IsNullOrEmpty(yUnitString)) meta.Properties.Add("unit_string", yUnitString);
  188. if(!string.IsNullOrEmpty(xUnitString)) meta.Properties.Add("wf_xunit_string", xUnitString);
  189. if(!string.IsNullOrEmpty(xName)) meta.Properties.Add("wf_xname", xName);
  190. if(startTime != new DateTime(1,1,1)) meta.Properties.Add("wf_start_time", startTime);
  191. if(increment != 0) meta.Properties.Add("wf_increment", increment);
  192. meta.RawData = new Reader.RawData();
  193. meta.RawData.DataType = DataType.GetDataType(Activator.CreateInstance(dataType));
  194. meta.RawData.Count = dataCount;
  195. meta.RawData.Dimension = 1; //always 1
  196. if (dataType == typeof(string)) meta.RawData.Size = stringBlobLength;
  197. return meta;
  198. }
  199. #endregion
  200. }
  201. }