/*
* Process Hacker -
* provider base class
*
* Copyright (C) 2008-2009 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Threading;
using ProcessHacker.Common;
using ProcessHacker.Common.Objects;
namespace ProcessHacker
{
///
/// Provides services for continuously updating a dictionary.
///
public abstract class Provider : BaseObject, IProvider
{
///
/// A generic delegate which is used when updating the dictionary.
///
public delegate void ProviderUpdateOnce();
///
/// Represents a handler called when a dictionary item is added.
///
/// The added item.
public delegate void ProviderDictionaryAdded(TValue item);
///
/// Represents a handler called when a dictionary item is modified.
///
/// The modified item.
public delegate void ProviderDictionaryModified(TValue oldItem, TValue newItem);
///
/// Represents a handler called when a dictionary item is removed.
///
/// The removed item.
public delegate void ProviderDictionaryRemoved(TValue item);
///
/// Represents a handler called when an error occurs while updating.
///
/// The raised exception.
public delegate void ProviderError(Exception ex);
///
/// Occurs when the provider needs to update the dictionary (after waiting the duration of the interval).
///
protected event ProviderUpdateOnce ProviderUpdate;
public new event Action Disposed;
public event ProviderUpdateOnce BeforeUpdate;
///
/// Occurs when the provider has been updated.
///
public event ProviderUpdateOnce Updated;
///
/// Occurs when the provider adds an item to the dictionary.
///
public event ProviderDictionaryAdded DictionaryAdded;
///
/// Occurs when the provider modifies an item in the dictionary.
///
public event ProviderDictionaryModified DictionaryModified;
///
/// Occurs when the provider removes an item from the dictionary.
///
public event ProviderDictionaryRemoved DictionaryRemoved;
///
/// Occurs when an exception is raised while updating.
///
public event ProviderError Error;
private string _name = string.Empty;
private Thread _thread;
private IDictionary _dictionary;
private object _busyLock = new object();
private bool _disposing = false;
private bool _busy = false;
private bool _createThread = true;
private bool _enabled = false;
private int _runCount = 0;
private int _interval;
///
/// Creates a new instance of the Provider class.
///
public Provider()
: this(new Dictionary())
{ }
///
/// Creates a new instance of the Provider class, specifying a
/// custom equality comparer.
///
public Provider(IEqualityComparer comparer)
: this(new Dictionary(comparer))
{ }
///
/// Creates a new instance of the Provider class, specifying a
/// custom instance.
///
public Provider(IDictionary dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
protected override void DisposeObject(bool disposing)
{
Logging.Log(Logging.Importance.Information, "Provider (" + this.Name + "): disposing (" + disposing.ToString() + ")");
_disposing = true;
if (disposing)
Monitor.Enter(_busyLock);
//if (_thread != null)
//{
// _thread.Abort();
// _thread = null;
//}
if (this.Disposed != null)
{
try
{
this.Disposed(this);
}
catch (Exception ex)
{
Logging.Log(ex);
}
}
if (disposing)
Monitor.Exit(_busyLock);
Logging.Log(Logging.Importance.Information, "Provider (" + this.Name + "): finished disposing (" + disposing.ToString() + ")");
}
public string Name
{
get { return _name; }
protected set
{
_name = value;
if (_name == null)
_name = string.Empty;
}
}
///
/// Determines whether the provider is currently updating.
///
public bool Busy
{
get { return _busy; }
}
///
/// If enabled, the provider manages a background thread for the updater.
///
public bool CreateThread
{
get { return _createThread; }
set { _createThread = value; }
}
///
/// Determines whether the provider should update.
///
public bool Enabled
{
get { return _enabled; }
set
{
_enabled = value;
if (_enabled && _createThread && _thread == null)
{
_thread = new Thread(new ThreadStart(Update));
_thread.IsBackground = true;
_thread.SetApartmentState(ApartmentState.STA);
_thread.Start();
_thread.Priority = ThreadPriority.Lowest;
}
}
}
///
/// Gets the number of times this provider has updated.
///
public int RunCount
{
get { return _runCount; }
}
///
/// Gets or sets the interval to wait between each update.
///
public int Interval
{
get { return _interval; }
set { _interval = value; }
}
///
/// Gets the dictionary.
///
public IDictionary Dictionary
{
get { return _dictionary; }
protected set { _dictionary = value; }
}
///
/// Updates the provider if it is enabled.
///
private void Update()
{
while (true)
{
if (_enabled && !_disposing)
{
this.RunOnce();
}
Thread.Sleep(_interval);
}
}
///
/// Updates the provider. If it is already updating, this function waits until it finishes.
///
public void RunOnce()
{
lock (_busyLock)
{
// Bail out if we are disposing
if (_disposing)
{
Logging.Log(Logging.Importance.Warning, "Provider (" + _name + "): RunOnce: currently disposing");
return;
}
_busy = true;
if (ProviderUpdate != null)
{
try
{
if (BeforeUpdate != null)
BeforeUpdate();
}
catch
{ }
try
{
ProviderUpdate();
_runCount++;
}
catch (Exception ex)
{
try
{
if (Error != null)
Error(ex);
}
catch
{ }
Logging.Log(ex);
}
try
{
if (Updated != null)
Updated();
}
catch
{ }
}
_busy = false;
}
}
///
/// Updates the provider in an internal worker thread.
///
public void RunOnceAsync()
{
WorkQueue.GlobalQueueWorkItemTag(new Action(this.RunOnce), "provider-runonceasync");
}
///
/// Executes code as soon as no updater is running.
///
public void InterlockedExecute(Delegate action, params object[] args)
{
this.InterlockedExecute(action, -1, args);
}
///
/// Executes code as soon as no updater is running.
///
public void InterlockedExecute(Delegate action, int timeout, params object[] args)
{
lock (_busyLock)
action.DynamicInvoke(args);
}
///
/// Waits for the current update process to finish. If an update process is not currently
/// running, this function returns immediately.
///
public void Wait()
{
this.Wait(-1);
}
///
/// Waits for the current update process to finish. If an update process is not currently
/// running, this function returns immediately. You may specify a timeout for the wait.
///
/// The time in milliseconds to wait for the update process to finish.
/// Whether the update process was finished before the timeout.
public bool Wait(int timeout)
{
if (Monitor.TryEnter(_busyLock, timeout))
{
Monitor.Exit(_busyLock);
return true;
}
return false;
}
private void CallEvent(Delegate e, params object[] args)
{
if (e != null)
{
try
{
e.DynamicInvoke(args);
}
catch (Exception ex)
{
Logging.Log(ex);
}
}
}
protected void OnDictionaryAdded(TValue item)
{
this.CallEvent(this.DictionaryAdded, item);
}
protected void OnDictionaryModified(TValue oldItem, TValue newItem)
{
this.CallEvent(this.DictionaryModified, oldItem, newItem);
}
protected void OnDictionaryRemoved(TValue item)
{
this.CallEvent(this.DictionaryRemoved, item);
}
}
}