From 38883285eee5598e37d56e6ddc417fad7d872ed3 Mon Sep 17 00:00:00 2001 From: Tyler Gill Date: Mon, 25 Sep 2017 23:17:45 -0600 Subject: Change IBackend.List() to return IEnumerable instead of List By changing to IEnumerable, it is possible to iterate only a portion of the list, which is useful when not all entries are needed (e.g., when testing a connection). All existing backends have been updated, and any which were able to be changed to yield return results in a straightforward way now do. Many backends had a try/catch in the List() method. Due to the fact that yield returns can't be placed within a try/catch block, these have been refactored to either scope the try/catch to the parts that (should) be the only places throwing exceptions, so that exceptions are still caught and handled. Note that lazy evaluation may cause some changes in behavior - exceptions that were previously thrown at the point of invokation of List() may now be thrown while it is being enumerated. I believe this will not be problematic though, as the only well-known exception seems to be FolderMissingException, which should be thrown by Test(), but TestList() attempts to enumerate the list to force this exception. Any places that require the legacy behavior can get it by simply converting the lazy enumerable to a List() --- .../AlternativeFTP/AlternativeFTPBackend.cs | 6 +- .../Library/Backend/AmazonCloudDrive/AmzCD.cs | 4 +- .../Library/Backend/AzureBlob/AzureBlobBackend.cs | 4 +- Duplicati/Library/Backend/Backblaze/B2.cs | 4 +- Duplicati/Library/Backend/Box/BoxBackend.cs | 9 +- Duplicati/Library/Backend/CloudFiles/CloudFiles.cs | 10 +- Duplicati/Library/Backend/Dropbox/Dropbox.cs | 38 ++-- Duplicati/Library/Backend/FTP/FTPBackend.cs | 43 +++-- Duplicati/Library/Backend/File/FileBackend.cs | 16 +- .../Backend/GoogleServices/GoogleCloudStorage.cs | 66 +++---- .../Library/Backend/GoogleServices/GoogleDrive.cs | 73 +++---- Duplicati/Library/Backend/HubiC/HubiCBackend.cs | 2 +- Duplicati/Library/Backend/Jottacloud/Jottacloud.cs | 10 +- Duplicati/Library/Backend/Mega/MegaBackend.cs | 9 +- Duplicati/Library/Backend/OneDrive/OneDrive.cs | 15 +- .../Library/Backend/OpenStack/OpenStackStorage.cs | 69 +++---- Duplicati/Library/Backend/S3/S3Backend.cs | 29 +-- Duplicati/Library/Backend/S3/S3Wrapper.cs | 37 ++-- Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs | 10 +- .../Backend/SharePoint/SharePointBackend.cs | 35 ++-- Duplicati/Library/Backend/Sia/Sia.cs | 26 +-- .../Library/Backend/TahoeLAFS/TahoeBackend.cs | 9 +- Duplicati/Library/Backend/WEBDAV/WEBDAV.cs | 213 +++++++++++---------- 23 files changed, 368 insertions(+), 369 deletions(-) (limited to 'Duplicati/Library/Backend') diff --git a/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs b/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs index 9cdcc09b9..032854043 100644 --- a/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs +++ b/Duplicati/Library/Backend/AlternativeFTP/AlternativeFTPBackend.cs @@ -194,17 +194,17 @@ namespace Duplicati.Library.Backend.AlternativeFTP } } - public List List() + public IEnumerable List() { return List(""); } - public List List(string filename) + public IEnumerable List(string filename) { return List(filename, false); } - private List List(string filename, bool stripFile) + private IEnumerable List(string filename, bool stripFile) { var list = new List(); string remotePath = filename; diff --git a/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs b/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs index e24a949fa..b68319333 100644 --- a/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs +++ b/Duplicati/Library/Backend/AmazonCloudDrive/AmzCD.cs @@ -336,7 +336,7 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive #endregion #region IBackend implementation - public List List() + public IEnumerable List() { EnforceConsistencyDelay(RemoteOperation.List); @@ -416,7 +416,7 @@ namespace Duplicati.Library.Backend.AmazonCloudDrive } public void Test() { - List(); + this.TestList(); } public void CreateFolder() { diff --git a/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs b/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs index 2e200c8fd..d55eb17b0 100644 --- a/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs +++ b/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs @@ -82,7 +82,7 @@ namespace Duplicati.Library.Backend.AzureBlob get { return true; } } - public List List() + public IEnumerable List() { return _azureBlob.ListContainerEntries(); } @@ -161,7 +161,7 @@ namespace Duplicati.Library.Backend.AzureBlob public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/Backblaze/B2.cs b/Duplicati/Library/Backend/Backblaze/B2.cs index 383e2e009..682e05e23 100644 --- a/Duplicati/Library/Backend/Backblaze/B2.cs +++ b/Duplicati/Library/Backend/Backblaze/B2.cs @@ -285,7 +285,7 @@ namespace Duplicati.Library.Backend.Backblaze } } - public List List() + public IEnumerable List() { m_filecache = null; var cache = new Dictionary>(); @@ -380,7 +380,7 @@ namespace Duplicati.Library.Backend.Backblaze public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/Box/BoxBackend.cs b/Duplicati/Library/Backend/Box/BoxBackend.cs index 51e3c838e..124a51fe7 100644 --- a/Duplicati/Library/Backend/Box/BoxBackend.cs +++ b/Duplicati/Library/Backend/Box/BoxBackend.cs @@ -252,12 +252,11 @@ namespace Duplicati.Library.Backend.Box #region IBackend implementation - public System.Collections.Generic.List List() + public System.Collections.Generic.IEnumerable List() { - return ( + return from n in PagedFileListResponse(CurrentFolder, false) - select (IFileEntry)new FileEntry(n.Name, n.Size, n.ModifiedAt, n.ModifiedAt) { IsFolder = n.Type == "folder" } - ).ToList(); + select (IFileEntry)new FileEntry(n.Name, n.Size, n.ModifiedAt, n.ModifiedAt) { IsFolder = n.Type == "folder" }; } public void Put(string remotename, string filename) @@ -295,7 +294,7 @@ namespace Duplicati.Library.Backend.Box public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs index 1b33756d3..29cbc2ba1 100644 --- a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs +++ b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs @@ -19,6 +19,7 @@ #endregion using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Net; using Duplicati.Library.Interface; @@ -128,9 +129,8 @@ namespace Duplicati.Library.Backend get { return "cloudfiles"; } } - public List List() + public IEnumerable List() { - var files = new List(); string extraUrl = "?format=xml&limit=" + ITEM_LIST_LIMIT.ToString(); string markerUrl = ""; @@ -182,7 +182,7 @@ namespace Duplicati.Library.Backend mod = new DateTime(); lastItemName = name; - files.Add(new FileEntry(name, size, mod, mod)); + yield return new FileEntry(name, size, mod, mod); } repeat = lst.Count == ITEM_LIST_LIMIT; @@ -191,8 +191,6 @@ namespace Duplicati.Library.Backend markerUrl = "&marker=" + Library.Utility.Uri.UrlEncode(lastItemName); } while (repeat); - - return files; } public void Put(string remotename, string filename) @@ -263,7 +261,7 @@ namespace Duplicati.Library.Backend public void Test() { //The "Folder not found" is not detectable :( - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/Dropbox/Dropbox.cs b/Duplicati/Library/Backend/Dropbox/Dropbox.cs index 8a70684a0..76e555c0a 100644 --- a/Duplicati/Library/Backend/Dropbox/Dropbox.cs +++ b/Duplicati/Library/Backend/Dropbox/Dropbox.cs @@ -67,35 +67,37 @@ namespace Duplicati.Library.Backend return ife; } - - public List List() + + public IEnumerable List() { try { - var list = new List(); - var lfr = dbx.ListFiles(m_path); - - foreach (var md in lfr.entries) - list.Add(ParseEntry(md)); - - while (lfr.has_more) - { - lfr = dbx.ListFilesContinue(lfr.cursor); - foreach (var md in lfr.entries) - list.Add(ParseEntry(md)); - } - - return list; + return ListWithoutExceptionCatch(); } catch (DropboxException de) { if (de.errorJSON["error"][".tag"].ToString() == "path" && de.errorJSON["error"]["path"][".tag"].ToString() == "not_found") throw new FolderMissingException(); - + throw; } } + private IEnumerable ListWithoutExceptionCatch() + { + var lfr = dbx.ListFiles(m_path); + + foreach (var md in lfr.entries) + yield return ParseEntry(md); + + while (lfr.has_more) + { + lfr = dbx.ListFilesContinue(lfr.cursor); + foreach (var md in lfr.entries) + yield return ParseEntry(md); + } + } + public void Put(string remotename, string filename) { using(FileStream fs = System.IO.File.OpenRead(filename)) @@ -136,7 +138,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/FTP/FTPBackend.cs b/Duplicati/Library/Backend/FTP/FTPBackend.cs index 1aea8537d..6ac888979 100644 --- a/Duplicati/Library/Backend/FTP/FTPBackend.cs +++ b/Duplicati/Library/Backend/FTP/FTPBackend.cs @@ -170,12 +170,12 @@ namespace Duplicati.Library.Backend get { return true; } } - public List List() + public IEnumerable List() { return List(""); } - - public List List(string filename) + + public IEnumerable List(string filename) { var req = CreateRequest(filename); req.Method = System.Net.WebRequestMethods.Ftp.ListDirectoryDetails; @@ -183,21 +183,7 @@ namespace Duplicati.Library.Backend try { - var lst = new List(); - var areq = new Utility.AsyncHttpRequest(req); - using (var resp = areq.GetResponse()) - using (var rs = areq.GetResponseStream()) - using (var sr = new System.IO.StreamReader(new StreamReadHelper(rs))) - { - string line; - while ((line = sr.ReadLine()) != null) - { - FileEntry f = ParseLine(line); - if (f != null) - lst.Add(f); - } - } - return lst; + return ListWithoutExceptionCatch(req); } catch (System.Net.WebException wex) { @@ -208,6 +194,23 @@ namespace Duplicati.Library.Backend } } + private IEnumerable ListWithoutExceptionCatch(System.Net.FtpWebRequest req) + { + var areq = new Utility.AsyncHttpRequest(req); + using (var resp = areq.GetResponse()) + using (var rs = areq.GetResponseStream()) + using (var sr = new System.IO.StreamReader(new StreamReadHelper(rs))) + { + string line; + while ((line = sr.ReadLine()) != null) + { + FileEntry f = ParseLine(line); + if (f != null) + yield return f; + } + } + } + public void Put(string remotename, System.IO.Stream input) { System.Net.FtpWebRequest req = null; @@ -227,7 +230,7 @@ namespace Duplicati.Library.Backend if (m_listVerify) { - List files = List(remotename); + IEnumerable files = List(remotename); foreach(IFileEntry fe in files) if (fe.Name.Equals(remotename) || fe.Name.EndsWith("/" + remotename) || fe.Name.EndsWith("\\" + remotename)) { @@ -308,7 +311,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/File/FileBackend.cs b/Duplicati/Library/Backend/File/FileBackend.cs index cafba1170..8a846594b 100644 --- a/Duplicati/Library/Backend/File/FileBackend.cs +++ b/Duplicati/Library/Backend/File/FileBackend.cs @@ -164,30 +164,26 @@ namespace Duplicati.Library.Backend get { return true; } } - public List List() + public IEnumerable List() { - List ls = new List(); - PreAuthenticate(); if (!System.IO.Directory.Exists(m_path)) throw new FolderMissingException(Strings.FileBackend.FolderMissingError(m_path)); - foreach (string s in System.IO.Directory.GetFiles(m_path)) + foreach (string s in System.IO.Directory.EnumerateFiles(m_path)) { System.IO.FileInfo fi = new System.IO.FileInfo(s); - ls.Add(new FileEntry(fi.Name, fi.Length, fi.LastAccessTime, fi.LastWriteTime)); + yield return new FileEntry(fi.Name, fi.Length, fi.LastAccessTime, fi.LastWriteTime); } - foreach (string s in System.IO.Directory.GetDirectories(m_path)) + foreach (string s in System.IO.Directory.EnumerateDirectories(m_path)) { System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(s); FileEntry fe = new FileEntry(di.Name, 0, di.LastAccessTime, di.LastWriteTime); fe.IsFolder = true; - ls.Add(fe); + yield return fe; } - - return ls; } #if DEBUG_RETRY @@ -265,7 +261,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs index dcdeb64db..663bd274b 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs @@ -98,8 +98,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage m_oauth = new OAuthHelper(authid, this.ProtocolKey); m_oauth.AutoAuthHeader = true; } - - + private class ListBucketResponse { @@ -130,39 +129,13 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage public string location { get; set; } public string storageClass { get; set; } } + #region IBackend implementation - public List List() + public IEnumerable List() { try { - var res = new List(); - string token = null; - do - { - var url = string.Format("{0}/b/{1}/o?prefix={2}", API_URL, m_bucket, Library.Utility.Uri.UrlEncode(m_prefix)); - if (!string.IsNullOrEmpty(token)) - url += string.Format("&pageToken={0}", token); - var resp = m_oauth.ReadJSONResponse(url); - - if (resp.items != null) - foreach(var f in resp.items) - { - var name = f.name; - if (name.StartsWith(m_prefix, StringComparison.OrdinalIgnoreCase)) - name = name.Substring(m_prefix.Length); - if (f.size == null) - res.Add(new FileEntry(name)); - else if (f.updated == null) - res.Add(new FileEntry(name, f.size.Value)); - else - res.Add(new FileEntry(name, f.size.Value, f.updated.Value, f.updated.Value)); - } - - token = resp.nextPageToken; - - } while(!string.IsNullOrEmpty(token)); - - return res; + return this.ListWithoutExceptionCatch(); } catch (WebException wex) { @@ -173,6 +146,35 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage } } + private IEnumerable ListWithoutExceptionCatch() + { + string token = null; + do + { + var url = string.Format("{0}/b/{1}/o?prefix={2}", API_URL, m_bucket, Library.Utility.Uri.UrlEncode(m_prefix)); + if (!string.IsNullOrEmpty(token)) + url += string.Format("&pageToken={0}", token); + var resp = m_oauth.ReadJSONResponse(url); + + if (resp.items != null) + foreach (var f in resp.items) + { + var name = f.name; + if (name.StartsWith(m_prefix, StringComparison.OrdinalIgnoreCase)) + name = name.Substring(m_prefix.Length); + if (f.size == null) + yield return new FileEntry(name); + else if (f.updated == null) + yield return new FileEntry(name, f.size.Value); + else + yield return new FileEntry(name, f.size.Value, f.updated.Value, f.updated.Value); + } + + token = resp.nextPageToken; + + } while (!string.IsNullOrEmpty(token)); + } + public void Put(string remotename, string filename) { using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) @@ -195,7 +197,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs index c4e6af992..736044bbe 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs @@ -189,44 +189,14 @@ namespace Duplicati.Library.Backend.GoogleDrive #region IBackend implementation - public List List() + public IEnumerable List() { try { - var res = new List(); m_filecache.Clear(); - foreach(var n in ListFolder(CurrentFolderId)) - { - FileEntry fe = null; - - if (n.fileSize == null) - fe = new FileEntry(n.title); - else if (n.modifiedDate == null) - fe = new FileEntry(n.title, n.fileSize.Value); - else - fe = new FileEntry(n.title, n.fileSize.Value, n.modifiedDate.Value, n.modifiedDate.Value); - - if (fe != null) - { - fe.IsFolder = FOLDER_MIMETYPE.Equals(n.mimeType, StringComparison.OrdinalIgnoreCase); - res.Add(fe); - - if (!fe.IsFolder) - { - GoogleDriveFolderItem[] lst; - if (!m_filecache.TryGetValue(fe.Name, out lst)) - m_filecache[fe.Name] = new GoogleDriveFolderItem[] { n }; - else - { - Array.Resize(ref lst, lst.Length + 1); - lst[lst.Length - 1] = n; - } - } - } - } - - return res; + // For now, this class assumes that List() fully populates the file cache + return ListWithoutExceptionCatch().ToList(); } catch { @@ -234,7 +204,42 @@ namespace Duplicati.Library.Backend.GoogleDrive throw; } + } + + private IEnumerable ListWithoutExceptionCatch() + { + foreach (var n in ListFolder(CurrentFolderId)) + { + FileEntry fe = null; + + if (n.fileSize == null) + fe = new FileEntry(n.title); + else if (n.modifiedDate == null) + fe = new FileEntry(n.title, n.fileSize.Value); + else + fe = new FileEntry(n.title, n.fileSize.Value, n.modifiedDate.Value, n.modifiedDate.Value); + + if (fe != null) + { + fe.IsFolder = FOLDER_MIMETYPE.Equals(n.mimeType, StringComparison.OrdinalIgnoreCase); + + if (!fe.IsFolder) + { + GoogleDriveFolderItem[] lst; + if (!m_filecache.TryGetValue(fe.Name, out lst)) + { + m_filecache[fe.Name] = new GoogleDriveFolderItem[] { n }; + } + else + { + Array.Resize(ref lst, lst.Length + 1); + lst[lst.Length - 1] = n; + } + } + yield return fe; + } + } } public void Put(string remotename, string filename) @@ -274,7 +279,7 @@ namespace Duplicati.Library.Backend.GoogleDrive public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/HubiC/HubiCBackend.cs b/Duplicati/Library/Backend/HubiC/HubiCBackend.cs index 748842d59..1f8fb03c0 100644 --- a/Duplicati/Library/Backend/HubiC/HubiCBackend.cs +++ b/Duplicati/Library/Backend/HubiC/HubiCBackend.cs @@ -21,6 +21,6 @@ namespace Duplicati.Library.Backend.HubiC public class HubiCBackend : IBackend, IStreamingBackend { private const string AUTHID_OPTION = "authid"; private const string HUBIC_API_URL = "https://api.hubic.com/1.0/"; private const string HUBIC_API_CREDENTIAL_URL = HUBIC_API_URL + "account/credentials"; private OpenStackStorage m_openstack; private class HubiCAuthResponse { public string token { get; set; } public string endpoint { get; set; } public DateTime? expires { get; set;} } private class OpenStackHelper : OpenStackStorage { private OAuthHelper m_helper; private HubiCAuthResponse m_token; public OpenStackHelper(string authid, string url) : base(url, MockOptions()) { m_helper = new OAuthHelper(authid, "hubic") { AutoAuthHeader = true }; } private static Dictionary MockOptions() { var res = new Dictionary(); res["openstack-authuri"] = "invalid://dont-use"; res["openstack-apikey"] = "invalid"; res["auth-username"] = "invalid"; return res; } private HubiCAuthResponse AuthToken { get { if (m_token == null || (m_token.expires != null && (m_token.expires.Value - DateTime.UtcNow).TotalSeconds < 30)) m_token = m_helper.ReadJSONResponse(HUBIC_API_CREDENTIAL_URL); return m_token; } } protected override string AccessToken { get { return AuthToken.token; } } protected override string SimpleStorageEndPoint { get { return AuthToken.endpoint; } } } public HubiCBackend() { } - public HubiCBackend(string url, Dictionary options) { string authid = null; if (options.ContainsKey(AUTHID_OPTION)) authid = options[AUTHID_OPTION]; m_openstack = new OpenStackHelper(authid, url); } #region IStreamingBackend implementation public void Put(string remotename, System.IO.Stream stream) { m_openstack.Put(remotename, stream); } public void Get(string remotename, System.IO.Stream stream) { m_openstack.Get(remotename, stream); } #endregion #region IBackend implementation public List List() { return m_openstack.List(); } public void Put(string remotename, string filename) { m_openstack.Put(remotename, filename); } public void Get(string remotename, string filename) { m_openstack.Get(remotename, filename); } public void Delete(string remotename) { m_openstack.Delete(remotename); } public void Test() { m_openstack.Test(); } public void CreateFolder() { m_openstack.CreateFolder(); } public string DisplayName { get { return Strings.HubiC.DisplayName; } } public string ProtocolKey { get { return "hubic"; } } public System.Collections.Generic.IList SupportedCommands { get { return new List(new ICommandLineArgument[] { new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.HubiC.AuthidShort, Strings.HubiC.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("hubic"))), }); } } public string Description { get { return Strings.HubiC.Description; } } #endregion #region IDisposable implementation public void Dispose() { if (m_openstack != null) { m_openstack.Dispose(); m_openstack = null; } } #endregion } + public HubiCBackend(string url, Dictionary options) { string authid = null; if (options.ContainsKey(AUTHID_OPTION)) authid = options[AUTHID_OPTION]; m_openstack = new OpenStackHelper(authid, url); } #region IStreamingBackend implementation public void Put(string remotename, System.IO.Stream stream) { m_openstack.Put(remotename, stream); } public void Get(string remotename, System.IO.Stream stream) { m_openstack.Get(remotename, stream); } #endregion #region IBackend implementation public IEnumerable List() { return m_openstack.List(); } public void Put(string remotename, string filename) { m_openstack.Put(remotename, filename); } public void Get(string remotename, string filename) { m_openstack.Get(remotename, filename); } public void Delete(string remotename) { m_openstack.Delete(remotename); } public void Test() { m_openstack.Test(); } public void CreateFolder() { m_openstack.CreateFolder(); } public string DisplayName { get { return Strings.HubiC.DisplayName; } } public string ProtocolKey { get { return "hubic"; } } public System.Collections.Generic.IList SupportedCommands { get { return new List(new ICommandLineArgument[] { new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.HubiC.AuthidShort, Strings.HubiC.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("hubic"))), }); } } public string Description { get { return Strings.HubiC.Description; } } #endregion #region IDisposable implementation public void Dispose() { if (m_openstack != null) { m_openstack.Dispose(); m_openstack = null; } } #endregion } } diff --git a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs index 94a5dd16d..ba1bbd0d5 100644 --- a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs +++ b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs @@ -159,7 +159,7 @@ namespace Duplicati.Library.Backend get { return "jottacloud"; } } - public List List() + public IEnumerable List() { var doc = new System.Xml.XmlDocument(); try @@ -181,7 +181,6 @@ namespace Duplicati.Library.Backend // element must be a "folder", else it could also have been a "mountPoint" (which has a very similar structure). // We must check for "deleted" attribute, because files/folders which has it is deleted (attribute contains the timestamp of deletion) // so we treat them as non-existant here. - List files = new List(); var xRoot = doc.DocumentElement; if (xRoot.Attributes["deleted"] != null) { @@ -192,7 +191,7 @@ namespace Duplicati.Library.Backend // Subfolders are only listed with name. We can get a timestamp by sending a request for each folder, but that is probably not necessary? FileEntry fe = new FileEntry(xFolder.Attributes["name"].Value); fe.IsFolder = true; - files.Add(fe); + yield return fe; } foreach (System.Xml.XmlNode xFile in xRoot.SelectNodes("files/file[not(@deleted)]")) { @@ -216,11 +215,10 @@ namespace Duplicati.Library.Backend if (xNode == null || !DateTime.TryParseExact(xNode.InnerText, JFS_DATE_FORMAT, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal, out lastModified)) lastModified = new DateTime(); FileEntry fe = new FileEntry(name, size, lastModified, lastModified); - files.Add(fe); + yield return fe; } } } - return files; } public void Put(string remotename, string filename) @@ -263,7 +261,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/Mega/MegaBackend.cs b/Duplicati/Library/Backend/Mega/MegaBackend.cs index 732f0f040..e9261717f 100644 --- a/Duplicati/Library/Backend/Mega/MegaBackend.cs +++ b/Duplicati/Library/Backend/Mega/MegaBackend.cs @@ -170,16 +170,15 @@ namespace Duplicati.Library.Backend.Mega #region IBackend implementation - public List List() + public IEnumerable List() { if (m_filecache == null) ResetFileCache(); - return ( + return from n in m_filecache.Values let item = n.OrderByDescending(x => x.ModificationDate).First() - select (IFileEntry)new FileEntry(item.Name, item.Size, item.ModificationDate ?? new DateTime(0), item.ModificationDate ?? new DateTime(0)) - ).ToList(); + select new FileEntry(item.Name, item.Size, item.ModificationDate ?? new DateTime(0), item.ModificationDate ?? new DateTime(0)); } public void Put(string remotename, string filename) @@ -218,7 +217,7 @@ namespace Duplicati.Library.Backend.Mega public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/OneDrive/OneDrive.cs b/Duplicati/Library/Backend/OneDrive/OneDrive.cs index 2b85409bb..30c31742a 100644 --- a/Duplicati/Library/Backend/OneDrive/OneDrive.cs +++ b/Duplicati/Library/Backend/OneDrive/OneDrive.cs @@ -212,7 +212,7 @@ namespace Duplicati.Library.Backend if (string.IsNullOrWhiteSpace(id)) { // Refresh the list of files, just in case - List(); + foreach (IFileEntry file in List()) { /* We just need to iterate the whole list */ } m_fileidCache.TryGetValue(name, out id); if (string.IsNullOrWhiteSpace(id) && throwIfMissing) @@ -232,7 +232,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() @@ -250,13 +250,11 @@ namespace Duplicati.Library.Backend get { return "onedrive"; } } - public List List() + public IEnumerable List() { int offset = 0; int count = FILE_LIST_PAGE_SIZE; - - var files = new List(); - + m_fileidCache.Clear(); while(count == FILE_LIST_PAGE_SIZE) @@ -273,7 +271,7 @@ namespace Duplicati.Library.Backend var fe = new FileEntry(r.name, r.size.Value, r.updated_time.Value, r.updated_time.Value); fe.IsFolder = string.Equals(r.type, "folder", StringComparison.OrdinalIgnoreCase); - files.Add(fe); + yield return fe; } } else @@ -282,10 +280,7 @@ namespace Duplicati.Library.Backend } offset += count; - } - - return files; } public void Put(string remotename, string filename) diff --git a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs index 91b68f1cc..865306eb6 100644 --- a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs +++ b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs @@ -305,43 +305,15 @@ namespace Duplicati.Library.Backend.OpenStack } #endregion #region IBackend implementation - public List List() + public IEnumerable List() { - var res = new List(); var plainurl = JoinUrls(SimpleStorageEndPoint, m_container) + string.Format("?format=json&delimiter=/&limit={0}", PAGE_LIMIT); if (!string.IsNullOrEmpty(m_prefix)) plainurl += "&prefix=" + Library.Utility.Uri.UrlEncode(m_prefix); - - var url = plainurl; - + try { - while(true) - { - var req = m_helper.CreateRequest(url); - req.Accept = "application/json"; - - var items = m_helper.ReadJSONResponse(req); - foreach(var n in items) - { - var name = n.name; - if (name.StartsWith(m_prefix)) - name = name.Substring(m_prefix.Length); - - if (n.bytes == null) - res.Add(new FileEntry(name)); - else if (n.last_modified == null) - res.Add(new FileEntry(name, n.bytes.Value)); - else - res.Add(new FileEntry(name, n.bytes.Value, n.last_modified.Value, n.last_modified.Value)); - } - - if (items.Length != PAGE_LIMIT) - return res; - - // Prepare next listing entry - url = plainurl + string.Format("&marker={0}", Library.Utility.Uri.UrlEncode(items.Last().name)); - } + return ListWithoutExceptionCatch(plainurl); } catch(WebException wex) { @@ -351,6 +323,39 @@ namespace Duplicati.Library.Backend.OpenStack throw; } } + + private IEnumerable ListWithoutExceptionCatch(string plainurl) + { + var url = plainurl; + + while (true) + { + var req = m_helper.CreateRequest(url); + req.Accept = "application/json"; + + var items = m_helper.ReadJSONResponse(req); + foreach (var n in items) + { + var name = n.name; + if (name.StartsWith(m_prefix)) + name = name.Substring(m_prefix.Length); + + if (n.bytes == null) + yield return new FileEntry(name); + else if (n.last_modified == null) + yield return new FileEntry(name, n.bytes.Value); + else + yield return new FileEntry(name, n.bytes.Value, n.last_modified.Value, n.last_modified.Value); + } + + if (items.Length != PAGE_LIMIT) + yield break; + + // Prepare next listing entry + url = plainurl + string.Format("&marker={0}", Library.Utility.Uri.UrlEncode(items.Last().name)); + } + } + public void Put(string remotename, string filename) { using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) @@ -369,7 +374,7 @@ namespace Duplicati.Library.Backend.OpenStack } public void Test() { - List(); + this.TestList(); } public void CreateFolder() { diff --git a/Duplicati/Library/Backend/S3/S3Backend.cs b/Duplicati/Library/Backend/S3/S3Backend.cs index 861a6dd69..c287fe491 100644 --- a/Duplicati/Library/Backend/S3/S3Backend.cs +++ b/Duplicati/Library/Backend/S3/S3Backend.cs @@ -291,20 +291,11 @@ namespace Duplicati.Library.Backend get { return true; } } - public List List() + public IEnumerable List() { try { - List lst = Connection.ListBucket(m_bucket, m_prefix); - for (int i = 0; i < lst.Count; i++) - { - ((FileEntry)lst[i]).Name = lst[i].Name.Substring(m_prefix.Length); - - //Fix for a bug in Duplicati 1.0 beta 3 and earlier, where filenames are incorrectly prefixed with a slash - if (lst[i].Name.StartsWith("/") && !m_prefix.StartsWith("/")) - ((FileEntry)lst[i]).Name = lst[i].Name.Substring(1); - } - return lst; + return ListWithouExceptionCatch(); } catch (Exception ex) { @@ -317,6 +308,20 @@ namespace Duplicati.Library.Backend } } + private IEnumerable ListWithouExceptionCatch() + { + foreach (IFileEntry file in Connection.ListBucket(m_bucket, m_prefix)) + { + ((FileEntry)file).Name = file.Name.Substring(m_prefix.Length); + + //Fix for a bug in Duplicati 1.0 beta 3 and earlier, where filenames are incorrectly prefixed with a slash + if (file.Name.StartsWith("/") && !m_prefix.StartsWith("/")) + ((FileEntry)file).Name = file.Name.Substring(1); + + yield return file; + } + } + public void Put(string remotename, string localname) { using (System.IO.FileStream fs = System.IO.File.Open(localname, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read)) @@ -429,7 +434,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() diff --git a/Duplicati/Library/Backend/S3/S3Wrapper.cs b/Duplicati/Library/Backend/S3/S3Wrapper.cs index cbf1f543b..667d6f5f9 100644 --- a/Duplicati/Library/Backend/S3/S3Wrapper.cs +++ b/Duplicati/Library/Backend/S3/S3Wrapper.cs @@ -135,12 +135,15 @@ namespace Duplicati.Library.Backend m_client.DeleteObject(objectDeleteRequest); } - public virtual List ListBucket(string bucketName, string prefix) + public virtual IEnumerable ListBucket(string bucketName, string prefix) { bool isTruncated = true; string filename = null; - List files = new List(); + //TODO: Figure out if this is the case with AWSSDK too + //Unfortunately S3 sometimes reports duplicate values when requesting more than one page of results + //So, track the files that have already been returned and skip any duplicates. + HashSet alreadyReturned = new HashSet(); //We truncate after ITEM_LIST_LIMIT elements, and then repeat while (isTruncated) @@ -161,29 +164,17 @@ namespace Duplicati.Library.Backend foreach (S3Object obj in listResponse.S3Objects) { - files.Add(new FileEntry( - obj.Key, - obj.Size, - obj.LastModified, - obj.LastModified - )); - + if (alreadyReturned.Add(obj.Key)) + { + yield return new FileEntry( + obj.Key, + obj.Size, + obj.LastModified, + obj.LastModified + ); + } } } - - //TODO: Figure out if this is the case with AWSSDK too - //Unfortunately S3 sometimes reports duplicate values when requesting more than one page of results - Dictionary tmp = new Dictionary(); - for (int i = 0; i < files.Count; i++) - if (tmp.ContainsKey(files[i].Name)) - { - files.RemoveAt(i); - i--; - } - else - tmp.Add(files[i].Name, null); - - return files; } public void RenameFile(string bucketName, string source, string target) diff --git a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs index 503ea4d80..1131c521f 100644 --- a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs +++ b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs @@ -106,7 +106,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() @@ -313,10 +313,8 @@ namespace Duplicati.Library.Backend } } - public List List() + public IEnumerable List() { - var files = new List(); - string path = "."; CreateConnection(); @@ -324,9 +322,7 @@ namespace Duplicati.Library.Backend foreach (Renci.SshNet.Sftp.SftpFile ls in m_con.ListDirectory(path)) if (ls.Name.ToString() != "." && ls.Name.ToString() != "..") - files.Add(new FileEntry(ls.Name.ToString(), ls.Length, ls.LastAccessTime, ls.LastWriteTime) { IsFolder = ls.Attributes.IsDirectory }); - - return files; + yield return new FileEntry(ls.Name.ToString(), ls.Length, ls.LastAccessTime, ls.LastWriteTime) { IsFolder = ls.Attributes.IsDirectory }; } public static Renci.SshNet.PrivateKeyFile ValidateKeyFile(string filename, string password) diff --git a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs index 4793cf9a1..8e837e39f 100644 --- a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs +++ b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs @@ -393,8 +393,8 @@ namespace Duplicati.Library.Backend testContextForWeb(ctx, true); } - public List List() { return doList(false); } - private List doList(bool useNewContext) + public IEnumerable List() { return doList(false); } + private IEnumerable doList(bool useNewContext) { SP.ClientContext ctx = getSpClientContext(useNewContext); try @@ -407,20 +407,7 @@ namespace Duplicati.Library.Backend if (!remoteFolder.Exists) throw new Interface.FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); - List files = new List(remoteFolder.Folders.Count + remoteFolder.Files.Count); - foreach (var f in remoteFolder.Folders.Where(ff => ff.Exists)) - { - FileEntry fe = new FileEntry(f.Name, -1, f.TimeLastModified, f.TimeLastModified); // f.TimeCreated - fe.IsFolder = true; - files.Add(fe); - } - foreach (var f in remoteFolder.Files.Where(ff => ff.Exists)) - { - FileEntry fe = new FileEntry(f.Name, f.Length, f.TimeLastModified, f.TimeLastModified); // f.TimeCreated - fe.IsFolder = false; - files.Add(fe); - } - return files; + return doListWithoutExceptionCatch(remoteFolder); } catch (ServerException) { throw; /* rethrow if Server answered */ } catch (Interface.FileMissingException) { throw; } @@ -429,6 +416,22 @@ namespace Duplicati.Library.Backend finally { } } + private IEnumerable doListWithoutExceptionCatch(SP.Folder remoteFolder) + { + foreach (var f in remoteFolder.Folders.Where(ff => ff.Exists)) + { + FileEntry fe = new FileEntry(f.Name, -1, f.TimeLastModified, f.TimeLastModified); // f.TimeCreated + fe.IsFolder = true; + yield return fe; + } + foreach (var f in remoteFolder.Files.Where(ff => ff.Exists)) + { + FileEntry fe = new FileEntry(f.Name, f.Length, f.TimeLastModified, f.TimeLastModified); // f.TimeCreated + fe.IsFolder = false; + yield return fe; + } + } + public void Get(string remotename, string filename) { using (System.IO.FileStream fs = System.IO.File.Create(filename)) diff --git a/Duplicati/Library/Backend/Sia/Sia.cs b/Duplicati/Library/Backend/Sia/Sia.cs index a0ff731c0..2899d75aa 100644 --- a/Duplicati/Library/Backend/Sia/Sia.cs +++ b/Duplicati/Library/Backend/Sia/Sia.cs @@ -258,7 +258,7 @@ namespace Duplicati.Library.Backend.Sia public void Test() { - List(); + this.TestList(); } public void CreateFolder() @@ -277,15 +277,23 @@ namespace Duplicati.Library.Backend.Sia } - public List List() + public IEnumerable List() { - var files = new List(); try { SiaFileList fl = GetFiles(); - if (fl.Files == null) - return files; + return ListWithoutExceptionCatch(fl); + } + catch (System.Net.WebException wex) + { + throw new Exception("failed to call /renter/files "+wex.Message); + } + } + private IEnumerable ListWithoutExceptionCatch(SiaFileList fl) + { + if (fl.Files != null) + { foreach (var f in fl.Files) { // Sia returns a complete file list, but we're only interested in files that are @@ -295,16 +303,10 @@ namespace Duplicati.Library.Backend.Sia FileEntry fe = new FileEntry(f.Siapath.Substring(m_targetpath.Length + 1)); fe.Size = f.Filesize; fe.IsFolder = false; - files.Add(fe); + yield return fe; } } } - catch (System.Net.WebException wex) - { - throw new Exception("failed to call /renter/files "+wex.Message); - } - - return files; } public void Put(string remotename, string filename) diff --git a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs index 5d09b7c7a..2c78d77a9 100644 --- a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs +++ b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs @@ -123,7 +123,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() @@ -145,7 +145,7 @@ namespace Duplicati.Library.Backend get { return "tahoe"; } } - public List List() + public IEnumerable List() { TahoeEl data; @@ -184,7 +184,6 @@ namespace Duplicati.Library.Backend if (data == null || data.node == null || data.nodetype != "dirnode") throw new Exception("Invalid folder listing response"); - var files = new List(); foreach (var e in data.node.children) { if (e.Value == null || e.Value.node == null) @@ -205,10 +204,8 @@ namespace Duplicati.Library.Backend if (isFile) fe.Size = e.Value.node.size; - files.Add(fe); + yield return fe; } - - return files; } public void Put(string remotename, string filename) diff --git a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs index 6662755f8..d35815f16 100644 --- a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs +++ b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs @@ -125,126 +125,129 @@ namespace Duplicati.Library.Backend get { return "webdav"; } } - public List List() + public IEnumerable List() { try { - var req = CreateRequest(""); + return this.ListWithouExceptionCatch(); + } + catch (System.Net.WebException wex) + { + if (wex.Response as System.Net.HttpWebResponse != null && + ((wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.NotFound || (wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.Conflict)) + throw new Interface.FolderMissingException(Strings.WEBDAV.MissingFolderError(m_path, wex.Message), wex); - req.Method = "PROPFIND"; - req.Headers.Add("Depth", "1"); - req.ContentType = "text/xml"; - req.ContentLength = PROPFIND_BODY.Length; + if (wex.Response as System.Net.HttpWebResponse != null && (wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.MethodNotAllowed) + throw new UserInformationException(Strings.WEBDAV.MethodNotAllowedError((wex.Response as System.Net.HttpWebResponse).StatusCode), wex); - var areq = new Utility.AsyncHttpRequest(req); - using (System.IO.Stream s = areq.GetRequestStream()) - s.Write(PROPFIND_BODY, 0, PROPFIND_BODY.Length); - - var doc = new System.Xml.XmlDocument(); - using (var resp = (System.Net.HttpWebResponse)areq.GetResponse()) - { - int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); + throw; + } + } - if (!string.IsNullOrEmpty(m_debugPropfindFile)) - { - using (var rs = areq.GetResponseStream()) - using (var fs = new System.IO.FileStream(m_debugPropfindFile, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) - Utility.Utility.CopyStream(rs, fs, false, m_copybuffer); + private IEnumerable ListWithouExceptionCatch() + { + var req = CreateRequest(""); - doc.Load(m_debugPropfindFile); - } - else - { - using (var rs = areq.GetResponseStream()) - doc.Load(rs); - } - } + req.Method = "PROPFIND"; + req.Headers.Add("Depth", "1"); + req.ContentType = "text/xml"; + req.ContentLength = PROPFIND_BODY.Length; - System.Xml.XmlNamespaceManager nm = new System.Xml.XmlNamespaceManager(doc.NameTable); - nm.AddNamespace("D", "DAV:"); + var areq = new Utility.AsyncHttpRequest(req); + using (System.IO.Stream s = areq.GetRequestStream()) + s.Write(PROPFIND_BODY, 0, PROPFIND_BODY.Length); - List files = new List(); - m_filenamelist = new List(); + var doc = new System.Xml.XmlDocument(); + using (var resp = (System.Net.HttpWebResponse)areq.GetResponse()) + { + int code = (int)resp.StatusCode; + if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically + throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); - foreach (System.Xml.XmlNode n in doc.SelectNodes("D:multistatus/D:response/D:href", nm)) + if (!string.IsNullOrEmpty(m_debugPropfindFile)) { - //IIS uses %20 for spaces and %2B for + - //Apache uses %20 for spaces and + for + - string name = Library.Utility.Uri.UrlDecode(n.InnerText.Replace("+", "%2B")); - - string cmp_path; - - //TODO: This list is getting ridiculous, should change to regexps - - if (name.StartsWith(m_url)) - cmp_path = m_url; - else if (name.StartsWith(m_rawurl)) - cmp_path = m_rawurl; - else if (name.StartsWith(m_rawurlPort)) - cmp_path = m_rawurlPort; - else if (name.StartsWith(m_path)) - cmp_path = m_path; - else if (name.StartsWith("/" + m_path)) - cmp_path = "/" + m_path; - else if (name.StartsWith(m_sanitizedUrl)) - cmp_path = m_sanitizedUrl; - else if (name.StartsWith(m_reverseProtocolUrl)) - cmp_path = m_reverseProtocolUrl; - else - continue; - - if (name.Length <= cmp_path.Length) - continue; - - name = name.Substring(cmp_path.Length); - - long size = -1; - DateTime lastAccess = new DateTime(); - DateTime lastModified = new DateTime(); - bool isCollection = false; - - System.Xml.XmlNode stat = n.ParentNode.SelectSingleNode("D:propstat/D:prop", nm); - if (stat != null) - { - System.Xml.XmlNode s = stat.SelectSingleNode("D:getcontentlength", nm); - if (s != null) - size = long.Parse(s.InnerText); - s = stat.SelectSingleNode("D:getlastmodified", nm); - if (s != null) - try - { - //Not important if this succeeds - lastAccess = lastModified = DateTime.Parse(s.InnerText, System.Globalization.CultureInfo.InvariantCulture); - } - catch { } - - s = stat.SelectSingleNode("D:iscollection", nm); - if (s != null) - isCollection = s.InnerText.Trim() == "1"; - else - isCollection = (stat.SelectSingleNode("D:resourcetype/D:collection", nm) != null); - } - - FileEntry fe = new FileEntry(name, size, lastAccess, lastModified); - fe.IsFolder = isCollection; - files.Add(fe); - m_filenamelist.Add(name); - } + using (var rs = areq.GetResponseStream()) + using (var fs = new System.IO.FileStream(m_debugPropfindFile, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) + Utility.Utility.CopyStream(rs, fs, false, m_copybuffer); - return files; + doc.Load(m_debugPropfindFile); + } + else + { + using (var rs = areq.GetResponseStream()) + doc.Load(rs); + } } - catch (System.Net.WebException wex) + + System.Xml.XmlNamespaceManager nm = new System.Xml.XmlNamespaceManager(doc.NameTable); + nm.AddNamespace("D", "DAV:"); + + List files = new List(); + m_filenamelist = new List(); + + foreach (System.Xml.XmlNode n in doc.SelectNodes("D:multistatus/D:response/D:href", nm)) { - if (wex.Response as System.Net.HttpWebResponse != null && - ((wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.NotFound || (wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.Conflict)) - throw new Interface.FolderMissingException(Strings.WEBDAV.MissingFolderError(m_path, wex.Message), wex); + //IIS uses %20 for spaces and %2B for + + //Apache uses %20 for spaces and + for + + string name = Library.Utility.Uri.UrlDecode(n.InnerText.Replace("+", "%2B")); + + string cmp_path; + + //TODO: This list is getting ridiculous, should change to regexps + + if (name.StartsWith(m_url)) + cmp_path = m_url; + else if (name.StartsWith(m_rawurl)) + cmp_path = m_rawurl; + else if (name.StartsWith(m_rawurlPort)) + cmp_path = m_rawurlPort; + else if (name.StartsWith(m_path)) + cmp_path = m_path; + else if (name.StartsWith("/" + m_path)) + cmp_path = "/" + m_path; + else if (name.StartsWith(m_sanitizedUrl)) + cmp_path = m_sanitizedUrl; + else if (name.StartsWith(m_reverseProtocolUrl)) + cmp_path = m_reverseProtocolUrl; + else + continue; - if (wex.Response as System.Net.HttpWebResponse != null && (wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.MethodNotAllowed) - throw new UserInformationException(Strings.WEBDAV.MethodNotAllowedError((wex.Response as System.Net.HttpWebResponse).StatusCode), wex); + if (name.Length <= cmp_path.Length) + continue; + + name = name.Substring(cmp_path.Length); + + long size = -1; + DateTime lastAccess = new DateTime(); + DateTime lastModified = new DateTime(); + bool isCollection = false; + + System.Xml.XmlNode stat = n.ParentNode.SelectSingleNode("D:propstat/D:prop", nm); + if (stat != null) + { + System.Xml.XmlNode s = stat.SelectSingleNode("D:getcontentlength", nm); + if (s != null) + size = long.Parse(s.InnerText); + s = stat.SelectSingleNode("D:getlastmodified", nm); + if (s != null) + try + { + //Not important if this succeeds + lastAccess = lastModified = DateTime.Parse(s.InnerText, System.Globalization.CultureInfo.InvariantCulture); + } + catch { } + + s = stat.SelectSingleNode("D:iscollection", nm); + if (s != null) + isCollection = s.InnerText.Trim() == "1"; + else + isCollection = (stat.SelectSingleNode("D:resourcetype/D:collection", nm) != null); + } - throw; + FileEntry fe = new FileEntry(name, size, lastAccess, lastModified); + fe.IsFolder = isCollection; + m_filenamelist.Add(name); + yield return fe; } } @@ -308,7 +311,7 @@ namespace Duplicati.Library.Backend public void Test() { - List(); + this.TestList(); } public void CreateFolder() -- cgit v1.2.3