/// <summary> /// Writes the <paramref name="dataPoint"/> to the <see cref="ArchiveDataBlock"/>. /// </summary> /// <param name="dataPoint"><see cref="IDataPoint"/> to write.</param> /// <param name="exception">Any <see cref="Exception"/> that may have been encountered while writing.</param> /// <returns> /// <c>true</c> if data point was written; otherwise, <c>false</c>. /// </returns> public bool Write(IDataPoint dataPoint, out Exception exception) { exception = null; try { TimeTag value = dataPoint.Time; // Do not attempt to write values with a bad timestamp, this will just throw an exception if (value.CompareTo(TimeTag.MinValue) < 0 || value.CompareTo(TimeTag.MaxValue) > 0) { exception = new TimeTagException("Skipping data write for point: Bad time tag, value must between 01/01/1995 and 01/19/2063"); } else { if (SlotsAvailable > 0) { // We have enough space to write the provided point data to the data block. m_lastActivityTime = DateTime.UtcNow; lock (m_parent.FileData) { // Write the data. if (m_writeCursor != m_parent.FileData.Position) { m_parent.FileData.Seek(m_writeCursor, SeekOrigin.Begin); } dataPoint.CopyBinaryImageToStream(m_parent.FileData); // Update the write cursor. m_writeCursor = m_parent.FileData.Position; // Flush the data if configured. if (!m_parent.CacheWrites) { m_parent.FileData.Flush(); } } } else { exception = new InvalidOperationException("Skipping data write for point: No slots available for writing new data"); } } } catch (Exception ex) { exception = new InvalidOperationException($"Skipping data write for point: {ex.Message}", ex); } if ((object)exception == null) { return(true); } OnDataWriteException(exception); return(false); }
/// <summary> /// Initializes <see cref="ArchiveDataPoint"/> from the specified <paramref name="buffer"/>. /// </summary> /// <param name="buffer">Binary image to be used for initializing <see cref="ArchiveDataPoint"/>.</param> /// <param name="startIndex">0-based starting index of initialization data in the <paramref name="buffer"/>.</param> /// <param name="length">Valid number of bytes in <paramref name="buffer"/> from <paramref name="startIndex"/>.</param> /// <returns>Number of bytes used from the <paramref name="buffer"/> for initializing <see cref="ArchiveDataPoint"/>.</returns> public virtual int ParseBinaryImage(byte[] buffer, int startIndex, int length) { if (length >= FixedLength) { // Binary image has sufficient data. Flags = LittleEndian.ToInt16(buffer, startIndex + 4); Value = LittleEndian.ToSingle(buffer, startIndex + 6); TimeTag value = new TimeTag(LittleEndian.ToInt32(buffer, startIndex) + // Seconds ((decimal)(m_flags.GetMaskedValue(MillisecondMask) >> 5) / 1000)); // Milliseconds // Make sure to properly validate timestamps for newly initialized or possibly corrupted blocks if (value.CompareTo(TimeTag.MinValue) < 0 || value.CompareTo(TimeTag.MaxValue) > 0) { value = TimeTag.MinValue; } Time = value; return(FixedLength); } // Binary image does not have sufficient data. return(0); }
/// <summary> /// Tests if the <paramref name="dataBlockPointer"/> matches the specified search criteria. /// </summary> /// <param name="dataBlockPointer"><see cref="ArchiveDataBlockPointer"/> to test.</param> /// <param name="historianID">Desired historian ID.</param> /// <param name="startTime">Desired start time.</param> /// <param name="endTime">Desired end time.</param> /// <returns><c>true</c> if the specified <paramref name="dataBlockPointer"/> is for <paramref name="historianID"/> and falls within the <paramref name="startTime"/> and <paramref name="endTime"/>; otherwise <c>false</c>.</returns> public static bool Matches(this ArchiveDataBlockPointer dataBlockPointer, int historianID, TimeTag startTime, TimeTag endTime) { // Note: The StartTime value of the pointer is ignored if m_searchStartTime = TimeTag.MinValue and // m_searchEndTime = TimeTag.MaxValue. In this case only the PointID value is compared. This // comes in handy when the first or last pointer is to be found from the list of pointers for // a point ID in addition to all the pointers for a point ID. if ((object)dataBlockPointer != null) { return(dataBlockPointer.HistorianID == historianID && (startTime.CompareTo(TimeTag.MinValue) == 0 || dataBlockPointer.StartTime.CompareTo(startTime) >= 0) && (endTime.CompareTo(TimeTag.MaxValue) == 0 || dataBlockPointer.StartTime.CompareTo(endTime) <= 0)); } return(false); }
/// <summary> /// Compares the current <see cref="ArchiveDataBlockPointer"/> object to <paramref name="obj"/>. /// </summary> /// <param name="obj">Object against which the current <see cref="ArchiveDataBlockPointer"/> object is to be compared.</param> /// <returns> /// Negative value if the current <see cref="ArchiveDataBlockPointer"/> object is less than <paramref name="obj"/>, /// Zero if the current <see cref="ArchiveDataBlockPointer"/> object is equal to <paramref name="obj"/>, /// Positive value if the current <see cref="ArchiveDataBlockPointer"/> object is greater than <paramref name="obj"/>. /// </returns> public virtual int CompareTo(object obj) { ArchiveDataBlockPointer other = obj as ArchiveDataBlockPointer; if ((object)other == null) { return(1); } int result = m_historianID.CompareTo(other.HistorianID); if (result != 0) { return(result); } return(m_startTime.CompareTo(other.StartTime)); }
/// <summary> /// Compares the current <see cref="ArchiveDataPoint"/> object to <paramref name="obj"/>. /// </summary> /// <param name="obj">Object against which the current <see cref="ArchiveDataPoint"/> object is to be compared.</param> /// <returns> /// Negative value if the current <see cref="ArchiveDataPoint"/> object is less than <paramref name="obj"/>, /// Zero if the current <see cref="ArchiveDataPoint"/> object is equal to <paramref name="obj"/>, /// Positive value if the current <see cref="ArchiveDataPoint"/> object is greater than <paramref name="obj"/>. /// </returns> public virtual int CompareTo(object obj) { ArchiveDataPoint other = obj as ArchiveDataPoint; if (other == null) { return(1); } else { int result = m_historianID.CompareTo(other.HistorianID); if (result != 0) { return(result); } else { return(m_time.CompareTo(other.Time)); } } }
/// <summary> /// Initializes <see cref="ArchiveDataPoint"/> from the specified <paramref name="buffer"/>. /// </summary> /// <param name="buffer">Binary image to be used for initializing <see cref="ArchiveDataPoint"/>.</param> /// <param name="startIndex">0-based starting index of initialization data in the <paramref name="buffer"/>.</param> /// <param name="length">Valid number of bytes in <paramref name="buffer"/> from <paramref name="startIndex"/>.</param> /// <returns>Number of bytes used from the <paramref name="buffer"/> for initializing <see cref="ArchiveDataPoint"/>.</returns> public virtual int ParseBinaryImage(byte[] buffer, int startIndex, int length) { if (length >= FixedLength) { // Binary image has sufficient data. Flags = LittleEndian.ToInt16(buffer, startIndex + 4); Value = LittleEndian.ToSingle(buffer, startIndex + 6); TimeTag value = new TimeTag(LittleEndian.ToInt32(buffer, startIndex) + // Seconds ((decimal)(m_flags.GetMaskedValue(MillisecondMask) >> 5) / 1000)); // Milliseconds // Make sure to properly validate timestamps for newly initialized or possibly corrupted blocks if (value.CompareTo(TimeTag.MinValue) < 0 || value.CompareTo(TimeTag.MaxValue) > 0) value = TimeTag.MinValue; Time = value; return FixedLength; } // Binary image does not have sufficient data. return 0; }
private bool FindHistoricArchiveFileForWrite(Info fileInfo, TimeTag searchTime) { return ((object)fileInfo != null && searchTime.CompareTo(fileInfo.StartTimeTag) >= 0 && searchTime.CompareTo(fileInfo.EndTimeTag) <= 0); }
private bool FindHistoricArchiveFileForRead(Info fileInfo, TimeTag startTime, TimeTag endTime) { return ((object)fileInfo != null && ((startTime.CompareTo(fileInfo.StartTimeTag) >= 0 && startTime.CompareTo(fileInfo.EndTimeTag) <= 0) || (endTime.CompareTo(fileInfo.StartTimeTag) >= 0 && endTime.CompareTo(fileInfo.EndTimeTag) <= 0) || (startTime.CompareTo(fileInfo.StartTimeTag) < 0 && endTime.CompareTo(fileInfo.EndTimeTag) > 0))); }
// Read data implementation private IEnumerable<IDataPoint> ReadData(IEnumerable<int> historianIDs, TimeTag startTime, TimeTag endTime, IDataPoint resumeFrom, bool timeSorted) { // Yield to archive rollover process. m_rolloverWaitHandle.WaitOne(); // Ensure that the current file is open. if (!IsOpen) throw new InvalidOperationException(string.Format("\"{0}\" file is not open", m_fileName)); // Ensure that the current file is active. if (m_fileType != ArchiveFileType.Active) throw new InvalidOperationException("Data can only be directly read from files that are Active"); // Ensure that the start and end time are valid. if (startTime.CompareTo(endTime) > 0) throw new ArgumentException("End Time precedes Start Time in the specified time span"); List<Info> dataFiles = new List<Info>(); bool pendingRollover = false; bool usingActiveFile = false; if (startTime.CompareTo(m_fat.FileStartTime) < 0) { // Data is to be read from historic file(s) - make sure that the list has been built if (m_buildHistoricFileListThread.IsAlive) m_buildHistoricFileListThread.Join(); lock (m_historicArchiveFiles) { dataFiles.AddRange(m_historicArchiveFiles.FindAll(info => FindHistoricArchiveFileForRead(info, startTime, endTime))); } } if (endTime.CompareTo(m_fat.FileStartTime) >= 0) { // Data is to be read from the active file. Info activeFileInfo = new Info(m_fileName) { StartTimeTag = m_fat.FileStartTime, EndTimeTag = m_fat.FileEndTime }; dataFiles.Add(activeFileInfo); } // Read data from all qualifying files. foreach (Info dataFile in dataFiles) { ArchiveFile file = null; try { if (string.Compare(dataFile.FileName, m_fileName, StringComparison.OrdinalIgnoreCase) == 0) { // Read data from current file. usingActiveFile = true; file = this; // Atomically increment total number of readers for active file Interlocked.Increment(ref m_activeFileReaders); // Handle race conditions between rollover // and incrementing the active readers while (m_rolloverInProgress) { Interlocked.Decrement(ref m_activeFileReaders); m_rolloverWaitHandle.WaitOne(); Interlocked.Increment(ref m_activeFileReaders); } } else { // Read data from historic file. usingActiveFile = false; file = new ArchiveFile(); file.FileName = dataFile.FileName; file.StateFile = m_stateFile; file.IntercomFile = m_intercomFile; file.MetadataFile = m_metadataFile; file.FileAccessMode = FileAccess.Read; file.Open(); } // Create new data point scanner for the desired points in this file and given time range IArchiveFileScanner scanner; if (timeSorted) scanner = new TimeSortedArchiveFileScanner(); else scanner = new ArchiveFileScanner(); scanner.FileAllocationTable = file.Fat; scanner.HistorianIDs = historianIDs; scanner.StartTime = startTime; scanner.EndTime = endTime; scanner.ResumeFrom = resumeFrom; scanner.DataReadExceptionHandler = (sender, e) => OnDataReadException(e.Argument); // Reset resumeFrom to scan from beginning after picking up where left off from roll over resumeFrom = null; // Return data points foreach (IDataPoint dataPoint in scanner.Read()) { yield return dataPoint; // If a rollover needs to happen, we need to relinquish read lock and close file if (m_rolloverInProgress) { resumeFrom = dataPoint; pendingRollover = true; break; } } } finally { if (usingActiveFile) { // Atomically decrement active file reader count to signal in-process code that read is complete or yielded Interlocked.Decrement(ref m_activeFileReaders); } else if ((object)file != null) { file.Dispose(); } } if (pendingRollover) break; } if (pendingRollover) { // Recurse into this function with an updated start time and last read point ID so that read can // resume right where it left off - recursed function call will wait until rollover is complete foreach (IDataPoint dataPoint in ReadData(historianIDs, startTime, endTime, resumeFrom, timeSorted)) { yield return dataPoint; } } }
/// <summary> /// Tests if the <paramref name="dataBlockPointer"/> matches the specified search criteria. /// </summary> /// <param name="dataBlockPointer"><see cref="ArchiveDataBlockPointer"/> to test.</param> /// <param name="historianID">Desired historian ID.</param> /// <param name="startTime">Desired start time.</param> /// <param name="endTime">Desired end time.</param> /// <returns><c>true</c> if the specified <paramref name="dataBlockPointer"/> is for <paramref name="historianID"/> and falls within the <paramref name="startTime"/> and <paramref name="endTime"/>; otherwise <c>false</c>.</returns> public static bool Matches(this ArchiveDataBlockPointer dataBlockPointer, int historianID, TimeTag startTime, TimeTag endTime) { // Note: The StartTime value of the pointer is ignored if m_searchStartTime = TimeTag.MinValue and // m_searchEndTime = TimeTag.MaxValue. In this case only the PointID value is compared. This // comes in handy when the first or last pointer is to be found from the list of pointers for // a point ID in addition to all the pointers for a point ID. if ((object)dataBlockPointer != null) return dataBlockPointer.HistorianID == historianID && (startTime.CompareTo(TimeTag.MinValue) == 0 || dataBlockPointer.StartTime.CompareTo(startTime) >= 0) && (endTime.CompareTo(TimeTag.MaxValue) == 0 || dataBlockPointer.StartTime.CompareTo(endTime) <= 0); return false; }