From 1f35824317e7b875f42f49e24dfdbac866efa49b Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Thu, 30 Jul 2015 14:31:27 +0200 Subject: [PATCH] Converted to REST based API, with a wrapper layer for control.cgi so the current UI still works. --- .../HttpServerConnection.cs | 111 +- Duplicati/Server/Duplicati.Server.csproj | 27 +- Duplicati/Server/Program.cs | 2 +- .../Server/WebServer/AuthenticationHandler.cs | 4 +- Duplicati/Server/WebServer/BodyWriter.cs | 27 +- Duplicati/Server/WebServer/ControlHandler.cs | 979 +++--------------- .../ControlMethods/AddOrUpdateBackup.cs | 167 --- .../WebServer/ControlMethods/DeleteBackup.cs | 122 --- .../WebServer/ControlMethods/GetBackup.cs | 53 - .../ControlMethods/GetFolderContents.cs | 116 --- .../ControlMethods/ListFolderAsNodes.cs | 86 -- .../WebServer/ControlMethods/ReadLogData.cs | 78 -- .../ControlMethods/SearchBackupFiles.cs | 65 -- Duplicati/Server/WebServer/RESTHandler.cs | 2 +- .../WebServer/RESTMethods/Acknowledgements.cs | 25 + .../Server/WebServer/RESTMethods/Backup.cs | 25 + .../WebServer/RESTMethods/BackupDefaults.cs | 27 + .../Server/WebServer/RESTMethods/Backups.cs | 8 +- .../Server/WebServer/RESTMethods/BugReport.cs | 25 + .../Server/WebServer/RESTMethods/Changelog.cs | 25 + .../WebServer/RESTMethods/Filesystem.cs | 25 + .../WebServer/RESTMethods/IRESTMethod.cs | 2 +- .../WebServer/RESTMethods/Localizations.cs | 25 + .../Server/WebServer/RESTMethods/LogData.cs | 25 + .../WebServer/RESTMethods/Notification.cs | 25 + .../WebServer/RESTMethods/Notifications.cs | 25 + .../WebServer/RESTMethods/ProgressState.cs | 2 +- .../WebServer/RESTMethods/RemoteOperation.cs | 25 + .../WebServer/RESTMethods/RequestInfo.cs | 4 +- .../WebServer/RESTMethods/ServerSetting.cs | 25 + .../WebServer/RESTMethods/ServerSettings.cs | 4 +- .../WebServer/RESTMethods/ServerState.cs | 2 +- .../Server/WebServer/RESTMethods/Tags.cs | 24 + .../Server/WebServer/RESTMethods/Task.cs | 24 + .../Server/WebServer/RESTMethods/Tasks.cs | 24 + .../WebServer/RESTMethods/UISettings.cs | 25 + .../Server/WebServer/RESTMethods/Updates.cs | 25 + .../Server/WebServer/RESTMethods/WebModule.cs | 28 + .../WebServer/RESTMethods/WebModules.cs | 28 + 39 files changed, 717 insertions(+), 1624 deletions(-) delete mode 100644 Duplicati/Server/WebServer/ControlMethods/AddOrUpdateBackup.cs delete mode 100644 Duplicati/Server/WebServer/ControlMethods/DeleteBackup.cs delete mode 100644 Duplicati/Server/WebServer/ControlMethods/GetBackup.cs delete mode 100644 Duplicati/Server/WebServer/ControlMethods/GetFolderContents.cs delete mode 100644 Duplicati/Server/WebServer/ControlMethods/ListFolderAsNodes.cs delete mode 100644 Duplicati/Server/WebServer/ControlMethods/ReadLogData.cs delete mode 100644 Duplicati/Server/WebServer/ControlMethods/SearchBackupFiles.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Acknowledgements.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Backup.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/BackupDefaults.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/BugReport.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Changelog.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Filesystem.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Localizations.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/LogData.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Notification.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Notifications.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/RemoteOperation.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/ServerSetting.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Tags.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Task.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Tasks.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/UISettings.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/Updates.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/WebModule.cs create mode 100644 Duplicati/Server/WebServer/RESTMethods/WebModules.cs diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs index 72d490164..9ff96d119 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs @@ -9,16 +9,32 @@ namespace Duplicati.GUI.TrayIcon { public class HttpServerConnection : IDisposable { - private const string CONTROL_SCRIPT = "control.cgi"; private const string LOGIN_SCRIPT = "login.cgi"; private const string STATUS_WINDOW = "index.html"; - private const string EDIT_WINDOW = "edit-window.html"; private const string XSRF_COOKIE = "xsrf-token"; private const string XSRF_HEADER = "X-XSRF-Token"; private const string AUTH_COOKIE = "session-auth"; - - private Uri m_controlUri; + + private class BackgroundRequest + { + public string Method; + public string Endpoint; + public Dictionary Query; + + public BackgroundRequest() + { + } + + public BackgroundRequest(string method, string endpoint, Dictionary query) + { + this.Method = method; + this.Endpoint = endpoint; + this.Query = query; + } + } + + private string m_apiUri; private string m_baseUri; private string m_password; private bool m_saltedpassword; @@ -46,22 +62,22 @@ namespace Duplicati.GUI.TrayIcon public IServerStatus Status { get { return m_status; } } private object m_lock = new object(); - private Queue> m_workQueue = new Queue>(); + private Queue m_workQueue = new Queue(); public HttpServerConnection(Uri server, string password, bool saltedpassword) { m_baseUri = server.ToString(); if (!m_baseUri.EndsWith("/")) m_baseUri += "/"; - + + m_apiUri = m_baseUri + "api/v1"; + m_firstNotificationTime = DateTime.Now; - m_controlUri = new Uri(m_baseUri + CONTROL_SCRIPT); m_password = password; m_saltedpassword = saltedpassword; m_updateRequest = new Dictionary(); - m_updateRequest["action"] = "get-current-state"; m_updateRequest["longpoll"] = "false"; m_updateRequest["lasteventid"] = "0"; @@ -85,7 +101,7 @@ namespace Duplicati.GUI.TrayIcon private void UpdateStatus() { - m_status = PerformRequest(m_updateRequest); + m_status = PerformRequest("GET", "/serverstate", m_updateRequest); m_updateRequest["lasteventid"] = m_status.LastEventID.ToString(); if (OnStatusUpdated != null) @@ -101,9 +117,7 @@ namespace Duplicati.GUI.TrayIcon private void UpdateNotifications() { var req = new Dictionary(); - req["action"] = "get-notifications"; - - var notifications = PerformRequest(req); + var notifications = PerformRequest("GET", "/notifications", req); if (notifications != null) { foreach(var n in notifications.Where(x => x.Timestamp > m_firstNotificationTime)) @@ -137,7 +151,7 @@ namespace Duplicati.GUI.TrayIcon { try { - Dictionary req; + BackgroundRequest req; bool any = false; do { @@ -153,7 +167,7 @@ namespace Duplicati.GUI.TrayIcon if (req != null) { any = true; - PerformRequest(req); + PerformRequest(req.Method, req.Endpoint, req.Query); } } while (req != null); @@ -267,7 +281,7 @@ namespace Duplicati.GUI.TrayIcon } - private T PerformRequest(Dictionary queryparams) + private T PerformRequest(string method, string urlfragment, Dictionary queryparams) { var hasTriedXSRF = false; var hasTriedPassword = false; @@ -276,7 +290,7 @@ namespace Duplicati.GUI.TrayIcon { try { - return PerformRequestInternal(queryparams); + return PerformRequestInternal(method, urlfragment, queryparams); } catch (System.Net.WebException wex) { @@ -321,17 +335,14 @@ namespace Duplicati.GUI.TrayIcon } } - private T PerformRequestInternal(Dictionary queryparams) + private T PerformRequestInternal(string method, string endpoint, Dictionary queryparams) { queryparams["format"] = "json"; string query = EncodeQueryString(queryparams); - byte[] data = ENCODING.GetBytes(query); - System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(m_controlUri); - req.Method = "POST"; - req.ContentLength = data.Length; - req.ContentType = "application/x-www-form-urlencoded ; charset=" + ENCODING.BodyName; + System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(new Uri(m_apiUri + endpoint + '?' + query)); + req.Method = method; req.Headers.Add("Accept-Charset", ENCODING.BodyName); if (m_xsrftoken != null) req.Headers.Add(XSRF_HEADER, m_xsrftoken); @@ -347,11 +358,8 @@ namespace Duplicati.GUI.TrayIcon //Wrap it all in async stuff Duplicati.Library.Utility.AsyncHttpRequest areq = new Library.Utility.AsyncHttpRequest(req); - using (System.IO.Stream s = areq.GetRequestStream()) - s.Write(data, 0, data.Length); - //Assign the timeout, and add a little processing time as well - if (queryparams["action"] == "get-current-state" && queryparams.ContainsKey("duration")) + if (endpoint.Equals("/serverstate", StringComparison.InvariantCultureIgnoreCase) && queryparams.ContainsKey("duration")) areq.Timeout = (int)(Duplicati.Library.Utility.Timeparser.ParseTimeSpan(queryparams["duration"]) + TimeSpan.FromSeconds(5)).TotalMilliseconds; using(System.Net.HttpWebResponse r = (System.Net.HttpWebResponse)areq.GetResponse()) @@ -372,67 +380,54 @@ namespace Duplicati.GUI.TrayIcon } - private void ExecuteAndNotify(Dictionary req) + private void ExecuteAndNotify(string method, string urifragment, Dictionary req) { lock (m_lock) { - m_workQueue.Enqueue(req); + m_workQueue.Enqueue(new BackgroundRequest(method, urifragment, req)); m_waitLock.Set(); } } public void Pause(string duration = null) { - Dictionary req = new Dictionary(); - req.Add("action", "send-command"); - req.Add("command", "pause"); + var req = new Dictionary(); if (!string.IsNullOrWhiteSpace(duration)) req.Add("duration", duration); - ExecuteAndNotify(req); + ExecuteAndNotify("POST", "/serverstate/pause", req); } public void Resume() { - Dictionary req = new Dictionary(); - req.Add("action", "send-command"); - req.Add("command", "resume"); - ExecuteAndNotify(req); + var req = new Dictionary(); + ExecuteAndNotify("POST", "/serverstate/resume", req); } - public void StopBackup() + public void StopTask(long id) { - Dictionary req = new Dictionary(); - req.Add("action", "send-command"); - req.Add("command", "stop"); - ExecuteAndNotify(req); + var req = new Dictionary(); + ExecuteAndNotify("POST", string.Format("/task/{0}/stop", Library.Utility.Uri.UrlPathEncode(id.ToString())), req); } - public void AbortBackup() + public void AbortTask(long id) { - Dictionary req = new Dictionary(); - req.Add("action", "send-command"); - req.Add("command", "abort"); - ExecuteAndNotify(req); + var req = new Dictionary(); + ExecuteAndNotify("POST", string.Format("/task/{0}/abort", Library.Utility.Uri.UrlPathEncode(id.ToString())), req); } public void RunBackup(long id, bool forcefull = false) { - Dictionary req = new Dictionary(); - req.Add("action", "send-command"); - req.Add("command", "run-backup"); - req.Add("id", id.ToString()); + var req = new Dictionary(); if (forcefull) req.Add("full", "true"); - ExecuteAndNotify(req); + ExecuteAndNotify("POST", string.Format("/backup/{0}/start", Library.Utility.Uri.UrlPathEncode(id.ToString())), req); } public void DismissNotification(long id) { - Dictionary req = new Dictionary(); - req.Add("action", "dismiss-notification"); - req.Add("id", id.ToString()); - ExecuteAndNotify(req); + var req = new Dictionary(); + ExecuteAndNotify("DELETE", string.Format("/notification/{0}", Library.Utility.Uri.UrlPathEncode(id.ToString())), req); } public void Dispose() @@ -456,11 +451,5 @@ namespace Duplicati.GUI.TrayIcon return m_baseUri + STATUS_WINDOW; } } - - public string EditWindowURL - { - get { return m_baseUri + EDIT_WINDOW; } - } - } } diff --git a/Duplicati/Server/Duplicati.Server.csproj b/Duplicati/Server/Duplicati.Server.csproj index 2d9207ae4..eb4301658 100644 --- a/Duplicati/Server/Duplicati.Server.csproj +++ b/Duplicati/Server/Duplicati.Server.csproj @@ -85,13 +85,6 @@ - - - - - - - @@ -108,6 +101,25 @@ + + + + + + + + + + + + + + + + + + + @@ -239,6 +251,7 @@ +