#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);
}
}
}