diff --git a/.travis.yml b/.travis.yml
index d6580239b..8a18829db 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -55,6 +55,7 @@ jobs:
- env: CATEGORY=Purge
- env: CATEGORY=Serialization
- env: CATEGORY=Utility
+ - env: CATEGORY=UriUtility
- env: CATEGORY=GUI
addons:
diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs
index 4b4788e81..36c5b0a36 100644
--- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs
+++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs
@@ -134,7 +134,7 @@ namespace Duplicati.GUI.TrayIcon
public virtual IBrowserWindow ShowUrlInWindow(string url)
{
//Fallback is to just show the window in a browser
- Duplicati.Library.Utility.UrlUtillity.OpenURL(url, Program.BrowserCommand);
+ Duplicati.Library.Utility.UrlUtility.OpenURL(url, Program.BrowserCommand);
return null;
}
diff --git a/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj b/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj
index 817a57af5..070b4832a 100644
--- a/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj
+++ b/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj
@@ -41,6 +41,7 @@
+
diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs
index b426eab5b..b0d336d03 100644
--- a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs
+++ b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs
@@ -18,7 +18,6 @@ using System;
using Duplicati.Library.Interface;
using System.Collections.Generic;
using System.Net;
-using System.Web;
using Duplicati.Library.Utility;
using Newtonsoft.Json;
using System.Text;
@@ -97,9 +96,9 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
m_oauth = new OAuthHelper(authid, this.ProtocolKey);
m_oauth.AutoAuthHeader = true;
- }
-
-
+ }
+
+
private class ListBucketResponse
{
public string kind { get; set; }
@@ -205,7 +204,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
if (string.IsNullOrEmpty(m_project))
throw new UserInformationException(Strings.GoogleCloudStorage.ProjectIDMissingError(PROJECT_OPTION), "GoogleCloudStorageMissingProjectID");
- var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new CreateBucketRequest() {
+ var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new CreateBucketRequest
+ {
name = m_bucket,
location = m_location,
storageClass = m_storage_class
@@ -220,7 +220,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
var areq = new AsyncHttpRequest(req);
- using(var rs = areq.GetRequestStream())
+ using (var rs = areq.GetRequestStream())
rs.Write(data, 0, data.Length);
m_oauth.ReadJSONResponse(areq);
@@ -246,8 +246,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
foreach (KeyValuePair s in KNOWN_GCS_LOCATIONS)
locations.AppendLine(string.Format("{0}: {1}", s.Key, s.Value));
foreach (KeyValuePair s in KNOWN_GCS_STORAGE_CLASSES)
- storageClasses.AppendLine(string.Format("{0}: {1}", s.Key, s.Value));
-
+ storageClasses.AppendLine(string.Format("{0}: {1}", s.Key, s.Value));
+
return new List(new ICommandLineArgument[] {
new CommandLineArgument(LOCATION_OPTION, CommandLineArgument.ArgumentType.String, Strings.GoogleCloudStorage.LocationDescriptionShort, Strings.GoogleCloudStorage.LocationDescriptionLong(locations.ToString())),
new CommandLineArgument(STORAGECLASS_OPTION, CommandLineArgument.ArgumentType.String, Strings.GoogleCloudStorage.StorageclassDescriptionShort, Strings.GoogleCloudStorage.StorageclassDescriptionLong(locations.ToString())),
@@ -276,8 +276,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
var res = GoogleCommon.ChunckedUploadWithResume(m_oauth, item, url, stream);
if (res == null)
- throw new Exception(string.Format("Upload succeeded, but no data was returned"));
-
+ throw new Exception("Upload succeeded, but no data was returned");
+
}
public void Get(string remotename, System.IO.Stream stream)
@@ -288,8 +288,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
var req = m_oauth.CreateRequest(url);
var areq = new AsyncHttpRequest(req);
- using(var resp = areq.GetResponse())
- using(var rs = areq.GetResponseStream())
+ using (var resp = areq.GetResponse())
+ using (var rs = areq.GetResponseStream())
Library.Utility.Utility.CopyStream(rs, stream);
}
catch (WebException wex)
@@ -304,7 +304,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
public void Rename(string oldname, string newname)
{
- var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new BucketResourceItem() {
+ var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new BucketResourceItem
+ {
name = m_prefix + newname,
}));
@@ -315,7 +316,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
req.ContentType = "application/json; charset=UTF-8";
var areq = new AsyncHttpRequest(req);
- using(var rs = areq.GetRequestStream())
+ using (var rs = areq.GetRequestStream())
rs.Write(data, 0, data.Length);
m_oauth.ReadJSONResponse(req);
@@ -323,8 +324,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
#region IDisposable implementation
public void Dispose()
- {
-
+ {
+
}
#endregion
}
diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs
index fe846c529..fef6f15f9 100644
--- a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs
+++ b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs
@@ -15,14 +15,18 @@
// 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.Collections.Generic;
+using System.Collections.Specialized;
using System.Linq;
+using System.Net;
+
+using Newtonsoft.Json;
+
+using Duplicati.Library.Backend.GoogleServices;
using Duplicati.Library.Interface;
using Duplicati.Library.Utility;
-using System.Collections.Generic;
-using System.Net;
-using Newtonsoft.Json;
-using Duplicati.Library.Backend.GoogleServices;
-
+using System.Text;
+
namespace Duplicati.Library.Backend.GoogleDrive
{
public class GoogleDrive : IBackend, IStreamingBackend, IQuotaEnabledBackend, IRenameEnabledBackend
@@ -31,13 +35,11 @@ namespace Duplicati.Library.Backend.GoogleDrive
private const string DISABLE_TEAMDRIVE_OPTION = "googledrive-disable-teamdrive";
private const string FOLDER_MIMETYPE = "application/vnd.google-apps.folder";
- private const string DRIVE_API_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v2";
- private const string DRIVE_API_URL = "https://www.googleapis.com/drive/v2";
+
+ private readonly string m_path;
+ private readonly bool m_useTeamDrive = true;
- private string m_path;
- private bool m_useTeamDrive = true;
-
- private OAuthHelper m_oauth;
+ private readonly OAuthHelper m_oauth;
private string m_currentFolderId;
private Dictionary m_filecache;
@@ -66,26 +68,30 @@ namespace Duplicati.Library.Backend.GoogleDrive
private string GetFolderId(string path, bool autocreate = false)
{
- var curparent = GetAboutInfo().rootFolderId;
- var curdisplay = "/";
+ var curparent = GetAboutInfo().rootFolderId;
+ var curdisplay = new StringBuilder("/");
- foreach(var p in path.Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries))
+ foreach (var p in path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries))
{
- var res = ListFolder(curparent, true, p).ToArray();
-
- if (res.Length == 0)
- {
- if (!autocreate)
- throw new FolderMissingException();
-
- curparent = CreateFolder(p, curparent).id;
- }
- else if (res.Length > 1)
- throw new UserInformationException(Strings.GoogleDrive.MultipleEntries(p, curdisplay), "GoogleDriveMultipleEntries");
- else
- curparent = res[0].id;
-
- curdisplay += p + "/";
+ var res = ListFolder(curparent, true, p).ToArray();
+
+ if (res.Length == 0)
+ {
+ if (!autocreate)
+ throw new FolderMissingException();
+
+ curparent = CreateFolder(p, curparent).id;
+ }
+ else if (res.Length > 1)
+ {
+ throw new UserInformationException(Strings.GoogleDrive.MultipleEntries(p, curdisplay.ToString()), "GoogleDriveMultipleEntries");
+ }
+ else
+ {
+ curparent = res[0].id;
+ }
+
+ curdisplay.Append(p).Append("/");
}
return curparent;
@@ -110,17 +116,17 @@ namespace Duplicati.Library.Backend.GoogleDrive
if (entries != null)
return entries;
- var list = ListFolder(CurrentFolderId, false, remotename).ToArray();
+ entries = ListFolder(CurrentFolderId, false, remotename).ToArray();
- if (list == null || list.Length == 0)
+ if (entries == null || entries.Length == 0)
{
if (throwMissingException)
throw new FileMissingException();
else
return null;
}
-
- return m_filecache[remotename] = list;
+
+ return m_filecache[remotename] = entries;
}
private static string EscapeTitleEntries(string title)
@@ -141,27 +147,32 @@ namespace Duplicati.Library.Backend.GoogleDrive
GoogleDriveFolderItem[] files;
m_filecache.TryGetValue(remotename, out files);
- string fileid = null;
+ string fileId = null;
if (files != null)
{
if (files.Length == 1)
- fileid = files[0].id;
+ fileId = files[0].id;
else
Delete(remotename);
}
- var isUpdate = !string.IsNullOrWhiteSpace(fileid);
+ var isUpdate = !string.IsNullOrWhiteSpace(fileId);
+
+ var values = new NameValueCollection {
+ { WebApi.GoogleDrive.QueryParam.UploadType,
+ WebApi.GoogleDrive.QueryValue.Resumable } };
var url = isUpdate ?
- string.Format("{0}/files/{1}?uploadType=resumable", DRIVE_API_UPLOAD_URL, Library.Utility.Uri.UrlPathEncode(fileid)) :
- string.Format("{0}/files?uploadType=resumable", DRIVE_API_UPLOAD_URL);
+ WebApi.GoogleDrive.FileUploadUrl(Library.Utility.Uri.UrlPathEncode(fileId), values) :
+ WebApi.GoogleDrive.FileUploadUrl(values);
- var item = new GoogleDriveFolderItem() {
+ var item = new GoogleDriveFolderItem
+ {
title = remotename,
description = remotename,
mimeType = "application/octet-stream",
labels = new GoogleDriveFolderItemLabels { hidden = true },
- parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference() { id = CurrentFolderId } }
+ parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference { id = CurrentFolderId } }
};
var res = GoogleCommon.ChunckedUploadWithResume(m_oauth, item, url, stream, isUpdate ? "PUT" : "POST");
@@ -172,7 +183,6 @@ namespace Duplicati.Library.Backend.GoogleDrive
m_filecache.Clear();
throw;
}
-
}
public void Get(string remotename, System.IO.Stream stream)
@@ -181,12 +191,15 @@ namespace Duplicati.Library.Backend.GoogleDrive
if (m_filecache.Count == 0)
foreach (var file in List()) { /* Enumerate the full listing */ }
- var fileid = GetFileEntries(remotename).OrderByDescending(x => x.createdDate).First().id;
+ var fileId = GetFileEntries(remotename).OrderByDescending(x => x.createdDate).First().id;
- var req = m_oauth.CreateRequest(string.Format("{0}/files/{1}?alt=media{2}", DRIVE_API_URL, fileid, m_useTeamDrive ? "&supportsTeamDrives=true" : string.Empty));
+ var url = WebApi.GoogleDrive.FileQueryUrl(fileId, new NameValueCollection{
+ { WebApi.GoogleDrive.QueryParam.Alt, WebApi.GoogleDrive.QueryValue.Media }
+ });
+ var req = m_oauth.CreateRequest(url);
var areq = new AsyncHttpRequest(req);
- using(var resp = (HttpWebResponse)areq.GetResponse())
- using(var rs = areq.GetResponseStream())
+ using (var resp = (HttpWebResponse)areq.GetResponse())
+ using (var rs = areq.GetResponseStream())
Duplicati.Library.Utility.Utility.CopyStream(rs, stream);
}
@@ -247,7 +260,7 @@ namespace Duplicati.Library.Backend.GoogleDrive
}
}
}
-
+
public void Put(string remotename, string filename)
{
using (System.IO.FileStream fs = System.IO.File.OpenRead(filename))
@@ -264,11 +277,11 @@ namespace Duplicati.Library.Backend.GoogleDrive
{
try
{
- foreach(var fileid in from n in GetFileEntries(remotename) select n.id)
+ foreach (var fileid in from n in GetFileEntries(remotename) select n.id)
{
- var url = string.Format("{0}/files/{1}{2}", DRIVE_API_URL, Library.Utility.Uri.UrlPathEncode(fileid), m_useTeamDrive ? "?supportsTeamDrives=true" : string.Empty);
-
- m_oauth.GetJSONData
@@ -173,4 +174,4 @@
-
\ No newline at end of file
+
diff --git a/Duplicati/UnitTest/UriUtilityTests.cs b/Duplicati/UnitTest/UriUtilityTests.cs
new file mode 100644
index 000000000..84d43c8a3
--- /dev/null
+++ b/Duplicati/UnitTest/UriUtilityTests.cs
@@ -0,0 +1,69 @@
+// Copyright (C) 2018, The Duplicati Team
+// http://www.duplicati.com, info@duplicati.com
+//
+// 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 NUnit.Framework;
+using System.Collections.Specialized;
+
+namespace Duplicati.UnitTest
+{
+ public class UriUtilityTests
+ {
+ [Test]
+ [Category("UriUtility")]
+ public static void TestBuildUriQuery()
+ {
+ var query = new NameValueCollection { { "a", "b" } };
+ var queryUrl = Library.Utility.Uri.BuildUriQuery(query);
+ Assert.AreEqual("a=b", queryUrl);
+ query.Add(new NameValueCollection { { "c", "d" } });
+ queryUrl = Library.Utility.Uri.BuildUriQuery(query);
+ Assert.AreEqual("a=b&c=d", queryUrl);
+ }
+
+ [Test]
+ [Category("UriUtility")]
+ public static void TestUrlBuilder()
+ {
+ var baseUrl = "http://localhost";
+ var path = "files";
+ var query = new NameValueCollection { { "a", "b" }, { "c", "d" } };
+ var url = Library.Utility.Uri.UriBuilder(baseUrl, path, query);
+ Assert.AreEqual(baseUrl + "/" + path + "?a=b&c=d", url);
+ }
+
+ [Test]
+ [Category("UriUtility")]
+ public static void TestExtractPath()
+ {
+ var url = "http://localhost/a/b";
+ var path = Library.Utility.Uri.ExtractPath(url);
+ Assert.AreEqual("a/b", path);
+ }
+
+
+ [Test]
+ [Category("UriUtility")]
+ public static void TestConcatPaths()
+ {
+ var path1 = "/a";
+ var path2 = "b/";
+ Assert.AreEqual("/a/b/", Library.Utility.Uri.ConcatPaths(path1, path2));
+ Assert.AreEqual("/a", Library.Utility.Uri.ConcatPaths(path1, null));
+ Assert.AreEqual("/b/", Library.Utility.Uri.ConcatPaths(string.Empty, path2));
+ }
+ }
+}