#region License // Copyright (c) 2010, Jasper Yeh. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of ClearCanvas Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY // OF SUCH DAMAGE. #endregion using System; using System.IO; using System.Text; using System.Threading; using ClearCanvas.Common; using ClearCanvas.Common.Utilities; using ClearCanvas.Desktop; using Nullstack.ClearCanvasEx.DevTools.Common; namespace Nullstack.ClearCanvasEx.DevTools.Logging { /// /// An extension point which views of the should use. /// [ExtensionPoint] public class LogViewerComponentViewExtensionPoint : ExtensionPoint { } /// /// A component for viewing logs in the application. /// [AssociateView(typeof(LogViewerComponentViewExtensionPoint))] public class LogViewerComponent : ApplicationComponent { private const int _initialBufferCapacity = 1024 * 1024; private const int _updateInterval = 1000; private const int _partialSynchronizeThreshold = 1024; private const int _bytesToMatch = 128; private readonly Object _syncLock = new Object(); private event EventHandler> _bufferAppended; private event EventHandler _bufferReset; private SynchronizationContext _synchronizationContext; private DelayedEventPublisher _fileUpdatedPublisher; private FileMonitor _fileMonitor; private MemoryStream _fileBuffer; private Encoding _fileEncoding; private string _filename; private bool _fileExists; public LogViewerComponent() { _fileEncoding = Encoding.Default; } public LogViewerComponent(string filename) : this() { _filename = filename; } public event EventHandler> BufferAppended { add { _bufferAppended += value; } remove { _bufferAppended -= value; } } public event EventHandler BufferReset { add { _bufferReset += value; } remove { _bufferReset -= value; } } public string Filename { get { return _filename; } private set { if (_filename != value) { _filename = value; NotifyPropertyChanged("Filename"); } } } public bool FileExists { get { return _fileExists; } private set { if (_fileExists != value) { _fileExists = value; NotifyPropertyChanged("FileExists"); } } } public string ReadBuffer() { if (_fileBuffer == null) { Refresh(); if (_fileBuffer == null) throw new InvalidOperationException(); } lock (_syncLock) { // don't dispose it because that will close the buffer _fileBuffer.Seek(0, SeekOrigin.Begin); var reader = new StreamReader(_fileBuffer, Encoding.Default, true, 1024); var contents = reader.ReadToEnd(); _fileEncoding = reader.CurrentEncoding; return contents; } } public override void Start() { base.Start(); _synchronizationContext = SynchronizationContext.Current; if (_fileUpdatedPublisher == null) { _fileUpdatedPublisher = new DelayedEventPublisher(FileUpdated, _updateInterval); } if (!string.IsNullOrEmpty(Filename)) Open(Filename); } public override void Stop() { Close(); if (_fileUpdatedPublisher != null) { _fileUpdatedPublisher.Cancel(); _fileUpdatedPublisher.Dispose(); _fileUpdatedPublisher = null; } _synchronizationContext = null; base.Stop(); } public void Open() { var args = new FileDialogCreationArgs(Filename ?? string.Empty); var result = Host.DesktopWindow.ShowOpenFileDialogBox(args); if (result.Action == DialogBoxAction.Ok) { Open(result.FileName); } } public void Open(string filename) { if (string.IsNullOrEmpty(filename)) throw new ArgumentNullException("filename"); Close(); lock (_syncLock) { Filename = filename; FileExists = File.Exists(filename); _fileMonitor = FileMonitor.CreateFileMonitor(filename); _fileMonitor.Changed += FileLiveChanged; _fileMonitor.ExistsChanged += FileExistenceChanged; } Refresh(); } public void Close() { lock (_syncLock) { if (_fileMonitor != null) { _fileMonitor.ExistsChanged -= FileExistenceChanged; _fileMonitor.Changed -= FileLiveChanged; _fileMonitor.Dispose(); _fileMonitor = null; } FileExists = false; Filename = string.Empty; } } public void Refresh() { lock (_syncLock) { DestroyBuffer(); } _fileUpdatedPublisher.PublishNow(this, new EventArgs()); } private void DestroyBuffer() { if (_fileBuffer != null) { _fileBuffer.Close(); _fileBuffer.Dispose(); _fileBuffer = null; } } private void NotifyBufferRefreshed() { _synchronizationContext.Post(s => EventsHelper.Fire(_bufferReset, this, new EventArgs()), null); } private void NotifyBufferUpdated(string data) { _synchronizationContext.Post(s => EventsHelper.Fire(_bufferAppended, this, new ItemEventArgs(data)), null); } private void FileUpdated(object sender, EventArgs e) { string incrementalData = null; lock (_syncLock) { if (_fileBuffer == null) _fileBuffer = new MemoryStream(_initialBufferCapacity); var incrementalUpdateCursor = 0L; if (File.Exists(Filename)) { using (var fs = File.Open(Filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { if (_fileBuffer.Length > _partialSynchronizeThreshold && fs.Length > _fileBuffer.Length) { fs.Seek(_partialSynchronizeThreshold - _bytesToMatch, SeekOrigin.Begin); _fileBuffer.Seek(_partialSynchronizeThreshold - _bytesToMatch, SeekOrigin.Begin); bool match = true; for (int n = 0; n < _bytesToMatch; n++) { if (fs.ReadByte() != _fileBuffer.ReadByte()) { match = false; break; } } if (match) { incrementalUpdateCursor = _fileBuffer.Position; } else { fs.Seek(0, SeekOrigin.Begin); _fileBuffer.Seek(0, SeekOrigin.Begin); _fileBuffer.SetLength(fs.Length); } } else { _fileBuffer.Seek(0, SeekOrigin.Begin); _fileBuffer.SetLength(fs.Length); } var buffer = new byte[512]; int count; do { count = fs.Read(buffer, 0, buffer.Length); if (count > 0) _fileBuffer.Write(buffer, 0, count); } while (count > 0); fs.Close(); } } if (incrementalUpdateCursor != 0) { _fileBuffer.Seek(incrementalUpdateCursor, SeekOrigin.Begin); var reader = new StreamReader(_fileBuffer, _fileEncoding, false, 1024); incrementalData = reader.ReadToEnd(); } } if (!string.IsNullOrEmpty(incrementalData)) NotifyBufferUpdated(incrementalData); else if (incrementalData == null) NotifyBufferRefreshed(); } private void FileLiveChanged(object sender, EventArgs e) { _fileUpdatedPublisher.Publish(sender, e); } private void FileExistenceChanged(object sender, EventArgs e) { lock (_syncLock) { FileExists = File.Exists(Filename); DestroyBuffer(); } _fileUpdatedPublisher.PublishNow(sender, e); } } }