Files
duplicati/Duplicati/Scheduler/Scheduler.cs
T
kenb@programmer.net 9b43ae6674 Removed DynamicLoader error log message
Several improvements to Scheduler
Added Scheduler backup monitors


git-svn-id: https://duplicati.googlecode.com/svn/trunk@1003 59da171f-624f-0410-aa54-27559c288bec
2011-11-18 15:39:16 +00:00

407 lines
19 KiB
C#

#region Disclaimer / License
// Copyright (C) 2011, Kenneth Bergeron, IAP Worldwide Services, Inc
// NOAA :: National Marine Fisheries Service
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
// This is a x86 exe - If you are using Express, see this:
// http://social.msdn.microsoft.com/Forums/en-US/Vsexpressvcs/thread/4650481d-b385-43f3-89c7-c07546a7f7cd
//
namespace Duplicati.Scheduler
{
/// <summary>
/// The mother form, shows a Summary and a tool menu
/// </summary>
public partial class Scheduler : Form
{
/// <summary>
/// The mother form, shows a Summary and a tool menu
/// </summary>
public Scheduler()
{
InitializeComponent();
this.Text = "Schedule (" + Utility.User.UserName + ")";
// Load the database
this.SchedulerDataSet.Load();
// Advertise for pipe connections
Pipe.Server(OperationProgress);
// Put up the navigation buttons
EnableNav();
#if DEBUG
this.toolStripButton2.Visible = true;
#endif
}
private System.IO.FileSystemWatcher itsLogWatcher;
// This little guy just looks for new log files and turns the "Log" button green if one arrives
private void InitializeLogWatcher()
{
itsLogWatcher = new System.IO.FileSystemWatcher(Utility.Tools.LogFileDirectory(Program.Package), "*.txt") { NotifyFilter = System.IO.NotifyFilters.Attributes };
itsLogWatcher.Changed += new System.IO.FileSystemEventHandler(
delegate(object sender, System.IO.FileSystemEventArgs e)
{
this.BeginInvoke((Action)delegate() { this.LogToolStripButton.ForeColor = System.Drawing.Color.Green; });
});
itsLogWatcher.EnableRaisingEvents = true;
}
/// <summary>
/// Adds a new job to the task scheduler
/// </summary>
/// <param name="aRow">Job to add</param>
/// <param name="aTrigger">Trigger to add</param>
/// <returns>True if all went well</returns>
private bool NewTask(Duplicati.Scheduler.Data.SchedulerDataSet.JobsRow aRow, Microsoft.Win32.TaskScheduler.Trigger aTrigger)
{
// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
if (this.toolStripButton2.Checked && aTrigger is Microsoft.Win32.TaskScheduler.TimeTrigger)
{
((Microsoft.Win32.TaskScheduler.TimeTrigger)aTrigger).Repetition.Interval = TimeSpan.FromMinutes(5);
((Microsoft.Win32.TaskScheduler.TimeTrigger)aTrigger).Repetition.Duration = TimeSpan.FromDays(1);
}
// Used for password
System.Security.SecureString ss = null;
bool Result = false;
do
{
DialogResult dr = DialogResult.None;
// Get the user credentials from the user
Result = Utility.Tools.NoException((Action)delegate()
{
dr = TaskScheduler.InvokeCredentialDialog(this, aRow.TaskName,
"Enter user account information for running this backup:", Utility.User.UserName, out ss);
});
// If there was an error or user pressed Cancel, bail.
if (!Result || dr != DialogResult.OK) return false;
// Send the Trigger to the task scheduler
Result = Utility.Tools.NoException((Action)delegate()
{
TaskScheduler.CreateOrUpdateTask(aRow.TaskName,
System.IO.Path.Combine(Application.StartupPath, "Duplicati.Scheduler.RunBackup.exe"),
"Duplicati backup task", Utility.User.UserName, ss, aTrigger,
"\"" + aRow.Name + "\" \"" + Duplicati.Scheduler.Data.SchedulerDataSet.DefaultPath() + "\"");
});
// Assume any error is a security thing and make user enter password again.
if (!Result && MessageBox.Show("Logon information is not valid for " + Utility.User.UserName + ", [OK] to try again?",
"Wrong", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel) return false;
} while (!Result);
return Result; // gonna be true
}
/// <summary>
/// Pressed ADD button, add a new job
/// </summary>
private void AddToolStripButton_Click(object sender, EventArgs e)
{
bool OK = false;
// Get the name, must be unique
SchedulerNameDialog snd = new SchedulerNameDialog();
do
{
if (snd.ShowDialog() == DialogResult.Cancel) return;
OK = this.SchedulerDataSet.Jobs.FindByName(snd.BackupName) == null;
if (!OK) MessageBox.Show("Backup name " + snd.BackupName + " is already used.");
} while (!OK);
this.Cursor = Cursors.WaitCursor;
// Fire up the editor
JobDialog sd = new JobDialog(this.SchedulerDataSet.Jobs.NewJobsRow(snd.BackupName));
if (sd.ShowDialog() == DialogResult.OK && NewTask(sd.Row, sd.Trigger))
{
// Add this to the database
this.SchedulerDataSet.Jobs.AddJobsRow(sd.Row);
Save(sd.Row);
this.jobSummary1.Enabled = sd.Row.Enabled;
}
this.Cursor = Cursors.Default;
}
/// <summary>
/// Reflects the current JobsBindingSource row as a JobsRow
/// </summary>
private Duplicati.Scheduler.Data.SchedulerDataSet.JobsRow CurrentRow
{
get
{
if (this.JobsBindingSource.Current == null) return null;
return (Duplicati.Scheduler.Data.SchedulerDataSet.JobsRow)((DataRowView)this.JobsBindingSource.Current).Row;
}
}
/// <summary>
/// Pressed the EDIT button
/// </summary>
private void EditToolStripButton_Click(object sender, EventArgs e)
{
if (CurrentRow == null) return;
this.Cursor = Cursors.WaitCursor;
// Fire up the editor
JobDialog sd = new JobDialog(CurrentRow);
// Get the trigger
sd.Trigger = TaskScheduler.GetTrigger(CurrentRow.TaskName);
// User said OK and the Task Scheduler took it OK, so save it
if (sd.ShowDialog() == DialogResult.OK && NewTask(sd.Row, sd.Trigger))
{
// Save the edited row
Save(sd.Row);
this.jobSummary1.Enabled = sd.Row.Enabled;
}
// Updates the screen
JobsBindingSource_CurrentChanged(null, null);
this.Cursor = Cursors.Default; // Should be in a 'finnaly'
}
/// <summary>
/// Save the database
/// </summary>
/// <param name="aRow">Altered row</param>
private void Save(Duplicati.Scheduler.Data.SchedulerDataSet.JobsRow aRow)
{
if (aRow == null) return; //
aRow.LastMod = DateTime.Now;
// Update the drive table
System.Collections.Generic.Dictionary<string, string> DriveMap = new System.Collections.Generic.Dictionary<string, string>();
foreach (System.IO.DriveInfo di in System.IO.DriveInfo.GetDrives())
if ((di.IsReady && di.DriveType == System.IO.DriveType.Network))
DriveMap.Add(di.Name[0].ToString(), Utility.Tools.DriveToUNC(di.Name[0]));
aRow.SetDriveMaps(DriveMap);
Exception Ex = this.SchedulerDataSet.Save();
if (Ex != null)
MessageBox.Show("Can not save schedule: " + Ex.Message);
}
/// <summary>
/// Pressed the LOG button - show the history
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LogToolStripButton_Click(object sender, EventArgs e)
{
// If button was green, ungreen it
this.LogToolStripButton.ForeColor = this.FilesToolStripButton.ForeColor;
if (CurrentRow == null) return;
LogView lv = new LogView(CurrentRow.Name);
lv.ShowDialog();
}
/// <summary>
/// Pressed the RESTORE button - List the backups
/// </summary>
private void FilesToolStripButton_Click(object sender, EventArgs e)
{
if (CurrentRow == null) return;
BackupListDialog fd = new BackupListDialog(CurrentRow.Name, CurrentRow.Destination, CurrentRow.Options);
fd.ShowDialog();
}
/// <summary>
/// Pressed the select dropdown, populate the drop-downs
/// </summary>
private void SelectToolStripDropDownButton_DropDownOpening(object sender, EventArgs e)
{
// Make a drop down entry for every job in the database
this.SelectToolStripDropDownButton.DropDownItems.Clear();
this.SelectToolStripDropDownButton.DropDownItems.AddRange(
(from Duplicati.Scheduler.Data.SchedulerDataSet.JobsRow qR in this.SchedulerDataSet.Jobs.Select()
select new ToolStripMenuItem(qR.Name)).ToArray());
}
/// <summary>
/// Clicked a SELECT drop-down - Go to that record
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SelectToolStripDropDownButton_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
// This is how one moves a bindingsource record...
int ix = this.JobsBindingSource.Find("Name", e.ClickedItem.Text);
if (ix >= 0) this.JobsBindingSource.Position = ix;
}
/// <summary>
/// Pressed a NEXT or PREV
/// </summary>
private void PositionButtons_Click(object sender, EventArgs e)
{
if (((ToolStripButton)sender).Text == "next") this.JobsBindingSource.MoveNext();
else this.JobsBindingSource.MovePrevious();
EnableNav();
}
/// <summary>
/// Make sure the correct NEXT, PREV, SELECT buttons are lit depending on the binding
/// </summary>
private void EnableNav()
{
this.SelectToolStripDropDownButton.Enabled = this.JobsBindingSource.Count > 1;
this.NextToolStripButton.Enabled = this.JobsBindingSource.Position < this.JobsBindingSource.Count-1;
this.PrevToolStripButton.Enabled = this.JobsBindingSource.Position > 0;
this.EditToolStripButton.Enabled =
this.DelToolStripButton.Enabled =
this.FilesToolStripButton.Enabled =
this.LogToolStripButton.Enabled =
this.JobsBindingSource.Count > 0;
this.SelectToolStripDropDownButton.Text = "Select (" + this.JobsBindingSource.Count.ToString() + ")";
}
/// <summary>
/// Selected a new record, adjust everything
/// </summary>
private void JobsBindingSource_CurrentChanged(object sender, EventArgs e)
{
if (CurrentRow == null)
{
this.jobSummary1.Clear();
}
else
{
this.jobSummary1.SetSummary(CurrentRow, TaskScheduler.Describe(CurrentRow.TaskName));
this.jobSummary1.Enabled = TaskScheduler.Enabled(CurrentRow.TaskName);
}
EnableNav();
if (CurrentRow == null) this.JobsBindingSource.MoveFirst(); // Will reenter if there are any more records
}
/// <summary>
/// Pressed the DELETE button
/// </summary>
private void DelToolStripButton_Click(object sender, EventArgs e)
{
if (CurrentRow == null || MessageBox.Show("Do you want to delete this job?", "DELETE", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) return;
CurrentRow.Delete();
Save(null);
// Updates the screen
JobsBindingSource_CurrentChanged(null, null);
}
/// <summary>
/// Update the progress bar
/// </summary>
private void SetProgress(int aProgress, string aMessage)
{
Debug.WriteLine(aMessage + ":" + aProgress.ToString());
try
{
if (aMessage.EndsWith("Completed")) aProgress = 100;
if (aProgress < 0)
{
this.toolStripProgressBar1.Style = ProgressBarStyle.Marquee;
}
else
{
this.toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
//if (aProgress > this.toolStripProgressBar1.Maximum) this.toolStripProgressBar1.Maximum = aProgress + 20;
this.toolStripProgressBar1.Value = Math.Min(this.toolStripProgressBar1.Maximum, aProgress);
}
if (!string.IsNullOrEmpty(aMessage))
{
this.toolStripStatusLabel1.Text = aMessage;
this.toolStripStatusLabel1.ToolTipText = aMessage;
}
}
catch (Exception Ex)
{
Debug.WriteLine(Ex);
}
}
/// <summary>
/// Called from the pipe thread to indicate progress from the exec
/// </summary>
/// <param name="aMessage">A message from the exec</param>
private void OperationProgress(string aMessage)
{
if (!this.IsHandleCreated || string.IsNullOrEmpty(aMessage)) return;
// Decode the message
Duplicati.Scheduler.RunBackup.Pipe.ProgressArguments pa = new Duplicati.Scheduler.RunBackup.Pipe.ProgressArguments(aMessage);
// Update the screen, be sure to use the forms thread
if (pa.OK) this.BeginInvoke((Action)delegate()
{ SetProgress(pa.Progress, pa.Job + ":" + pa.Operation + ":" + pa.Message); });
}
/// <summary>
/// Pressed the SETTINGS button, edit settings
/// </summary>
private void SettingsToolStripButton_Click(object sender, EventArgs e)
{
// Deals with its own save, cancel, etc.
new SettingsDialog(this.SchedulerDataSet.Settings).ShowDialog();
}
private void helpToolStripButton_Click(object sender, EventArgs e)
{
}
/// <summary>
/// The settings drop down is coming down, load the items
/// </summary>
private void SettingsToolStripDropDownButton_DropDownOpening(object sender, EventArgs e)
{
// If the drop downs are empty, make them
if (this.monitorSettingsToolStripMenuItem.Enabled && this.monitorSettingsToolStripMenuItem.DropDownItems.Count == 0)
{
// Look for plug in's
string[] Plugins = System.IO.Directory.GetFiles(Application.StartupPath, "Duplicati.Scheduler.Monitor.*.dll");
// No plugins? Disable the settings
if (Plugins.Length == 0)
{
this.monitorSettingsToolStripMenuItem.Enabled = false;
this.monitorSettingsToolStripMenuItem.Visible = false;
}
else
{
// Load a drop down for each plugin
foreach (string Plug in Plugins)
{
// Load the assembly [ Shouldn't there be an Unload? ]
System.Reflection.Assembly Ass = System.Reflection.Assembly.LoadFile(Plug);
if (Ass != null)
{
// Get the update type
Type ClassType = Ass.GetTypes().Where(qR => qR.Name == "Plugin").First();
Duplicati.Scheduler.Data.IMonitorPlugin Monitor = Activator.CreateInstance(ClassType, null)
as Duplicati.Scheduler.Data.IMonitorPlugin;
bool Disabled = this.SchedulerDataSet.Settings.DisabledMonitors.Contains(Monitor.Name);
ToolStripMenuItem tsi = new ToolStripMenuItem(Monitor.Name)
{
Tag = Monitor,
Checked = !Disabled,
ForeColor = Disabled ? System.Drawing.Color.Gray : this.ForeColor
};
tsi.MouseUp += new MouseEventHandler(tsi_MouseUp);
this.monitorSettingsToolStripMenuItem.DropDownItems.Add(tsi);
}
}
}
}
}
/// <summary>
/// Use the mouse up event to determine if user is pressing the check mark area or the drop down
/// </summary>
private void tsi_MouseUp(object sender, MouseEventArgs e)
{
// Only interested in left clicks
if (e.Button != MouseButtons.Left) return;
ToolStripMenuItem tsi = (ToolStripMenuItem)sender;
// The monitor is in the tag
Duplicati.Scheduler.Data.IMonitorPlugin Mon = (Duplicati.Scheduler.Data.IMonitorPlugin)tsi.Tag;
// X < 28, user clicked check mark area
if (e.X < 28 || !tsi.Checked)
{
// Toggle checked
tsi.Checked = !tsi.Checked;
// Enable/Disable the monitor
this.SchedulerDataSet.Settings.EnableMonitor(Mon.Name, tsi.Checked);
this.SchedulerDataSet.Save();
}
// Clicked the sweet spot, show the configuration menu from the monitor
else if (tsi.Checked)
Mon.Configure();
// Turn gray if not selected
tsi.ForeColor = tsi.Checked ? this.ForeColor : System.Drawing.Color.Gray;
}
}
}