diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs index d6de11134..1aff62daf 100644 --- a/Duplicati/Server/Program.cs +++ b/Duplicati/Server/Program.cs @@ -57,6 +57,16 @@ namespace Duplicati.Server /// public static Duplicati.Library.Utility.WorkerThread WorkThread; + /// + /// List of completed task results + /// + public static List> TaskResultCache = new List>(); + + /// + /// The maximum number of completed task results to keep in memory + /// + private static int MAX_TASK_RESULT_CACHE_SIZE = 100; + /// /// The thread running the ping-pong handler /// @@ -445,6 +455,28 @@ namespace Duplicati.Server Program.Scheduler.NewSchedule += new EventHandler(SignalNewEvent); Program.WorkThread.OnError += (worker, task, exception) => { Program.DataConnection.LogError(task == null ? null : task.BackupID, "Error in worker", exception); }; + Action registerTaskResult = (id, ex) => { + lock(Program.MainLock) { + + // If the new results says it crashed, we store that instead of success + if (Program.TaskResultCache.Count > 0 && Program.TaskResultCache.Last().Key == id) + { + if (ex != null && Program.TaskResultCache.Last().Value == null) + Program.TaskResultCache.RemoveAt(Program.TaskResultCache.Count - 1); + else + return; + } + + Program.TaskResultCache.Add(new KeyValuePair(id, ex)); + while(Program.TaskResultCache.Count > MAX_TASK_RESULT_CACHE_SIZE) + Program.TaskResultCache.RemoveAt(0); + } + }; + + Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); }; + Program.WorkThread.OnError += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); }; + + Program.WebServer = new WebServer.Server(commandlineOptions); if (Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort) diff --git a/Duplicati/Server/WebServer/RESTHandler.cs b/Duplicati/Server/WebServer/RESTHandler.cs index 6b240bc31..ee5b6a952 100644 --- a/Duplicati/Server/WebServer/RESTHandler.cs +++ b/Duplicati/Server/WebServer/RESTHandler.cs @@ -172,7 +172,7 @@ namespace Duplicati.Server.WebServer module = module.Trim('/'); - var key = string.Join("/", request.Uri.Segments.Skip(API_URI_SEGMENTS + 1)).Trim('/'); + var key = string.Join("", request.Uri.Segments.Skip(API_URI_SEGMENTS + 1)).Trim('/'); var method = request.Method; if (!string.IsNullOrWhiteSpace(request.Headers["X-HTTP-Method-Override"])) diff --git a/Duplicati/Server/WebServer/RESTMethods/Backup.cs b/Duplicati/Server/WebServer/RESTMethods/Backup.cs index 7bf2e37b9..19da33d1f 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Backup.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Backup.cs @@ -297,7 +297,7 @@ namespace Duplicati.Server.WebServer.RESTMethods switch (operation) { case "files": - SearchFiles(bk, parts.Last().Split(new char[] { '/' }, 2).LastOrDefault(), info); + SearchFiles(bk, parts.Last().Split(new char[] { '/' }, 2).Skip(1).FirstOrDefault(), info); return; case "log": FetchLogData(bk, info); diff --git a/Duplicati/Server/WebServer/RESTMethods/Backups.cs b/Duplicati/Server/WebServer/RESTMethods/Backups.cs index 061cab7cb..d5fe6b3de 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Backups.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Backups.cs @@ -156,6 +156,9 @@ namespace Duplicati.Server.WebServer.RESTMethods using(var tf = new Duplicati.Library.Utility.TempFile()) data.Backup.DBPath = tf; + data.Backup.Filters = data.Backup.Filters ?? new Duplicati.Server.Serialization.Interface.IFilter[0]; + data.Backup.Settings = data.Backup.Settings ?? new Duplicati.Server.Serialization.Interface.ISetting[0]; + Program.DataConnection.RegisterTemporaryBackup(data.Backup); info.OutputOK(new { status = "OK", ID = data.Backup.ID }); diff --git a/Duplicati/Server/WebServer/RESTMethods/Task.cs b/Duplicati/Server/WebServer/RESTMethods/Task.cs index d3548e20c..07a7f15c6 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Task.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Task.cs @@ -14,11 +14,91 @@ // 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 System; +using System.Linq; +using System.Collections.Generic; namespace Duplicati.Server.WebServer.RESTMethods { - public class Task : IRESTMethodPOST { public void POST(string key, RequestInfo info) { var parts = (key ?? "").Split(new char[] { '/' }, 2); long taskid; if (parts.Length == 2 && long.TryParse(parts.First(), out taskid)) { var task = Program.WorkThread.CurrentTask; var tasks = Program.WorkThread.CurrentTasks; if (task != null) tasks.Insert(0, task); task = tasks.Where(x => x.TaskID == taskid).FirstOrDefault(); if (task == null) { info.ReportClientError("No such task", System.Net.HttpStatusCode.NotFound); return; } switch (parts.Last().ToLowerInvariant()) { case "abort": task.Abort(); info.OutputOK(); return; case "stop": task.Stop(); info.OutputOK(); return; } } info.ReportClientError("Invalid or missing task id"); } + public class Task : IRESTMethodGET, IRESTMethodPOST + { + public void GET(string key, RequestInfo info) + { + var parts = (key ?? "").Split(new char[] { '/' }, 2); + long taskid; + if (parts.Length == 2 && long.TryParse(parts.First(), out taskid)) + { + var task = Program.WorkThread.CurrentTask; + var tasks = Program.WorkThread.CurrentTasks; + + if (task != null && task.TaskID == taskid) + { + info.OutputOK(new { Status = "Running" }); + return; + } + + task = tasks.Where(x => x.TaskID == taskid).FirstOrDefault(); + if (tasks.Where(x => x.TaskID == taskid).FirstOrDefault() == null) + { + KeyValuePair[] matches; + lock(Program.MainLock) + matches = Program.TaskResultCache.Where(x => x.Key == taskid).ToArray(); + + if (matches.Length == 0) + info.ReportClientError("No such task found", System.Net.HttpStatusCode.NotFound); + else + info.OutputOK(new { + Status = matches[0].Value == null ? "Completed" : "Failed", + ErrorMessage = matches[0].Value == null ? null : matches[0].Value.Message, + Exception = matches[0].Value == null ? null : matches[0].Value.ToString() + }); + } + else + { + info.OutputOK(new { Status = "Waiting" }); + } + } + else + { + info.ReportClientError("Invalid request"); + } + } + + public void POST(string key, RequestInfo info) + { + var parts = (key ?? "").Split(new char[] { '/' }, 2); + long taskid; + if (parts.Length == 2 && long.TryParse(parts.First(), out taskid)) + { + var task = Program.WorkThread.CurrentTask; + var tasks = Program.WorkThread.CurrentTasks; + + if (task != null) + tasks.Insert(0, task); + + task = tasks.Where(x => x.TaskID == taskid).FirstOrDefault(); + if (task == null) + { + info.ReportClientError("No such task", System.Net.HttpStatusCode.NotFound); + return; + } + + switch (parts.Last().ToLowerInvariant()) + { + case "abort": + task.Abort(); + info.OutputOK(); + return; + + case "stop": + task.Stop(); + info.OutputOK(); + return; + } + } + + info.ReportClientError("Invalid or missing task id"); + } } }