//******************************************************************************************************
// ScheduleManager.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 08/01/2006 - Pinal C. Patel
// Original version of source code generated.
// 04/23/2007 - Pinal C. Patel
// Made the schedules dictionary case-insensitive.
// 04/24/2007 - Pinal C. Patel
// Implemented the IPersistSettings and ISupportInitialize interfaces.
// 05/02/2007 - Pinal C. Patel
// Converted schedules to a list instead of a dictionary.
// 11/30/2007 - Pinal C. Patel
// Modified the "design time" check in EndInit() method to use LicenseManager.UsageMode property
// instead of DesignMode property as the former is more accurate than the latter.
// Modified the Stop() method to avoid a null reference exception that was most likely causing
// the IDE of the component's consumer to crash in design-mode.
// 09/19/2008 - J. Ritchie Carroll
// Convert to C#.
// 11/04/2008 - Pinal C. Patel
// Edited code comments.
// 06/18/2009 - Pinal C. Patel
// Fixed the implementation of Enabled property.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 06/21/2010 - Stephen C. Wills
// Modified code to avoid calls to abort the m_startTimerThread.
// 07/02/2010 - J. Ritchie Carroll
// Fixed an issue related to accessing a disposed timer.
// 08/04/2010 - Pinal C. Patel
// Fixed an issue with NullReferenceException being encountered when ScheduleManager object is
// disposed before it successfully starts up (StartTimer() method).
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Timers;
using GSF.Collections;
using GSF.Configuration;
using Timer = System.Timers.Timer;
namespace GSF.Scheduling
{
///
/// Monitors multiple at an interval of one minute to check if they are due.
///
///
/// This example shows how to use the component:
///
/// using System;
/// using GSF;
/// using GSF.Scheduling;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// ScheduleManager scheduler = new ScheduleManager();
/// scheduler.Initialize();
/// // Add event handlers.
/// scheduler.Starting += scheduler_Starting;
/// scheduler.Started += scheduler_Started;
/// scheduler.ScheduleDue += scheduler_ScheduleDue;
/// // Add test schedules.
/// scheduler.AddSchedule("Run.Notepad", "* * * * *");
/// scheduler.AddSchedule("Run.Explorer", "* * * * *");
/// // Start the scheduler.
/// scheduler.Start();
///
/// Console.ReadLine();
/// }
///
/// static void scheduler_Started(object sender, EventArgs e)
/// {
/// Console.WriteLine("Scheduler has started successfully.");
/// }
///
/// static void scheduler_Starting(object sender, EventArgs e)
/// {
/// Console.WriteLine("Scheduler is waiting to be started.");
/// }
///
/// static void scheduler_ScheduleDue(object sender, EventArgsSchedule]]> e)
/// {
/// Console.WriteLine(string.Format("{0} schedule is due for processing.", e.Argument.Name));
/// }
/// }
///
///
///
[ToolboxBitmap(typeof(ScheduleManager))]
public class ScheduleManager : Component, ISupportLifecycle, ISupportInitialize, IProvideStatus, IPersistSettings
{
#region [ Members ]
// Constants
///
/// Specifies the default value for the property.
///
public const bool DefaultPersistSettings = false;
///
/// Specifies the default value for the property.
///
public const string DefaultSettingsCategory = "ScheduleManager";
///
/// Number of milliseconds between timer ticks.
///
private const int TimerInterval = 60000;
// Events
///
/// Occurs while the is waiting to start at the top of the minute.
///
[Category("State"),
Description("Occurs while the ScheduleManager is waiting to start at the top of the minute.")]
public event EventHandler Starting;
///
/// Occurs when the has started at the top of the minute.
///
[Category("State"),
Description("Occurs when the ScheduleManager has started at the top of the minute.")]
public event EventHandler Started;
///
/// Occurs asynchronously when a is due according to the rule specified for it.
///
///
/// is the that is due.
///
[Category("Schedules"),
Description("Occurs asynchronously when a Schedule is due according to the rule specified for it.")]
public event EventHandler> ScheduleDue;
///
/// Occurs when the a particular is being checked to see if it is due.
///
///
/// is the that is being checked to see if it is due.
///
[Category("Schedules"),
Description("Occurs when the a particular Schedule is being checked to see if it is due.")]
public event EventHandler> ScheduleDueCheck;
// Fields
private bool m_persistSettings;
private string m_settingsCategory;
private readonly List m_schedules;
private Timer m_timer;
#if ThreadTracking
private ManagedThread m_startTimerThread;
#else
private Thread m_startTimerThread;
#endif
private bool m_disposed;
private bool m_initialized;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
public ScheduleManager()
{
m_persistSettings = DefaultPersistSettings;
m_settingsCategory = DefaultSettingsCategory;
m_schedules = new List();
}
///
/// Initializes a new instance of the class.
///
/// object that contains the .
public ScheduleManager(IContainer container)
: this()
{
if ((object)container != null)
container.Add(this);
}
#endregion
#region [ Properties ]
///
/// Gets or sets a boolean value that indicates whether the settings of object are
/// to be saved to the config file.
///
[Category("Persistence"),
DefaultValue(DefaultPersistSettings),
Description("Indicates whether the settings of ScheduleManager object are to be saved to the config file.")]
public bool PersistSettings
{
get
{
return m_persistSettings;
}
set
{
m_persistSettings = value;
}
}
///
/// Gets or sets the category under which the settings of object are to be saved
/// to the config file if the property is set to true.
///
/// The value being assigned is null or empty string.
[Category("Persistence"),
DefaultValue(DefaultSettingsCategory),
Description("Category under which the settings of ScheduleManager object are to be saved to the config file if the PersistSettings property is set to true.")]
public string SettingsCategory
{
get
{
return m_settingsCategory;
}
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException(nameof(value));
m_settingsCategory = value;
}
}
///
/// Gets a list of all monitored by the object.
///
///
/// Thread-safety Warning: Due to the asynchronous nature of , a lock must be
/// obtained on before accessing it.
///
[Category("Settings"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Description("Gets a list of all Schedule monitored by the ScheduleManager object.")]
public List Schedules
{
get
{
return m_schedules;
}
}
///
/// Gets a boolean value that indicates whether the is running.
///
[Browsable(false)]
public bool IsRunning
{
get
{
if ((object)m_timer != null)
return m_timer.Enabled;
return false;
}
}
///
/// Gets or sets a boolean value that indicates whether the object is currently enabled.
///
///
/// property is not be set by user-code directly.
///
[Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool Enabled
{
get
{
return IsRunning;
}
set
{
if (value && !Enabled)
Start();
else if (!value && Enabled)
Stop();
}
}
///
/// Gets a flag that indicates whether the object has been disposed.
///
[Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsDisposed
{
get
{
return m_disposed;
}
}
///
/// Gets the unique identifier of the object.
///
[Browsable(false)]
public string Name
{
get
{
return m_settingsCategory;
}
}
///
/// Gets the descriptive status of the object.
///
[Browsable(false)]
public string Status
{
get
{
StringBuilder status = new StringBuilder();
status.Append(" Number of schedules: ");
status.Append(m_schedules.Count);
status.AppendLine();
lock (m_schedules)
{
foreach (Schedule schedule in m_schedules)
{
status.AppendLine();
status.Append(schedule.Status);
}
}
return status.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Initializes the object.
///
///
/// is to be called by user-code directly only if the
/// object is not consumed through the designer surface of the IDE.
///
public void Initialize()
{
if (!m_initialized)
{
LoadSettings(); // Load settings from the config file.
m_initialized = true; // Initialize only once.
}
}
///
/// Performs necessary operations before the object properties are initialized.
///
///
/// should never be called by user-code directly. This method exists solely for use
/// by the designer if the object is consumed through the designer surface of the IDE.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public void BeginInit()
{
if (!DesignMode)
{
try
{
// Nothing needs to be done before component is initialized.
}
catch (Exception)
{
// Prevent the IDE from crashing when component is in design mode.
}
}
}
///
/// Performs necessary operations after the object properties are initialized.
///
///
/// should never be called by user-code directly. This method exists solely for use
/// by the designer if the object is consumed through the designer surface of the IDE.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public void EndInit()
{
if (!DesignMode)
{
try
{
Initialize();
}
catch (Exception)
{
// Prevent the IDE from crashing when component is in design mode.
}
}
}
///
/// Saves settings for the object to the config file if the
/// property is set to true.
///
/// has a value of null or empty string.
public void SaveSettings()
{
if (m_persistSettings)
{
// Ensure that settings category is specified.
if (string.IsNullOrEmpty(m_settingsCategory))
throw new ConfigurationErrorsException("SettingsCategory property has not been set");
// Save settings under the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory];
settings.Clear();
lock (m_schedules)
{
foreach (Schedule schedule in m_schedules)
{
if (schedule.UseLocalTime)
settings[schedule.Name, true].Update(string.Format("rule={0}; useLocalTime={1}", schedule.Rule, schedule.UseLocalTime), schedule.Description);
else
settings[schedule.Name, true].Update(schedule.Rule, schedule.Description);
}
}
config.Save();
}
}
///
/// Loads saved settings for the object from the config file if the
/// property is set to true.
///
/// has a value of null or empty string.
public void LoadSettings()
{
if (m_persistSettings)
{
// Ensure that settings category is specified.
if (string.IsNullOrEmpty(m_settingsCategory))
throw new ConfigurationErrorsException("SettingsCategory property has not been set");
// Load settings from the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory];
foreach (CategorizedSettingsElement setting in settings)
{
if ((object)setting != null && !string.IsNullOrWhiteSpace(setting.Name))
{
string value = setting.Value.ToNonNullNorWhiteSpace("* * * * *");
Dictionary lookup = value.ParseKeyValuePairs();
// Add the schedule if it doesn't exist or update it otherwise with data from the config file.
Schedule existingSchedule = FindSchedule(setting.Name);
if ((object)existingSchedule == null)
{
// Schedule doesn't exist, so we'll add it.
lock (m_schedules)
{
m_schedules.Add(new Schedule(setting.Name, lookup.GetOrDefault("rule", key => value), setting.Description.ToNonNullString(), lookup.GetOrDefault("useLocalTime").ParseBoolean()));
}
}
else
{
// Schedule exists, so we'll update it.
existingSchedule.Name = setting.Name;
existingSchedule.Rule = lookup.GetOrDefault("rule", key => value);
existingSchedule.Description = setting.Description.ToNonNullString();
existingSchedule.UseLocalTime = lookup.GetOrDefault("useLocalTime").ParseBoolean();
}
}
}
}
}
///
/// Starts the asynchronously if not running.
///
public void Start()
{
if (!IsRunning && (object)m_startTimerThread == null)
{
// Initialize if uninitialized.
Initialize();
// Initialize timer that checks schedules.
m_timer = new Timer(TimerInterval);
m_timer.Elapsed += m_timer_Elapsed;
// Spawn new thread to start timer at top of the minute.
#if ThreadTracking
m_startTimerThread = new ManagedThread(StartTimer);
m_startTimerThread.Name = "GSF.Scheduling.ScheduleManager.StartTimer()";
#else
m_startTimerThread = new Thread(StartTimer);
m_startTimerThread.IsBackground = true;
#endif
m_startTimerThread.Start();
}
}
///
/// Stops the if running.
///
public void Stop()
{
if ((object)m_timer != null)
{
m_timer.Elapsed -= m_timer_Elapsed;
m_timer.Dispose();
}
m_timer = null;
m_startTimerThread = null;
}
///
/// Checks all of the to determine if they are due.
///
public void CheckAllSchedules()
{
lock (m_schedules)
{
foreach (Schedule schedule in m_schedules)
{
OnScheduleDueCheck(new EventArgs(schedule));
// Schedule is due so raise the event.
if (schedule.IsDue())
OnScheduleDue(schedule);
}
}
}
///
/// Attempts to add a new .
///
/// Name of the new .
/// Rule of the new .
/// true if a new was added or an existing one was updated; otherwise false.
public bool AddSchedule(string scheduleName, string scheduleRule)
{
return AddSchedule(scheduleName, scheduleRule, false);
}
///
/// Attempts to add a new .
///
/// Name of the new .
/// Rule of the new .
/// true to update existing with the specified ; otherwise false.
/// true if a new was added or an existing one was updated; otherwise false.
public bool AddSchedule(string scheduleName, string scheduleRule, bool updateExisting)
{
return AddSchedule(scheduleName, scheduleRule, string.Empty, updateExisting);
}
///
/// Attempts to add a new .
///
/// Name of the new .
/// Rule of the new .
/// Description of the new .
/// true if a new was added; otherwise false.
public bool AddSchedule(string scheduleName, string scheduleRule, string scheduleDescription)
{
return AddSchedule(scheduleName, scheduleRule, scheduleDescription, false);
}
///
/// Attempts to add a new .
///
/// Name of the new .
/// Rule of the new .
/// Description of the new .
/// true to update existing with the specified ; otherwise false.
/// true if a new was added or an existing one was updated; otherwise false.
public bool AddSchedule(string scheduleName, string scheduleRule, string scheduleDescription, bool updateExisting)
{
Schedule existingSchedule = FindSchedule(scheduleName);
if ((object)existingSchedule == null)
{
// Schedule doesn't exist, so we'll add it.
lock (m_schedules)
{
m_schedules.Add(new Schedule(scheduleName, scheduleRule, scheduleDescription));
}
return true;
}
// Schedule exists, we'll update if specified.
if (updateExisting)
{
// Update existing schedule.
existingSchedule.Name = scheduleName;
existingSchedule.Rule = scheduleRule;
existingSchedule.Description = scheduleDescription;
return true;
}
// Leave existing schedule alone.
return false;
}
///
/// Attempts to remove a with the specified name if one exists.
///
/// Name of the to be removed.
/// true if the was removed; otherwise false.
public bool RemoveSchedule(string scheduleName)
{
Schedule scheduleToRemove = FindSchedule(scheduleName);
if ((object)scheduleToRemove != null)
{
// Schedule exists, so remove it.
lock (m_schedules)
{
m_schedules.Remove(scheduleToRemove);
}
return true;
}
// Can't remove schedule, since it doesn't exist.
return false;
}
///
/// Searches for the with the specified name.
///
/// Name of the to be obtained.
/// object if a match is found; otherwise null.
public Schedule FindSchedule(string scheduleName)
{
Schedule match = null;
lock (m_schedules)
{
foreach (Schedule schedule in m_schedules)
{
if (string.Compare(schedule.Name, scheduleName, StringComparison.OrdinalIgnoreCase) == 0)
{
match = schedule;
break;
}
}
}
return match;
}
///
/// Raises the event.
///
protected virtual void OnStarting()
{
if ((object)Starting != null)
Starting(this, EventArgs.Empty);
}
///
/// Raises the event.
///
protected virtual void OnStarted()
{
if ((object)Started != null)
Started(this, EventArgs.Empty);
}
///
/// Raises the event.
///
/// to send to event.
protected virtual void OnScheduleDue(Schedule schedule)
{
if ((object)ScheduleDue != null)
{
EventArgs args = new EventArgs(schedule);
foreach (Delegate invocationFunction in ScheduleDue.GetInvocationList())
{
EventHandler> handler = invocationFunction as EventHandler>;
// Asynchronously invoke handlers...
if ((object)handler != null)
handler.BeginInvoke(this, args, null, null);
}
}
}
///
/// Raises the event.
///
/// Event data.
protected virtual void OnScheduleDueCheck(EventArgs e)
{
if ((object)ScheduleDueCheck != null)
ScheduleDueCheck(this, e);
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
// This will be done regardless of whether the object is finalized or disposed.
if (disposing)
{
// This will be done only when the object is disposed by calling Dispose().
Stop();
SaveSettings();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
private void StartTimer()
{
while (!IsRunning && (object)m_timer != null)
{
OnStarting();
if (DateTime.UtcNow.Second == 0)
{
// We'll start the timer that will check the schedules at top of the minute.
m_timer.Start();
OnStarted();
CheckAllSchedules();
break;
}
Thread.Sleep(1000);
}
m_startTimerThread = null;
}
private void m_timer_Elapsed(object sender, ElapsedEventArgs e)
{
CheckAllSchedules();
}
#endregion
}
}