Merge branch 'master' into refactored
This commit is contained in:
@@ -55,6 +55,7 @@ jobs:
|
||||
- env: CATEGORY=Purge
|
||||
- env: CATEGORY=Serialization
|
||||
- env: CATEGORY=Utility
|
||||
- env: CATEGORY=UriUtility
|
||||
|
||||
- env: CATEGORY=GUI
|
||||
addons:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+1
@@ -41,6 +41,7 @@
|
||||
<Compile Include="GoogleCommon.cs" />
|
||||
<Compile Include="GoogleDrive.cs" />
|
||||
<Compile Include="GCSConfig.cs" />
|
||||
<Compile Include="WebApi.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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<BucketResourceItem>(areq);
|
||||
@@ -246,8 +246,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
|
||||
foreach (KeyValuePair<string, string> s in KNOWN_GCS_LOCATIONS)
|
||||
locations.AppendLine(string.Format("{0}: {1}", s.Key, s.Value));
|
||||
foreach (KeyValuePair<string, string> 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<ICommandLineArgument>(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<BucketResourceItem, BucketResourceItem>(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<BucketResourceItem>(req);
|
||||
@@ -323,8 +324,8 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage
|
||||
|
||||
#region IDisposable implementation
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
{
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -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<string, GoogleDriveFolderItem[]> 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<GoogleDriveFolderItem, GoogleDriveFolderItem>(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<object>(url, x => {
|
||||
var url = WebApi.GoogleDrive.FileQueryUrl(Library.Utility.Uri.UrlPathEncode(fileid), SupportsTeamDriveParam());
|
||||
m_oauth.GetJSONData<object>(url, x =>
|
||||
{
|
||||
x.Method = "DELETE";
|
||||
});
|
||||
}
|
||||
@@ -310,15 +323,16 @@ namespace Duplicati.Library.Backend.GoogleDrive
|
||||
}
|
||||
}
|
||||
|
||||
public System.Collections.Generic.IList<ICommandLineArgument> SupportedCommands
|
||||
{
|
||||
get {
|
||||
return new List<ICommandLineArgument>(new ICommandLineArgument[] {
|
||||
new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.GoogleDrive.AuthidShort, Strings.GoogleDrive.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("googledrive"))),
|
||||
new CommandLineArgument(DISABLE_TEAMDRIVE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.GoogleDrive.DisableTeamDriveShort, Strings.GoogleDrive.DisableTeamDriveLong),
|
||||
public System.Collections.Generic.IList<ICommandLineArgument> SupportedCommands => new List<ICommandLineArgument>(new ICommandLineArgument[] {
|
||||
new CommandLineArgument(AUTHID_OPTION,
|
||||
CommandLineArgument.ArgumentType.Password,
|
||||
Strings.GoogleDrive.AuthidShort,
|
||||
Strings.GoogleDrive.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("googledrive"))),
|
||||
new CommandLineArgument(DISABLE_TEAMDRIVE_OPTION,
|
||||
CommandLineArgument.ArgumentType.Boolean,
|
||||
Strings.GoogleDrive.DisableTeamDriveShort,
|
||||
Strings.GoogleDrive.DisableTeamDriveLong),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
@@ -348,8 +362,8 @@ namespace Duplicati.Library.Backend.GoogleDrive
|
||||
}
|
||||
|
||||
public string[] DNSName
|
||||
{
|
||||
get { return new string[] { new System.Uri(DRIVE_API_URL).Host, new System.Uri(DRIVE_API_UPLOAD_URL).Host }; }
|
||||
{
|
||||
get { return new string[] { new System.Uri(WebApi.GoogleDrive.Url.DRIVE).Host, new System.Uri(WebApi.GoogleDrive.Url.UPLOAD).Host }; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -365,21 +379,22 @@ namespace Duplicati.Library.Backend.GoogleDrive
|
||||
|
||||
var newfile = JsonConvert.DeserializeObject<GoogleDriveFolderItem>(JsonConvert.SerializeObject(files[0]));
|
||||
newfile.title = newname;
|
||||
newfile.parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference() { id = CurrentFolderId } };
|
||||
newfile.parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference { id = CurrentFolderId } };
|
||||
|
||||
var url = string.Format("{0}/files/{1}", DRIVE_API_UPLOAD_URL, Library.Utility.Uri.UrlPathEncode(files[0].id));
|
||||
var url = WebApi.GoogleDrive.FileQueryUrl(Library.Utility.Uri.UrlPathEncode(files[0].id));
|
||||
var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(newfile));
|
||||
|
||||
var nf = m_oauth.GetJSONData<GoogleDriveFolderItem>(url, x => {
|
||||
var nf = m_oauth.GetJSONData<GoogleDriveFolderItem>(url, x =>
|
||||
{
|
||||
x.Method = "PUT";
|
||||
x.ContentLength = data.Length;
|
||||
x.ContentType = "application/json; charset=UTF-8";
|
||||
}, x => {
|
||||
|
||||
using(var rs = x.GetRequestStream())
|
||||
}, x =>
|
||||
{
|
||||
using (var rs = x.GetRequestStream())
|
||||
rs.Write(data, 0, data.Length);
|
||||
});
|
||||
|
||||
|
||||
m_filecache[newname] = new GoogleDriveFolderItem[] { nf };
|
||||
m_filecache.Remove(oldname);
|
||||
}
|
||||
@@ -397,9 +412,24 @@ namespace Duplicati.Library.Backend.GoogleDrive
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private NameValueCollection SupportsTeamDriveParam()
|
||||
{
|
||||
return m_useTeamDrive ? new NameValueCollection {
|
||||
{ WebApi.GoogleDrive.QueryParam.SupportsTeamDrive,
|
||||
WebApi.GoogleDrive.QueryValue.True }
|
||||
} : null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
private NameValueCollection IncludeTeamDriveParam()
|
||||
{
|
||||
return m_useTeamDrive ? new NameValueCollection {
|
||||
{ WebApi.GoogleDrive.QueryParam.IncludeTeamDrive,
|
||||
WebApi.GoogleDrive.QueryValue.True } } : null;
|
||||
}
|
||||
|
||||
private class GoogleDriveParentReference
|
||||
{
|
||||
@@ -470,40 +500,52 @@ namespace Duplicati.Library.Backend.GoogleDrive
|
||||
|
||||
private IEnumerable<GoogleDriveFolderItem> ListFolder(string parentfolder, bool? onlyFolders = null, string name = null)
|
||||
{
|
||||
var p = new string[] {
|
||||
var fileQuery = new string[] {
|
||||
string.IsNullOrEmpty(name) ? null : string.Format("title = '{0}'", EscapeTitleEntries(name)),
|
||||
onlyFolders == null ? null : string.Format("mimeType {0}= '{1}'", onlyFolders.Value ? "" : "!", FOLDER_MIMETYPE),
|
||||
string.Format("'{0}' in parents", EscapeTitleEntries(parentfolder))
|
||||
};
|
||||
|
||||
var url = string.Format("{0}/files?q={1}{2}", DRIVE_API_URL, Library.Utility.Uri.UrlEncode(string.Join(" and ", p.Where(x => x != null))), m_useTeamDrive ? "&supportsTeamDrives=true&includeTeamDriveItems=true" : string.Empty);
|
||||
var token = string.Empty;
|
||||
var queryParams = new NameValueCollection
|
||||
{
|
||||
{WebApi.GoogleDrive.QueryParam.File,
|
||||
Library.Utility.Uri.UrlEncode(string.Join(" and ", fileQuery.Where(x => x != null)))},
|
||||
};
|
||||
queryParams.Add(SupportsTeamDriveParam());
|
||||
queryParams.Add(IncludeTeamDriveParam());
|
||||
|
||||
do
|
||||
while (true)
|
||||
{
|
||||
var res = m_oauth.GetJSONData<GoogleDriveListResponse>(url + (string.IsNullOrWhiteSpace(token) ? "" : "&pageToken=" + Library.Utility.Uri.UrlEncode(token)));
|
||||
var url = WebApi.GoogleDrive.FileQueryUrl(queryParams);
|
||||
var res = m_oauth.GetJSONData<GoogleDriveListResponse>(url);
|
||||
foreach (var n in res.items)
|
||||
yield return n;
|
||||
|
||||
token = res.nextPageToken;
|
||||
} while (!string.IsNullOrWhiteSpace(token));
|
||||
var token = res.nextPageToken;
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
break;
|
||||
|
||||
queryParams.Set(WebApi.GoogleDrive.QueryParam.PageToken, token);
|
||||
}
|
||||
}
|
||||
|
||||
private GoogleDriveAboutResponse GetAboutInfo()
|
||||
{
|
||||
var url = string.Format("{0}/about", DRIVE_API_URL);
|
||||
{
|
||||
var url = Library.Utility.Uri.UriBuilder(WebApi.GoogleDrive.Url.DRIVE, WebApi.GoogleDrive.Path.About);
|
||||
return m_oauth.GetJSONData<GoogleDriveAboutResponse>(url);
|
||||
}
|
||||
|
||||
private GoogleDriveFolderItem CreateFolder(string name, string parent)
|
||||
{
|
||||
var url = string.Format("{0}/files{1}", DRIVE_API_URL, m_useTeamDrive ? "?supportsTeamDrives=true" : string.Empty);
|
||||
var folder = new GoogleDriveFolderItem() {
|
||||
var url = WebApi.GoogleDrive.FileQueryUrl(SupportsTeamDriveParam());
|
||||
|
||||
var folder = new GoogleDriveFolderItem()
|
||||
{
|
||||
title = name,
|
||||
description = name,
|
||||
mimeType = FOLDER_MIMETYPE,
|
||||
labels = new GoogleDriveFolderItemLabels { hidden = true },
|
||||
parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference() { id = parent } }
|
||||
parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference { id = parent } }
|
||||
};
|
||||
|
||||
var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(folder));
|
||||
@@ -514,13 +556,11 @@ namespace Duplicati.Library.Backend.GoogleDrive
|
||||
x.ContentType = "application/json; charset=UTF-8";
|
||||
x.ContentLength = data.Length;
|
||||
|
||||
}, req => {
|
||||
|
||||
using(var rs = req.GetRequestStream())
|
||||
}, req =>
|
||||
{
|
||||
using (var rs = req.GetRequestStream())
|
||||
rs.Write(data, 0, data.Length);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 System.Collections.Specialized;
|
||||
|
||||
namespace Duplicati.Library.Backend.WebApi
|
||||
{
|
||||
static class GoogleDrive
|
||||
{
|
||||
public static class Url
|
||||
{
|
||||
public const string DRIVE = "https://www.googleapis.com/drive/v2";
|
||||
public const string UPLOAD = "https://www.googleapis.com/upload/drive/v2";
|
||||
}
|
||||
|
||||
public static class Path
|
||||
{
|
||||
public const string File = "files";
|
||||
public const string About = "about";
|
||||
}
|
||||
|
||||
public static class QueryParam
|
||||
{
|
||||
public const string File = "q";
|
||||
public const string SupportsTeamDrive = "supportsTeamDrives";
|
||||
public const string IncludeTeamDrive = "includeTeamDriveItems";
|
||||
public const string PageToken = "pageToken";
|
||||
public const string UploadType = "uploadType";
|
||||
public const string Alt = "alt";
|
||||
}
|
||||
|
||||
public static class QueryValue
|
||||
{
|
||||
public const string True = "true";
|
||||
public const string Resumable = "resumable";
|
||||
public const string Media = "media";
|
||||
}
|
||||
|
||||
public static string FileQueryUrl(NameValueCollection values)
|
||||
{
|
||||
return Library.Utility.Uri.UriBuilder(Url.DRIVE, Path.File, values);
|
||||
}
|
||||
|
||||
public static string FileQueryUrl(string fileId, NameValueCollection values = null)
|
||||
{
|
||||
return Library.Utility.Uri.UriBuilder(Url.DRIVE, Library.Utility.Uri.ConcatPaths(Path.File, fileId), values);
|
||||
}
|
||||
|
||||
public static string FileUploadUrl(string fileId, NameValueCollection values)
|
||||
{
|
||||
return Library.Utility.Uri.UriBuilder(Url.UPLOAD, Library.Utility.Uri.ConcatPaths(Path.File, fileId), values);
|
||||
}
|
||||
|
||||
public static string FileUploadUrl(NameValueCollection values)
|
||||
{
|
||||
return Library.Utility.Uri.UriBuilder(Url.UPLOAD, Path.File, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,9 +140,9 @@ namespace Duplicati.Library
|
||||
/// <returns>The deserialized JSON data.</returns>
|
||||
/// <param name="url">The remote URL</param>
|
||||
/// <param name="setup">A callback method that can be used to customize the request, e.g. by setting the method, content-type and headers.</param>
|
||||
/// <param name="setupreq">A callback method that can be used to submit data into the body of the request.</param>
|
||||
/// <param name="setupbodyreq">A callback method that can be used to submit data into the body of the request.</param>
|
||||
/// <typeparam name="T">The type of data to return.</typeparam>
|
||||
public virtual T GetJSONData<T>(string url, Action<HttpWebRequest> setup = null, Action<AsyncHttpRequest> setupreq = null)
|
||||
public virtual T GetJSONData<T>(string url, Action<HttpWebRequest> setup = null, Action<AsyncHttpRequest> setupbodyreq = null)
|
||||
{
|
||||
var req = CreateRequest(url);
|
||||
|
||||
@@ -151,8 +151,8 @@ namespace Duplicati.Library
|
||||
|
||||
var areq = new AsyncHttpRequest(req);
|
||||
|
||||
if (setupreq != null)
|
||||
setupreq(areq);
|
||||
if (setupbodyreq != null)
|
||||
setupbodyreq(areq);
|
||||
|
||||
return ReadJSONResponse<T>(areq);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace Duplicati.Library.Backend.OpenStack
|
||||
new KeyValuePair<string, string>("Rackspace UK", "https://lon.identity.api.rackspacecloud.com/v2.0"),
|
||||
new KeyValuePair<string, string>("OVH Cloud Storage", "https://auth.cloud.ovh.net/v2.0"),
|
||||
new KeyValuePair<string, string>("Selectel Cloud Storage", "https://auth.selcdn.ru"),
|
||||
new KeyValuePair<string, string>("Memset Cloud Storage", "https://auth.storage.memset.com"),
|
||||
};
|
||||
|
||||
public static readonly KeyValuePair<string, string>[] OPENSTACK_VERSIONS = {
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<Compile Include="TempFolder.cs" />
|
||||
<Compile Include="ThrottledStream.cs" />
|
||||
<Compile Include="Timeparser.cs" />
|
||||
<Compile Include="UrlUtillity.cs" />
|
||||
<Compile Include="UrlUtility.cs" />
|
||||
<Compile Include="Utility.cs" />
|
||||
<Compile Include="Win32.cs" />
|
||||
<Compile Include="WorkerThread.cs" />
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace Duplicati.Library.Utility
|
||||
@@ -62,18 +64,18 @@ namespace Duplicati.Library.Utility
|
||||
/// <summary>
|
||||
/// The password, if any
|
||||
/// </summary>
|
||||
public readonly string Password;
|
||||
|
||||
public readonly string Password;
|
||||
|
||||
/// <summary>
|
||||
/// The original URI.
|
||||
/// </summary>
|
||||
public readonly string OriginalUri;
|
||||
|
||||
public readonly string OriginalUri;
|
||||
|
||||
/// <summary>
|
||||
/// Cache for the query parameters.
|
||||
/// </summary>
|
||||
private NameValueCollection m_queryParams;
|
||||
|
||||
private NameValueCollection m_queryParams;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the paramters in the query string
|
||||
/// </summary>
|
||||
@@ -88,12 +90,12 @@ namespace Duplicati.Library.Utility
|
||||
m_queryParams = new NameValueCollection();
|
||||
else
|
||||
m_queryParams = ParseQueryString(Query);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return m_queryParams;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the host and path.
|
||||
/// </summary>
|
||||
@@ -109,8 +111,8 @@ namespace Duplicati.Library.Utility
|
||||
else
|
||||
return Host + (Path == null ? "" : "/" + Path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path and query.
|
||||
/// </summary>
|
||||
@@ -119,10 +121,10 @@ namespace Duplicati.Library.Utility
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Path ?? "") + (Query == null ? "" : "?" + Query);
|
||||
return (Path ?? "") + (Query == null ? "" : "?" + Query);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Duplicati.Library.Utility.Uri"/> struct.
|
||||
/// </summary>
|
||||
@@ -130,8 +132,8 @@ namespace Duplicati.Library.Utility
|
||||
public Uri(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
|
||||
m_queryParams = null;
|
||||
this.OriginalUri = url;
|
||||
|
||||
@@ -159,8 +161,8 @@ namespace Duplicati.Library.Utility
|
||||
{
|
||||
}
|
||||
throw new ArgumentException(Strings.Uri.UriParseError(url), nameof(url));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.Scheme = m.Groups["scheme"].Value;
|
||||
var h = m.Groups["hostname"].Success ? m.Groups["hostname"].Value : "";
|
||||
|
||||
@@ -185,8 +187,8 @@ namespace Duplicati.Library.Utility
|
||||
this.Port = int.Parse(m.Groups["port"].Value);
|
||||
else
|
||||
this.Port = -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a free-form URI from components
|
||||
/// </summary>
|
||||
@@ -208,13 +210,13 @@ namespace Duplicati.Library.Utility
|
||||
Password = password;
|
||||
Port = port;
|
||||
OriginalUri = AsString(scheme, host, path, query, username, password, port);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String"/> that represents the current <see cref="Duplicati.Library.Utility.Uri"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="System.String"/> that represents the current <see cref="Duplicati.Library.Utility.Uri"/>.</returns>
|
||||
public override string ToString ()
|
||||
public override string ToString()
|
||||
{
|
||||
return AsString(Scheme, Host, Path, Query, Username, Password, Port);
|
||||
}
|
||||
@@ -226,8 +228,8 @@ namespace Duplicati.Library.Utility
|
||||
{
|
||||
if (string.IsNullOrEmpty(Host))
|
||||
throw new ArgumentException(Strings.Uri.NoHostname(OriginalUri));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an url-like string from components.
|
||||
/// </summary>
|
||||
@@ -243,21 +245,21 @@ namespace Duplicati.Library.Utility
|
||||
{
|
||||
var s = scheme + "://";
|
||||
if (!string.IsNullOrEmpty(username) || !string.IsNullOrEmpty(password))
|
||||
{
|
||||
|
||||
{
|
||||
|
||||
s += UrlEncode(username ?? "");
|
||||
s += ":";
|
||||
s += UrlEncode(password ?? "");
|
||||
s += "@";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(host))
|
||||
{
|
||||
s += host;
|
||||
if (port != -1)
|
||||
s += ":" + port.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(host) && !path.StartsWith("/", StringComparison.Ordinal))
|
||||
@@ -267,9 +269,9 @@ namespace Duplicati.Library.Utility
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
s += "?" + query;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance with another scheme
|
||||
/// </summary>
|
||||
@@ -278,8 +280,8 @@ namespace Duplicati.Library.Utility
|
||||
public Uri SetScheme(string scheme)
|
||||
{
|
||||
return new Uri(scheme, Host, Path, Query, Username, Password, Port);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance with another host
|
||||
/// </summary>
|
||||
@@ -288,8 +290,8 @@ namespace Duplicati.Library.Utility
|
||||
public Uri SetHost(string host)
|
||||
{
|
||||
return new Uri(Scheme, host, Path, Query, Username, Password, Port);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance with another path
|
||||
/// </summary>
|
||||
@@ -298,8 +300,8 @@ namespace Duplicati.Library.Utility
|
||||
public Uri SetPath(string path)
|
||||
{
|
||||
return new Uri(Scheme, Host, path, Query, Username, Password, Port);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance with another query
|
||||
/// </summary>
|
||||
@@ -319,8 +321,8 @@ namespace Duplicati.Library.Utility
|
||||
public Uri SetCredentials(string username, string password)
|
||||
{
|
||||
return new Uri(Scheme, Host, Path, Query, username, password, Port);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance with another port
|
||||
/// </summary>
|
||||
@@ -329,13 +331,13 @@ namespace Duplicati.Library.Utility
|
||||
public Uri SetPort(int port)
|
||||
{
|
||||
return new Uri(Scheme, Host, Path, Query, Username, Password, port);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The regular expression that matches %20 type values in a querystring
|
||||
/// </summary>
|
||||
private static System.Text.RegularExpressions.Regex RE_ESCAPECHAR = new System.Text.RegularExpressions.Regex(@"[^0-9a-zA-Z\-_]", System.Text.RegularExpressions.RegexOptions.Compiled);
|
||||
|
||||
private static System.Text.RegularExpressions.Regex RE_ESCAPECHAR = new System.Text.RegularExpressions.Regex(@"[^0-9a-zA-Z\-_]", System.Text.RegularExpressions.RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a URL, like System.Web.HttpUtility.UrlEncode
|
||||
/// </summary>
|
||||
@@ -345,44 +347,45 @@ namespace Duplicati.Library.Utility
|
||||
public static string UrlPathEncode(string value, System.Text.Encoding encoding = null)
|
||||
{
|
||||
return UrlEncode(value, encoding, "%20");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a URL, like System.Web.HttpUtility.UrlEncode
|
||||
/// </summary>
|
||||
/// <returns>The encoded URL</returns>
|
||||
/// <param name="value">The URL fragment to encode</param>
|
||||
/// <param name="encoding">The encoding to use</param>
|
||||
public static string UrlEncode(string value, System.Text.Encoding encoding = null, string spacevalue = "+")
|
||||
public static string UrlEncode(string value, System.Text.Encoding encoding = null, string spacevalue = "+")
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
encoding = encoding ?? System.Text.Encoding.UTF8;
|
||||
|
||||
var encoder = encoding.GetEncoder();
|
||||
var inbuf = new char[1];
|
||||
var inbuf = new char[1];
|
||||
var outbuf = new byte[4];
|
||||
|
||||
return RE_ESCAPECHAR.Replace(value, (m) => {
|
||||
return RE_ESCAPECHAR.Replace(value, (m) =>
|
||||
{
|
||||
if (m.Value == " ")
|
||||
return spacevalue;
|
||||
|
||||
inbuf[0] = m.Value[0];
|
||||
|
||||
try
|
||||
return spacevalue;
|
||||
|
||||
inbuf[0] = m.Value[0];
|
||||
|
||||
try
|
||||
{
|
||||
var len = encoder.GetBytes(inbuf, 0, 1, outbuf, 0, true);
|
||||
return "%" + BitConverter.ToString(outbuf, 0, len).Replace("-", "%");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
//Fallback
|
||||
}
|
||||
|
||||
//Fallback
|
||||
return m.Value;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -399,15 +402,16 @@ namespace Duplicati.Library.Utility
|
||||
public static string UrlDecode(string value, System.Text.Encoding encoding = null)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
encoding = encoding ?? System.Text.Encoding.UTF8;
|
||||
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
encoding = encoding ?? System.Text.Encoding.UTF8;
|
||||
|
||||
var decoder = encoding.GetDecoder();
|
||||
var inbuf = new byte[8];
|
||||
var outbuf = new char[8];
|
||||
|
||||
return RE_NUMBER.Replace(value, (m) => {
|
||||
return RE_NUMBER.Replace(value, (m) =>
|
||||
{
|
||||
if (m.Value == "+")
|
||||
return " ";
|
||||
|
||||
@@ -416,24 +420,24 @@ namespace Duplicati.Library.Utility
|
||||
var hex = m.Groups["number"].Value;
|
||||
var bytelen = hex.Length / 2;
|
||||
Utility.HexStringAsByteArray(hex, inbuf);
|
||||
var c = decoder.GetChars(inbuf, 0, bytelen, outbuf, 0);
|
||||
var c = decoder.GetChars(inbuf, 0, bytelen, outbuf, 0);
|
||||
return new string(outbuf, 0, c);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
//Fallback
|
||||
}
|
||||
|
||||
//Fallback
|
||||
return m.Value;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The regular expression that matches a=b type values in a querystring
|
||||
/// </summary>
|
||||
private static System.Text.RegularExpressions.Regex RE_URLPARAM = new System.Text.RegularExpressions.Regex(@"(?<key>[^\=\&]+)(\=(?<value>[^\&]*))?", System.Text.RegularExpressions.RegexOptions.Compiled);
|
||||
|
||||
private static System.Text.RegularExpressions.Regex RE_URLPARAM = new System.Text.RegularExpressions.Regex(@"(?<key>[^\=\&]+)(\=(?<value>[^\&]*))?", System.Text.RegularExpressions.RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Parses the query string.
|
||||
/// This is a duplicate of the System.Web.HttpUtility.ParseQueryString that does not work well on Mono
|
||||
@@ -447,14 +451,104 @@ namespace Duplicati.Library.Utility
|
||||
if (query.StartsWith("?", StringComparison.Ordinal))
|
||||
query = query.Substring(1);
|
||||
if (string.IsNullOrEmpty(query))
|
||||
return new NameValueCollection(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return new NameValueCollection(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
|
||||
foreach(System.Text.RegularExpressions.Match m in RE_URLPARAM.Matches(query))
|
||||
result.Add(UrlDecode(m.Groups["key"].Value), UrlDecode(m.Groups["value"].Success ? m.Groups["value"].Value : ""));
|
||||
|
||||
return result;
|
||||
}
|
||||
foreach (System.Text.RegularExpressions.Match m in RE_URLPARAM.Matches(query))
|
||||
result.Add(UrlDecode(m.Groups["key"].Value), UrlDecode(m.Groups["value"].Success ? m.Groups["value"].Value : ""));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the querystring to be used in a URL
|
||||
/// </summary>
|
||||
/// <returns>The generated querystring</returns>
|
||||
/// <param name="query">A collection of name value pairs to be translated into a query string</param>
|
||||
/// <param name="delimiter">The delimiter to separate key value pairs in the query string</param>
|
||||
public static string BuildUriQuery(NameValueCollection query, string delimiter)
|
||||
{
|
||||
|
||||
if (query == null)
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
foreach (var key in query.Cast<string>().Where(key => !string.IsNullOrEmpty(query[key])))
|
||||
{
|
||||
builder.Append(builder.Length == 0 ? string.Empty : delimiter)
|
||||
.Append(key)
|
||||
.Append("=")
|
||||
.Append(query[key]);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the querystring to be used in a URL
|
||||
/// </summary>
|
||||
/// <returns>The generated querystring</returns>
|
||||
/// <param name="query">A collection of name value pairs to be translated into a query string that is
|
||||
/// ampsersand delimited.</param>
|
||||
public static string BuildUriQuery(NameValueCollection query)
|
||||
{
|
||||
return BuildUriQuery(query, "&");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a URL together using a base URL, a path and a query.
|
||||
/// </summary>
|
||||
/// <returns>The built together URL.</returns>
|
||||
/// <param name="url">Base URL, containing schema, host, port.</param>
|
||||
/// <param name="path">Base path.</param>
|
||||
/// <param name="query">A collection of name value pairs to be translated into a query string.</param>
|
||||
public static string UriBuilder(string url, string path, NameValueCollection query)
|
||||
{
|
||||
var builder = new UriBuilder(url)
|
||||
{
|
||||
Path = ConcatPaths(ExtractPath(url), path),
|
||||
Query = query != null ? BuildUriQuery(query) : null
|
||||
};
|
||||
return builder.Uri.AbsoluteUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concats paths of URIs.
|
||||
/// </summary>
|
||||
/// <returns>The concatenated paths.</returns>
|
||||
/// <param name="path1">Path1.</param>
|
||||
/// <param name="path2">Path2.</param>
|
||||
public static string ConcatPaths(string path1, string path2)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path2))
|
||||
{
|
||||
return path1;
|
||||
}
|
||||
|
||||
return path1.TrimEnd('/') + '/' + path2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grab path part of a URI.
|
||||
/// At the moment, simple implementation does not remove fragments.
|
||||
/// </summary>
|
||||
/// <returns>The path.</returns>
|
||||
/// <param name="url">URL.</param>
|
||||
public static string ExtractPath(string url)
|
||||
{
|
||||
return (new Uri(url)).Path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a URL together using a base URL and path.
|
||||
/// </summary>
|
||||
/// <returns>The built together URL.</returns>
|
||||
/// <param name="url">Base URL, containing schema, host, port.</param>
|
||||
/// <param name="path">Base path.</param>
|
||||
public static string UriBuilder(string url, string path)
|
||||
{
|
||||
return UriBuilder(url, path, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ using System.Text;
|
||||
|
||||
namespace Duplicati.Library.Utility
|
||||
{
|
||||
public static class UrlUtillity
|
||||
public static class UrlUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// The file path to the system browser selected
|
||||
@@ -1,4 +1,3 @@
|
||||
#region Disclaimer / License
|
||||
// Copyright (C) 2015, The Duplicati Team
|
||||
// http://www.duplicati.com, info@duplicati.com
|
||||
//
|
||||
@@ -15,21 +14,13 @@
|
||||
// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Duplicati.Library.Utility
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<Compile Include="UtilityTests.cs" />
|
||||
<Compile Include="FilterTest.cs" />
|
||||
<Compile Include="ResultFormatSerializerProviderTest.cs" />
|
||||
<Compile Include="UriUtilityTests.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
@@ -173,4 +174,4 @@
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user