PageRenderTime 36ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Duplicati/Library/Modules/Builtin/RunScript.cs

http://duplicati.googlecode.com/
C# | 345 lines | 277 code | 49 blank | 19 comment | 59 complexity | 2311749c244ab5585ac1d79be351ec22 MD5 | raw file
Possible License(s): LGPL-2.1, LGPL-2.0, Apache-2.0, GPL-3.0, CC-BY-SA-3.0
  1. // Copyright (C) 2011, Kenneth Skovhede
  2. // http://www.hexad.dk, opensource@hexad.dk
  3. //
  4. // This library is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as
  6. // published by the Free Software Foundation; either version 2.1 of the
  7. // License, or (at your option) any later version.
  8. //
  9. // This library is distributed in the hope that it will be useful, but
  10. // WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. // Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public
  15. // License along with this library; if not, write to the Free Software
  16. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. using System;
  18. using System.Diagnostics;
  19. using System.IO;
  20. using System.Text;
  21. using System.Collections.Generic;
  22. using Duplicati.Library.Utility;
  23. namespace Duplicati.Library.Modules.Builtin
  24. {
  25. public class RunScript : Duplicati.Library.Interface.IGenericCallbackModule
  26. {
  27. private const string STARTUP_OPTION = "run-script-before";
  28. private const string FINISH_OPTION = "run-script-after";
  29. private const string REQUIRED_OPTION = "run-script-before-required";
  30. private const string TIMEOUT_OPTION = "run-script-timeout";
  31. private const string DEFAULT_TIMEOUT = "60s";
  32. private string m_requiredScript = null;
  33. private string m_startScript = null;
  34. private string m_finishScript = null;
  35. private int m_timeout = 0;
  36. private string m_operationName;
  37. private string m_remoteurl;
  38. private string[] m_localpath;
  39. private IDictionary<string, string> m_options;
  40. #region IGenericModule implementation
  41. public void Configure(IDictionary<string, string> commandlineOptions)
  42. {
  43. commandlineOptions.TryGetValue(STARTUP_OPTION, out m_startScript);
  44. commandlineOptions.TryGetValue(REQUIRED_OPTION, out m_requiredScript);
  45. commandlineOptions.TryGetValue(FINISH_OPTION, out m_finishScript);
  46. string t;
  47. if (!commandlineOptions.TryGetValue(TIMEOUT_OPTION, out t))
  48. t = DEFAULT_TIMEOUT;
  49. m_timeout = (int)Utility.Timeparser.ParseTimeSpan(t).TotalMilliseconds;
  50. m_options = commandlineOptions;
  51. }
  52. public string Key { get { return "runscript"; } }
  53. public string DisplayName { get { return Strings.RunScript.DisplayName; } }
  54. public string Description { get { return Strings.RunScript.Description; } }
  55. public bool LoadAsDefault { get { return true; } }
  56. public IList<Duplicati.Library.Interface.ICommandLineArgument> SupportedCommands
  57. {
  58. get
  59. {
  60. return new List<Duplicati.Library.Interface.ICommandLineArgument>(new Duplicati.Library.Interface.ICommandLineArgument[] {
  61. new Duplicati.Library.Interface.CommandLineArgument(STARTUP_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.RunScript.StartupoptionShort, Strings.RunScript.StartupoptionLong),
  62. new Duplicati.Library.Interface.CommandLineArgument(FINISH_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.RunScript.FinishoptionShort, Strings.RunScript.FinishoptionLong),
  63. new Duplicati.Library.Interface.CommandLineArgument(REQUIRED_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.RunScript.RequiredoptionShort, Strings.RunScript.RequiredoptionLong),
  64. new Duplicati.Library.Interface.CommandLineArgument(TIMEOUT_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Timespan, Strings.RunScript.TimeoutoptionShort, Strings.RunScript.TimeoutoptionLong, DEFAULT_TIMEOUT),
  65. });
  66. }
  67. }
  68. #endregion
  69. #region IGenericCallbackModule implementation
  70. public void OnStart(string operationname, ref string remoteurl, ref string[] localpath)
  71. {
  72. m_operationName = operationname;
  73. m_remoteurl = remoteurl;
  74. m_localpath = localpath;
  75. if (!string.IsNullOrEmpty(m_requiredScript))
  76. Execute(m_requiredScript, "BEFORE", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, true, m_options, null);
  77. if (!string.IsNullOrEmpty(m_startScript))
  78. Execute(m_startScript, "BEFORE", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, null);
  79. }
  80. public void OnFinish (object result)
  81. {
  82. if (string.IsNullOrEmpty(m_finishScript))
  83. return;
  84. using (TempFile tmpfile = new TempFile())
  85. {
  86. SerializeResult(tmpfile, result);
  87. Execute(m_finishScript, "AFTER", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, tmpfile);
  88. }
  89. }
  90. #endregion
  91. public static void SerializeResult(string file, object result)
  92. {
  93. using (StreamWriter sw = new StreamWriter(file))
  94. {
  95. if (result == null)
  96. {
  97. sw.WriteLine("null?");
  98. }
  99. else if (result is System.Collections.IEnumerable)
  100. {
  101. System.Collections.IEnumerable ie = (System.Collections.IEnumerable)result;
  102. System.Collections.IEnumerator ien = ie.GetEnumerator();
  103. ien.Reset();
  104. while (ien.MoveNext())
  105. {
  106. object c = ien.Current;
  107. if (c == null)
  108. continue;
  109. if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
  110. {
  111. object key = c.GetType().GetProperty("Key").GetValue(c, null);
  112. object value = c.GetType().GetProperty("Value").GetValue(c, null);
  113. sw.WriteLine("{0}: {1}", key, value);
  114. }
  115. else
  116. sw.WriteLine(c.ToString());
  117. }
  118. }
  119. else if (result.GetType().IsArray)
  120. {
  121. Array a = (Array)result;
  122. for (int i = a.GetLowerBound(0); i <= a.GetUpperBound(0); i++)
  123. {
  124. object c = a.GetValue(i);
  125. if (c == null)
  126. continue;
  127. if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
  128. {
  129. object key = c.GetType().GetProperty("Key").GetValue(c, null);
  130. object value = c.GetType().GetProperty("Value").GetValue(c, null);
  131. sw.WriteLine("{0}: {1}", key, value);
  132. }
  133. else
  134. sw.WriteLine(c.ToString());
  135. }
  136. }
  137. else if (result is Exception)
  138. {
  139. //No localization, must be parseable by script
  140. Exception e = (Exception)result;
  141. sw.WriteLine("Failed: {0}", e.Message);
  142. sw.WriteLine("Details: {0}", e.ToString());
  143. }
  144. else
  145. {
  146. sw.WriteLine(result.ToString());
  147. }
  148. }
  149. }
  150. 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 datafile)
  151. {
  152. try
  153. {
  154. System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(scriptpath);
  155. psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
  156. psi.CreateNoWindow = true;
  157. psi.UseShellExecute = false;
  158. psi.RedirectStandardOutput = true;
  159. psi.RedirectStandardError = true;
  160. foreach(KeyValuePair<string, string> kv in options)
  161. psi.EnvironmentVariables["DUPLICATI__" + kv.Key.Replace('-', '_')] = kv.Value;
  162. if (!options.ContainsKey("backup-name"))
  163. psi.EnvironmentVariables["DUPLICATI__backup_name"] = System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetEntryAssembly().Location);
  164. psi.EnvironmentVariables["DUPLICATI__EVENTNAME"] = eventname;
  165. psi.EnvironmentVariables["DUPLICATI__OPERATIONNAME"] = operationname;
  166. psi.EnvironmentVariables["DUPLICATI__REMOTEURL"] = remoteurl;
  167. if (localpath != null)
  168. psi.EnvironmentVariables["DUPLICATI__LOCALPATH"] = string.Join(System.IO.Path.PathSeparator.ToString(), localpath);
  169. string stderr = null;
  170. string stdout = null;
  171. if (!string.IsNullOrEmpty(datafile))
  172. psi.EnvironmentVariables["DUPLICATI__RESULTFILE"] = datafile;
  173. using(System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi))
  174. {
  175. ConsoleDataHandler cs = new ConsoleDataHandler(p);
  176. if (timeout <= 0)
  177. p.WaitForExit();
  178. else
  179. p.WaitForExit(timeout);
  180. if (requiredScript)
  181. {
  182. if (!p.HasExited)
  183. throw new Exception(string.Format(Strings.RunScript.ScriptTimeoutError, scriptpath));
  184. else if (p.ExitCode != 0)
  185. throw new Exception(string.Format(Strings.RunScript.InvalidExitCodeError, scriptpath, p.ExitCode));
  186. }
  187. if (p.HasExited)
  188. {
  189. stderr = cs.StandardError;
  190. stdout = cs.StandardOutput;
  191. if (p.ExitCode != 0)
  192. Logging.Log.WriteMessage(string.Format(Strings.RunScript.InvalidExitCodeError, scriptpath, p.ExitCode), Duplicati.Library.Logging.LogMessageType.Warning);
  193. }
  194. else
  195. {
  196. Logging.Log.WriteMessage(string.Format(Strings.RunScript.ScriptTimeoutError, scriptpath), Duplicati.Library.Logging.LogMessageType.Warning);
  197. }
  198. }
  199. if (!string.IsNullOrEmpty(stderr))
  200. Logging.Log.WriteMessage(string.Format(Strings.RunScript.StdErrorReport, scriptpath, stderr), Duplicati.Library.Logging.LogMessageType.Warning);
  201. //We only allow setting parameters on startup
  202. if (eventname == "BEFORE" && stdout != null)
  203. {
  204. foreach(string rawline in stdout.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
  205. {
  206. string line = rawline.Trim();
  207. if (!line.StartsWith("--"))
  208. continue; //Ingore anything that does not start with --
  209. line = line.Substring(2);
  210. int lix = line.IndexOf('=');
  211. if (lix == 0) //Skip --= as that makes no sense
  212. continue;
  213. string key;
  214. string value;
  215. if (lix < 0)
  216. {
  217. key = line.Trim();
  218. value = "";
  219. }
  220. else
  221. {
  222. key = line.Substring(0, lix).Trim();
  223. value = line.Substring(lix + 1).Trim();
  224. if (value.Length >= 2 && value.StartsWith("\"") && value.EndsWith("\""))
  225. value = value.Substring(1, value.Length - 2);
  226. }
  227. if (string.Equals(key, "remoteurl", StringComparison.InvariantCultureIgnoreCase))
  228. {
  229. remoteurl = value;
  230. }
  231. else if (string.Equals(key, "localpath", StringComparison.InvariantCultureIgnoreCase))
  232. {
  233. localpath = value.Split(System.IO.Path.PathSeparator);
  234. }
  235. else if (
  236. string.Equals(key, "eventname", StringComparison.InvariantCultureIgnoreCase) ||
  237. string.Equals(key, "operationname", StringComparison.InvariantCultureIgnoreCase) ||
  238. string.Equals(key, "main-action", StringComparison.InvariantCultureIgnoreCase) ||
  239. key == ""
  240. )
  241. {
  242. //Ignore
  243. }
  244. else
  245. options[key] = value;
  246. }
  247. }
  248. }
  249. catch (Exception ex)
  250. {
  251. Logging.Log.WriteMessage(string.Format(Strings.RunScript.ScriptExecuteError, scriptpath, ex.Message), Duplicati.Library.Logging.LogMessageType.Warning, ex);
  252. if (requiredScript)
  253. throw;
  254. }
  255. }
  256. private class ConsoleDataHandler
  257. {
  258. public ConsoleDataHandler(System.Diagnostics.Process p)
  259. {
  260. p.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(HandleOutputDataReceived);
  261. p.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(HandleErrorDataReceived);
  262. p.BeginErrorReadLine();
  263. p.BeginOutputReadLine();
  264. }
  265. private readonly StringBuilder m_standardOutput = new StringBuilder();
  266. private readonly StringBuilder m_standardError = new StringBuilder();
  267. private readonly object m_lock = new object();
  268. private void HandleOutputDataReceived (object sender, System.Diagnostics.DataReceivedEventArgs e)
  269. {
  270. lock(m_lock)
  271. m_standardOutput.AppendLine(e.Data);
  272. }
  273. private void HandleErrorDataReceived (object sender, System.Diagnostics.DataReceivedEventArgs e)
  274. {
  275. lock(m_lock)
  276. m_standardError.AppendLine(e.Data);
  277. }
  278. public string StandardOutput
  279. {
  280. get
  281. {
  282. lock(m_lock)
  283. return m_standardOutput.ToString().Trim();
  284. }
  285. }
  286. public string StandardError
  287. {
  288. get
  289. {
  290. lock(m_lock)
  291. return m_standardError.ToString().Trim();
  292. }
  293. }
  294. }
  295. }
  296. }