// Copyright (C) 2014, 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.Linq; using HttpServer; using HttpServer.HttpModules; using System.Collections.Generic; using Duplicati.Server.Serialization; namespace Duplicati.Server.WebServer { internal partial class ControlHandler : HttpModule { private delegate void ProcessSub(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter writer); private readonly Dictionary SUPPORTED_METHODS; public ControlHandler() { SUPPORTED_METHODS = new Dictionary(System.StringComparer.InvariantCultureIgnoreCase); //Make a list of all supported actions SUPPORTED_METHODS.Add("supported-actions", ListSupportedActions); SUPPORTED_METHODS.Add("system-info", ListSystemInfo); SUPPORTED_METHODS.Add("list-backups", ListBackups); SUPPORTED_METHODS.Add("get-current-state", GetCurrentState); SUPPORTED_METHODS.Add("get-progress-state", GetProgressState); SUPPORTED_METHODS.Add("list-application-settings", ListApplicationSettings); SUPPORTED_METHODS.Add("list-options", ListCoreOptions); SUPPORTED_METHODS.Add("send-command", SendCommand); SUPPORTED_METHODS.Add("get-backup-defaults", GetBackupDefaults); SUPPORTED_METHODS.Add("get-folder-contents", GetFolderContents); SUPPORTED_METHODS.Add("get-backup", GetBackup); SUPPORTED_METHODS.Add("add-backup", AddBackup); SUPPORTED_METHODS.Add("update-backup", UpdateBackup); SUPPORTED_METHODS.Add("delete-backup", DeleteBackup); SUPPORTED_METHODS.Add("validate-path", ValidatePath); SUPPORTED_METHODS.Add("list-tags", ListTags); SUPPORTED_METHODS.Add("test-backend", TestBackend); SUPPORTED_METHODS.Add("create-remote-folder", CreateRemoteFolder); SUPPORTED_METHODS.Add("list-remote-folder", ListRemoteFolder); SUPPORTED_METHODS.Add("list-backup-sets", ListBackupSets); SUPPORTED_METHODS.Add("search-backup-files", SearchBackupFiles); SUPPORTED_METHODS.Add("restore-files", RestoreFiles); SUPPORTED_METHODS.Add("read-log", ReadLogData); SUPPORTED_METHODS.Add("get-license-data", GetLicenseData); SUPPORTED_METHODS.Add("get-changelog", GetChangelog); SUPPORTED_METHODS.Add("locate-uri-db", LocateUriDb); SUPPORTED_METHODS.Add("poll-log-messages", PollLogMessages); } public override bool Process (HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session) { //We use the fake entry point /control.cgi to listen for requests //This ensures that the rest of the webserver can just serve plain files if (!request.Uri.AbsolutePath.Equals("/control.cgi", StringComparison.InvariantCultureIgnoreCase)) return false; HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; string action = input["action"].Value ?? ""; //Lookup the actual handler method ProcessSub method; SUPPORTED_METHODS.TryGetValue(action, out method); if (method == null) { response.Status = System.Net.HttpStatusCode.NotImplemented; response.Reason = "Unsupported action: " + (action == null ? "" : ""); response.Send(); } else { //Default setup response.Status = System.Net.HttpStatusCode.OK; response.Reason = "OK"; #if DEBUG response.ContentType = "text/plain"; #else response.ContentType = "text/json"; #endif using (BodyWriter bw = new BodyWriter(response)) { try { method(request, response, session, bw); } catch (Exception ex) { try { if (!response.HeadersSent) { response.Status = System.Net.HttpStatusCode.InternalServerError; response.Reason = "Error"; response.ContentType = "text/plain"; bw.WriteJsonObject(new { Message = ex.Message, Type = ex.GetType().Name, #if DEBUG Stacktrace = ex.ToString() #endif }); bw.Flush(); } } catch (Exception flex) { Program.DataConnection.LogError("", "Handling outer ex", ex); Program.DataConnection.LogError("", "Gaver inner ex", flex); } } } } return true; } private void ReportError(HttpServer.IHttpResponse response, BodyWriter bw, string message) { response.Status = System.Net.HttpStatusCode.InternalServerError; response.Reason = message; bw.WriteJsonObject(new { Error = message }); } private List> DumpTable(System.Data.IDbCommand cmd, string tablename, string pagingfield, string offset_str, string pagesize_str) { var result = new List>(); long pagesize; if (!long.TryParse(pagesize_str, out pagesize)) pagesize = 100; pagesize = Math.Max(10, Math.Min(500, pagesize)); cmd.CommandText = "SELECT * FROM \"" + tablename + "\""; long offset = 0; if (!string.IsNullOrWhiteSpace(offset_str) && long.TryParse(offset_str, out offset) && !string.IsNullOrEmpty(pagingfield)) { var p = cmd.CreateParameter(); p.Value = offset; cmd.Parameters.Add(p); cmd.CommandText += " WHERE \"" + pagingfield + "\" < ?"; } if (!string.IsNullOrEmpty(pagingfield)) cmd.CommandText += " ORDER BY \"" + pagingfield + "\" DESC"; cmd.CommandText += " LIMIT " + pagesize.ToString(); using(var rd = cmd.ExecuteReader()) { var names = new List(); for(var i = 0; i < rd.FieldCount; i++) names.Add(rd.GetName(i)); while (rd.Read()) { var dict = new Dictionary(); for(int i = 0; i < names.Count; i++) dict[names[i]] = rd.GetValue(i); result.Add(dict); } } return result; } private void LocateUriDb(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; var uri = input["uri"].Value; if (string.IsNullOrWhiteSpace(uri)) { ReportError(response, bw, "Invalid request, missing uri"); } else { var path = Library.Main.DatabaseLocator.GetDatabasePath(uri, null, false, false); bw.SetOK(); bw.WriteJsonObject(new {Exists = !string.IsNullOrWhiteSpace(path)}); } } private void GetChangelog(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; var fromUpdate = input["from-update"].Value; if (string.IsNullOrWhiteSpace(fromUpdate)) { var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "changelog.txt"); bw.SetOK(); bw.WriteJsonObject(new { Status = "OK", Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), Changelog = System.IO.File.ReadAllText(path) }); } else { var updateInfo = Program.DataConnection.ApplicationSettings.UpdatedVersion; if (updateInfo == null) { ReportError(response, bw, "No update found"); } else { bw.SetOK(); bw.WriteJsonObject(new { Status = "OK", Version = updateInfo.Version, Changelog = updateInfo.ChangeInfo }); } } } private void GetLicenseData(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { bw.OutputOK(Duplicati.License.LicenseReader.ReadLicenses(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "licenses"))); } private void RestoreFiles(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; var bk = Program.DataConnection.GetBackup(input["id"].Value); if (bk == null) { ReportError(response, bw, "Invalid or missing backup id"); return; } var filters = input["paths"].Value.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries); var time = Duplicati.Library.Utility.Timeparser.ParseTimeInterval(input["time"].Value, DateTime.Now); var restoreTarget = input["restore-path"].Value; var overwrite = Duplicati.Library.Utility.Utility.ParseBool(input["overwrite"].Value, false); var task = Runner.CreateRestoreTask(bk, filters, time, restoreTarget, overwrite); Program.WorkThread.AddTask(task); bw.OutputOK(new { TaskID = task.TaskID }); } private void ListBackupSets(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; var bk = Program.DataConnection.GetBackup(input["id"].Value); if (bk == null) { ReportError(response, bw, "Invalid or missing backup id"); return; } var r = Runner.Run(Runner.CreateTask(DuplicatiOperation.List, bk), false) as Duplicati.Library.Interface.IListResults; bw.OutputOK(r.Filesets); } private void ListRemoteFolder(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; if (input["url"] == null || input["url"].Value == null) { ReportError(response, bw, "The url parameter was not set"); return; } try { using(var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(input["url"].Value, new Dictionary())) bw.OutputOK(new { Status = "OK", Folders = b.List() }); } catch (Exception ex) { ReportError(response, bw, ex.Message); } } private void CreateRemoteFolder(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; if (input["url"] == null || input["url"].Value == null) { ReportError(response, bw, "The url parameter was not set"); return; } try { using(var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(input["url"].Value, new Dictionary())) b.CreateFolder(); bw.OutputOK(); } catch (Exception ex) { ReportError(response, bw, ex.Message); } } private void TestBackend(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; if (input["url"] == null || input["url"].Value == null) { ReportError(response, bw, "The url parameter was not set"); return; } var modules = (from n in Library.DynamicLoader.GenericLoader.Modules where n is Library.Interface.IConnectionModule select n).ToArray(); try { var url = input["url"].Value; var uri = new Library.Utility.Uri(url); var qp = uri.QueryParameters; var opts = new Dictionary(); foreach(var k in qp.Keys.Cast()) opts[k] = qp[k]; foreach(var n in modules) n.Configure(opts); using(var b = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(url, new Dictionary())) b.Test(); bw.OutputOK(); } catch (Duplicati.Library.Interface.FolderMissingException) { ReportError(response, bw, "missing-folder"); } catch (Duplicati.Library.Utility.SslCertificateValidator.InvalidCertificateException icex) { if (string.IsNullOrWhiteSpace(icex.Certificate)) ReportError(response, bw, icex.Message); else ReportError(response, bw, "incorrect-cert:" + icex.Certificate); } catch (Exception ex) { ReportError(response, bw, ex.Message); } finally { foreach(var n in modules) if (n is IDisposable) ((IDisposable)n).Dispose(); } } private void ListSystemInfo(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { bw.OutputOK(new { APIVersion = 1, PasswordPlaceholder = Duplicati.Server.WebServer.Server.PASSWORD_PLACEHOLDER, ServerVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), ServerVersionName = Duplicati.License.VersionNumbers.Version, ServerTime = DateTime.Now, OSType = Library.Utility.Utility.IsClientLinux ? (Library.Utility.Utility.IsClientOSX ? "OSX" : "Linux") : "Windows", DirectorySeparator = System.IO.Path.DirectorySeparatorChar, PathSeparator = System.IO.Path.PathSeparator, CaseSensitiveFilesystem = Duplicati.Library.Utility.Utility.IsFSCaseSensitive, MonoVersion = Duplicati.Library.Utility.Utility.IsMono ? Duplicati.Library.Utility.Utility.MonoVersion.ToString() : null, MachineName = System.Environment.MachineName, NewLine = System.Environment.NewLine, CLRVersion = System.Environment.Version.ToString(), CLROSInfo = new { Platform = System.Environment.OSVersion.Platform.ToString(), ServicePack = System.Environment.OSVersion.ServicePack, Version = System.Environment.OSVersion.Version.ToString(), VersionString = System.Environment.OSVersion.VersionString }, Options = Serializable.ServerSettings.Options, CompressionModules = Serializable.ServerSettings.CompressionModules, EncryptionModules = Serializable.ServerSettings.EncryptionModules, BackendModules = Serializable.ServerSettings.BackendModules, GenericModules = Serializable.ServerSettings.GenericModules, WebModules = Serializable.ServerSettings.WebModules, ConnectionModules = Serializable.ServerSettings.ConnectionModules, UsingAlternateUpdateURLs = Duplicati.Library.AutoUpdater.AutoUpdateSettings.UsesAlternateURLs, LogLevels = Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType)) }); } private void ListSupportedActions(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { bw.OutputOK(new { Version = 1, Methods = SUPPORTED_METHODS.Keys }); } private void ListBackups (HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { var schedules = Program.DataConnection.Schedules; var backups = Program.DataConnection.Backups; var all = from n in backups select new AddOrUpdateBackupData() { Backup = (Database.Backup)n, Schedule = (from x in schedules where x.Tags != null && x.Tags.Contains("ID=" + n.ID) select (Database.Schedule)x).FirstOrDefault() }; bw.OutputOK(all.ToArray()); } private void ListTags(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { var r = from n in Serializable.ServerSettings.CompressionModules .Union(Serializable.ServerSettings.EncryptionModules) .Union(Serializable.ServerSettings.BackendModules) .Union(Serializable.ServerSettings.GenericModules) select n.Key.ToLower(); // Append all known tags r = r.Union(from n in Program.DataConnection.Backups select n.Tags into p from x in p select x.ToLower()); bw.OutputOK(r); } private void ValidatePath(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; if (input["path"] == null || input["path"].Value == null) { ReportError(response, bw, "The path parameter was not set"); return; } try { string path = SpecialFolders.ExpandEnvironmentVariables(input["path"].Value); if (System.IO.Path.IsPathRooted(path) && System.IO.Directory.Exists(path)) { bw.OutputOK(); return; } } catch { } ReportError(response, bw, "File or folder not found"); return; } private bool LongPollCheck(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, BodyWriter bw, EventPollNotify poller, ref long id, out bool isError) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; if (Library.Utility.Utility.ParseBool(input["longpoll"].Value, false)) { long lastEventId; if (!long.TryParse(input["lasteventid"].Value, out lastEventId)) { ReportError(response, bw, "When activating long poll, the request must include the last event id"); isError = true; return false; } TimeSpan ts; try { ts = Library.Utility.Timeparser.ParseTimeSpan(input["duration"].Value); } catch (Exception ex) { ReportError(response, bw, "Invalid duration: " + ex.Message); isError = true; return false; } if (ts <= TimeSpan.FromSeconds(10) || ts.TotalMilliseconds > int.MaxValue) { ReportError(response, bw, "Invalid duration, must be at least 10 seconds, and less than " + int.MaxValue + " milliseconds"); isError = true; return false; } isError = false; id = poller.Wait(lastEventId, (int)ts.TotalMilliseconds); return true; } isError = false; return false; } private void GetProgressState(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { if (Program.GenerateProgressState == null) { ReportError(response, bw, "No active backup"); } else { var ev = Program.GenerateProgressState(); bw.OutputOK(ev); } } private void GetCurrentState (HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { bool isError; long id = 0; if (LongPollCheck(request, response, bw, Program.StatusEventNotifyer, ref id, out isError)) { //Make sure we do not report a higher number than the eventnotifyer says var st = new Serializable.ServerStatus(); st.LastEventID = id; bw.OutputOK(st); } else if (!isError) { bw.OutputOK(new Serializable.ServerStatus()); } } private void ListCoreOptions(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { bw.OutputOK(new Duplicati.Library.Main.Options(new Dictionary()).SupportedCommands); } private void ListApplicationSettings(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { bw.OutputOK(Program.DataConnection.ApplicationSettings); } private static void MergeJsonObjects(Newtonsoft.Json.Linq.JObject self, Newtonsoft.Json.Linq.JObject other) { foreach(var p in other.Properties()) { var sp = self.Property(p.Name); if (sp == null) self.Add(p); else { switch (p.Type) { // Primitives override case Newtonsoft.Json.Linq.JTokenType.Boolean: case Newtonsoft.Json.Linq.JTokenType.Bytes: case Newtonsoft.Json.Linq.JTokenType.Comment: case Newtonsoft.Json.Linq.JTokenType.Constructor: case Newtonsoft.Json.Linq.JTokenType.Date: case Newtonsoft.Json.Linq.JTokenType.Float: case Newtonsoft.Json.Linq.JTokenType.Guid: case Newtonsoft.Json.Linq.JTokenType.Integer: case Newtonsoft.Json.Linq.JTokenType.String: case Newtonsoft.Json.Linq.JTokenType.TimeSpan: case Newtonsoft.Json.Linq.JTokenType.Uri: case Newtonsoft.Json.Linq.JTokenType.None: case Newtonsoft.Json.Linq.JTokenType.Null: case Newtonsoft.Json.Linq.JTokenType.Undefined: self.Replace(p); break; // Arrays merge case Newtonsoft.Json.Linq.JTokenType.Array: if (sp.Type == Newtonsoft.Json.Linq.JTokenType.Array) sp.Value = new Newtonsoft.Json.Linq.JArray(((Newtonsoft.Json.Linq.JArray)sp.Value).Union((Newtonsoft.Json.Linq.JArray)p.Value)); else { var a = new Newtonsoft.Json.Linq.JArray(sp.Value); sp.Value = new Newtonsoft.Json.Linq.JArray(a.Union((Newtonsoft.Json.Linq.JArray)p.Value)); } break; // Objects merge case Newtonsoft.Json.Linq.JTokenType.Object: if (sp.Type == Newtonsoft.Json.Linq.JTokenType.Object) MergeJsonObjects((Newtonsoft.Json.Linq.JObject)sp.Value, (Newtonsoft.Json.Linq.JObject)p.Value); else sp.Value = p.Value; break; // Ignore other stuff default: break; } } } } private void GetBackupDefaults(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { // Start with a scratch object var o = new Newtonsoft.Json.Linq.JObject(); // Add application wide settings o.Add("ApplicationOptions", new Newtonsoft.Json.Linq.JArray(Program.DataConnection.Settings)); try { // Add built-in defaults Newtonsoft.Json.Linq.JObject n; using(var s = new System.IO.StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + ".newbackup.json"))) n = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(s.ReadToEnd()); MergeJsonObjects(o, n); } catch { } try { // Add install defaults/overrides, if present var path = System.IO.Path.Combine(Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir, "newbackup.json"); if (System.IO.File.Exists(path)) { Newtonsoft.Json.Linq.JObject n; n = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(System.IO.File.ReadAllText(path)); MergeJsonObjects(o, n); } } catch { } bw.OutputOK(new { success = true, data = o }); } private void PollLogMessages(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; var level_str = input["level"].Value ?? ""; var id_str = input["id"].Value ?? ""; Library.Logging.LogMessageType level; long id; long.TryParse(id_str, out id); Enum.TryParse(level_str, true, out level); bw.OutputOK(Program.LogHandler.AfterID(id, level)); } private void SendCommand(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session, BodyWriter bw) { HttpServer.HttpInput input = request.Method.ToUpper() == "POST" ? request.Form : request.QueryString; string command = input["command"].Value ?? ""; switch (command.ToLowerInvariant()) { case "check-update": Program.UpdatePoller.CheckNow(); bw.OutputOK(); return; case "install-update": Program.UpdatePoller.InstallUpdate(); bw.OutputOK(); return; case "activate-update": if (Program.WorkThread.CurrentTask != null || Program.WorkThread.CurrentTasks.Count != 0) { ReportError(response, bw, "Cannot activate update while task is running or scheduled"); } else { Program.UpdatePoller.ActivateUpdate(); bw.OutputOK(); } return; case "postpone-update": Program.DataConnection.ApplicationSettings.SuppressUpdateUntil = DateTime.UtcNow.AddDays(2); bw.OutputOK(); return; case "pause": if (input.Contains("duration") && !string.IsNullOrWhiteSpace(input["duration"].Value)) { TimeSpan ts; try { ts = Library.Utility.Timeparser.ParseTimeSpan(input["duration"].Value); } catch (Exception ex) { ReportError(response, bw, ex.Message); return; } if (ts.TotalMilliseconds > 0) Program.LiveControl.Pause(ts); else Program.LiveControl.Pause(); } else { Program.LiveControl.Pause(); } bw.OutputOK(); return; case "resume": Program.LiveControl.Resume(); bw.OutputOK(); return; case "stop": case "abort": { var task = Program.WorkThread.CurrentTask; var tasks = Program.WorkThread.CurrentTasks; long taskid; if (!input.Contains("taskid") || !long.TryParse(input["taskid"].Value ?? "", out taskid)) { ReportError(response, bw, "Invalid or missing taskid"); return; } if (task != null) tasks.Insert(0, task); task = tasks.Where(x => x.TaskID == taskid).FirstOrDefault(); if (task == null) { ReportError(response, bw, "No such task"); return; } if (string.Equals(command, "abort", StringComparison.InvariantCultureIgnoreCase)) task.Abort(); else task.Stop(); bw.OutputOK(); return; } case "is-backup-active": { var backup = Program.DataConnection.GetBackup(input["id"].Value); if (backup == null) { ReportError(response, bw, string.Format("No backup found for id: {0}", input["id"].Value)); return; } var t = Program.WorkThread.CurrentTask; var bt = t == null ? null : t.Backup; if (bt != null && backup.ID == bt.ID) { bw.OutputOK(new { Status = "OK", Active = true }); return; } else if (Program.WorkThread.CurrentTasks.Where(x => { var bn = x == null ? null : x.Backup; return bn == null || bn.ID == backup.ID; }).Any()) { bw.OutputOK(new { Status = "OK", Active = true }); return; } else { bw.OutputOK(new { Status = "OK", Active = false }); return; } } case "run": case "run-backup": { var backup = Program.DataConnection.GetBackup(input["id"].Value); if (backup == null) { ReportError(response, bw, string.Format("No backup found for id: {0}", input["id"].Value)); return; } var t = Program.WorkThread.CurrentTask; var bt = t == null ? null : t.Backup; if (bt != null && backup.ID == bt.ID) { // Already running } else if (Program.WorkThread.CurrentTasks.Where(x => { var bn = x == null ? null : x.Backup; return bn == null || bn.ID == backup.ID; }).Any()) { // Already in queue } else { Program.WorkThread.AddTask(Runner.CreateTask(DuplicatiOperation.Backup, backup)); Program.StatusEventNotifyer.SignalNewEvent(); } } bw.OutputOK(); return; case "run-verify": { var backup = Program.DataConnection.GetBackup(input["id"].Value); if (backup == null) { ReportError(response, bw, string.Format("No backup found for id: {0}", input["id"].Value)); return; } Program.WorkThread.AddTask(Runner.CreateTask(DuplicatiOperation.Verify, backup)); Program.StatusEventNotifyer.SignalNewEvent(); } bw.OutputOK(); return; case "run-repair": { var backup = Program.DataConnection.GetBackup(input["id"].Value); if (backup == null) { ReportError(response, bw, string.Format("No backup found for id: {0}", input["id"].Value)); return; } Program.WorkThread.AddTask(Runner.CreateTask(DuplicatiOperation.Repair, backup)); Program.StatusEventNotifyer.SignalNewEvent(); } bw.OutputOK(); return; case "create-report": { var backup = Program.DataConnection.GetBackup(input["id"].Value); if (backup == null) { ReportError(response, bw, string.Format("No backup found for id: {0}", input["id"].Value)); return; } Program.WorkThread.AddTask(Runner.CreateTask(DuplicatiOperation.CreateReport, backup)); Program.StatusEventNotifyer.SignalNewEvent(); } bw.OutputOK(); return; case "clear-warning": Program.DataConnection.ApplicationSettings.UnackedWarning = false; Program.StatusEventNotifyer.SignalNewEvent(); bw.OutputOK(); return; case "clear-error": Program.DataConnection.ApplicationSettings.UnackedError = false; Program.StatusEventNotifyer.SignalNewEvent(); bw.OutputOK(); return; default: var m = Duplicati.Library.DynamicLoader.WebLoader.Modules.Where(x => x.Key.Equals(command, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (m == null) { ReportError(response, bw, string.Format("Unsupported command {0}", command)); return; } bw.OutputOK(new { Status = "OK", Result = m.Execute(input.Where(x => !x.Name.Equals("command", StringComparison.InvariantCultureIgnoreCase) && !x.Name.Equals("action", StringComparison.InvariantCultureIgnoreCase) ).ToDictionary(x => x.Name, x => x.Value)) }); return; } } } }