Files
duplicati/Duplicati/Library/Modules/Builtin/RunScript.cs
T
kenneth@hexad.dk da1150e6cc Added support for callbacks.
Added a run-script module.
Need to make a Windows example script

git-svn-id: https://duplicati.googlecode.com/svn/trunk@1426 59da171f-624f-0410-aa54-27559c288bec
2012-08-15 18:33:46 +00:00

340 lines
15 KiB
C#

// Copyright (C) 2011, Kenneth Skovhede
// http://www.hexad.dk, opensource@hexad.dk
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Text;
using System.Collections.Generic;
namespace Duplicati.Library.Modules.Builtin
{
public class RunScript : Duplicati.Library.Interface.IGenericCallbackModule
{
private const string STARTUP_OPTION = "run-script-on-start";
private const string FINISH_OPTION = "run-script-on-finish";
private const string REQUIRED_OPTION = "run-script-on-start-required";
private const string TIMEOUT_OPTION = "run-script-timeout";
private const string DEFAULT_TIMEOUT = "60s";
private string m_requiredScript = null;
private string m_startScript = null;
private string m_finishScript = null;
private int m_timeout = 0;
private string m_operationName;
private string m_remoteurl;
private string[] m_localpath;
private IDictionary<string, string> m_options;
#region IGenericModule implementation
public void Configure(IDictionary<string, string> commandlineOptions)
{
commandlineOptions.TryGetValue(STARTUP_OPTION, out m_startScript);
commandlineOptions.TryGetValue(REQUIRED_OPTION, out m_requiredScript);
commandlineOptions.TryGetValue(FINISH_OPTION, out m_finishScript);
string t;
if (!commandlineOptions.TryGetValue(TIMEOUT_OPTION, out t))
t = DEFAULT_TIMEOUT;
m_timeout = (int)Utility.Timeparser.ParseTimeSpan(t).TotalMilliseconds;
m_options = commandlineOptions;
}
public string Key { get { return "runscript"; } }
public string DisplayName { get { return "Run script"; } }
public string Description { get { return "Executes a script before starting an operation, and again on completion"; } }
public bool LoadAsDefault { get { return true; } }
public IList<Duplicati.Library.Interface.ICommandLineArgument> SupportedCommands
{
get
{
return new List<Duplicati.Library.Interface.ICommandLineArgument>(new Duplicati.Library.Interface.ICommandLineArgument[] {
new Duplicati.Library.Interface.CommandLineArgument(STARTUP_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, "", ""),
new Duplicati.Library.Interface.CommandLineArgument(FINISH_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, "", ""),
new Duplicati.Library.Interface.CommandLineArgument(REQUIRED_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, "", "", "false"),
new Duplicati.Library.Interface.CommandLineArgument(TIMEOUT_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Timespan, "", "", DEFAULT_TIMEOUT),
});
}
}
#endregion
#region IGenericCallbackModule implementation
public void OnStart(string operationname, ref string remoteurl, ref string[] localpath)
{
m_operationName = operationname;
m_remoteurl = remoteurl;
m_localpath = localpath;
if (!string.IsNullOrEmpty(m_requiredScript))
Execute(m_requiredScript, "START", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, true, m_options, null);
if (!string.IsNullOrEmpty(m_startScript))
Execute(m_startScript, "START", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, null);
}
public void OnFinish (object result)
{
string r;
if (result == null)
r = "null?";
else if (result is System.Collections.IEnumerable) {
StringBuilder sb = new StringBuilder();
System.Collections.IEnumerable ie = (System.Collections.IEnumerable)result;
System.Collections.IEnumerator ien = ie.GetEnumerator();
ien.Reset();
while (ien.MoveNext()) {
object c = ien.Current;
if (c == null)
continue;
if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) {
object key = c.GetType().GetProperty("Key").GetValue(c, null);
object value = c.GetType().GetProperty("Value").GetValue(c, null);
sb.AppendFormat("{0}: {1}{2}", key, value, Environment.NewLine);
} else
sb.AppendLine(c.ToString());
}
r = sb.ToString();
} else if (result.GetType().IsArray) {
StringBuilder sb = new StringBuilder();
Array a = (Array)result;
for (int i = a.GetLowerBound(0); i <= a.GetUpperBound(0); i++) {
object c = a.GetValue(i);
if (c == null)
continue;
if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) {
object key = c.GetType().GetProperty("Key").GetValue(c, null);
object value = c.GetType().GetProperty("Value").GetValue(c, null);
sb.AppendFormat("{0}: {1}{2}", key, value, Environment.NewLine);
} else
sb.AppendLine(c.ToString());
}
r = sb.ToString();
}
else if (result is Exception)
{
Exception e = (Exception)result;
r = string.Format("Failed: {0}{1}Details: {2}", e.Message, Environment.NewLine, e.ToString());
}
else
{
r = result.ToString();
}
if (!string.IsNullOrEmpty(m_finishScript))
Execute(m_finishScript, "FINISH", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, r);
}
#endregion
private static void Execute(string scriptpath, string eventname, string operationname, ref string remoteurl, ref string[] localpath, int timeout, bool requiredScript, IDictionary<string, string> options, string stdInData)
{
bool catchError = true;
try
{
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
int space_ix = scriptpath.IndexOf(' ');
if (space_ix > 0)
{
psi.FileName = scriptpath.Substring(0, space_ix);
psi.Arguments = scriptpath.Substring(space_ix + 1);
}
else
psi.FileName = scriptpath;
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.RedirectStandardInput = true;
foreach(KeyValuePair<string, string> kv in options)
psi.EnvironmentVariables["DUPLICATI__" + kv.Key.Replace('-', '_')] = kv.Value;
psi.EnvironmentVariables["DUPLICATI__EVENTNAME"] = eventname;
psi.EnvironmentVariables["DUPLICATI__OPERATIONNAME"] = operationname;
psi.EnvironmentVariables["DUPLICATI__REMOTEURL"] = remoteurl;
if (localpath != null)
psi.EnvironmentVariables["DUPLICATI__LOCALPATH"] = string.Join(System.IO.Path.PathSeparator.ToString(), localpath);
string stderr = null;
string stdout = null;
using(System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi))
{
ConsoleDataHandler cs = new ConsoleDataHandler(p);
if (!string.IsNullOrEmpty(stdInData))
using(System.IO.StreamWriter sw = p.StandardInput)
sw.Write(stdInData);
if (timeout <= 0)
p.WaitForExit();
else
p.WaitForExit(timeout);
if (requiredScript)
{
if (!p.HasExited)
{
catchError = false;
throw new Exception(string.Format("Execution of the script \"{0}\" timed out", scriptpath));
}
else if (p.ExitCode != 0)
{
catchError = false;
throw new Exception(string.Format("The script \"{0}\" returned with exit code {1}", scriptpath, p.ExitCode));
}
}
if (p.HasExited)
{
stderr = cs.StandardError;
stdout = cs.StandardOutput;
if (p.ExitCode != 0)
Logging.Log.WriteMessage(string.Format("The script \"{0}\" returned with exit code {1}", scriptpath, p.ExitCode), Duplicati.Library.Logging.LogMessageType.Warning);
}
else
{
Logging.Log.WriteMessage(string.Format("Execution of the script \"{0}\" timed out", scriptpath), Duplicati.Library.Logging.LogMessageType.Warning);
}
}
if (!string.IsNullOrEmpty(stderr))
Logging.Log.WriteMessage(string.Format("The script \"{0}\" reported error messages: {1}", scriptpath, stderr), Duplicati.Library.Logging.LogMessageType.Warning);
//We only allow setting parameters on startup
if (eventname == "START")
{
foreach(string _line in stdout.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
{
string line = _line.Trim();
if (!line.StartsWith("--"))
continue; //Ingore anything that does not start with --
line = line.Substring(2);
int lix = line.IndexOf('=');
if (lix == 0) //Skip --= as that makes no sense
continue;
string key;
string value;
if (lix < 0)
{
key = line.Trim();
value = "";
}
else
{
key = line.Substring(0, lix).Trim();
value = line.Substring(lix + 1).Trim();
if (value.Length >= 2 && value.StartsWith("\"") && value.EndsWith("\""))
value = value.Substring(1, value.Length - 2);
}
if (string.Equals(key, "remoteurl", StringComparison.InvariantCultureIgnoreCase))
{
remoteurl = value;
}
else if (string.Equals(key, "localpath", StringComparison.InvariantCultureIgnoreCase))
{
localpath = value.Split(System.IO.Path.PathSeparator);
}
else if (
string.Equals(key, "eventname", StringComparison.InvariantCultureIgnoreCase) ||
string.Equals(key, "operationname", StringComparison.InvariantCultureIgnoreCase) ||
string.Equals(key, "main-action", StringComparison.InvariantCultureIgnoreCase) ||
key == ""
)
{
//Ignore
}
else
options[key] = value;
}
}
}
catch (Exception ex)
{
Logging.Log.WriteMessage(string.Format("Error while executing script \"{0}\": {1}", scriptpath, ex.Message), Duplicati.Library.Logging.LogMessageType.Warning, ex);
if (!catchError || parseExitCodes)
throw;
}
}
private class ConsoleDataHandler
{
public ConsoleDataHandler(System.Diagnostics.Process p)
{
p.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(HandleOutputDataReceived);
p.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(HandleErrorDataReceived);
p.BeginErrorReadLine();
p.BeginOutputReadLine();
}
private readonly StringBuilder m_standardOutput = new StringBuilder();
private readonly StringBuilder m_standardError = new StringBuilder();
private object m_lock = new object();
private void HandleOutputDataReceived (object sender, System.Diagnostics.DataReceivedEventArgs e)
{
lock(m_lock)
m_standardOutput.Append(e.Data);
}
private void HandleErrorDataReceived (object sender, System.Diagnostics.DataReceivedEventArgs e)
{
lock(m_lock)
m_standardError.Append(e.Data);
}
public string StandardOutput
{
get
{
lock(m_lock)
return m_standardOutput.ToString();
}
}
public string StandardError
{
get
{
lock(m_lock)
return m_standardError.ToString();
}
}
}
}
}