/wasDAVClient/Client.cs |
@@ -14,11 +14,6 @@ |
{ |
public class Client : IClient |
{ |
private static readonly HttpMethod PropFind = new HttpMethod("PROPFIND"); |
private static readonly HttpMethod MoveMethod = new HttpMethod("MOVE"); |
|
private static readonly HttpMethod MkCol = new HttpMethod(WebRequestMethods.Http.MkCol); |
|
private const int HttpStatusCode_MultiStatus = 207; |
|
// http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND |
@@ -38,28 +33,54 @@ |
//" </prop> " + |
"</propfind>"; |
|
private static readonly HttpMethod PropFind = new HttpMethod("PROPFIND"); |
private static readonly HttpMethod MoveMethod = new HttpMethod("MOVE"); |
|
private static readonly HttpMethod MkCol = new HttpMethod(WebRequestMethods.Http.MkCol); |
|
private static readonly string AssemblyVersion = typeof(IClient).Assembly.GetName().Version.ToString(); |
|
private readonly HttpClient _client; |
private readonly HttpClient _uploadClient; |
private string _server; |
private string _basePath = "/"; |
|
private string _encodedBasePath; |
private string _server; |
|
|
public Client(NetworkCredential credential = null) |
{ |
var handler = new HttpClientHandler(); |
|
if (handler.SupportsProxy) |
handler.Proxy = WebRequest.DefaultWebProxy; |
|
if (handler.SupportsAutomaticDecompression) |
handler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; |
|
if (credential != null) |
{ |
handler.Credentials = credential; |
handler.PreAuthenticate = true; |
} |
|
_client = new HttpClient(handler); |
_client.Timeout = TimeSpan.FromSeconds(Timeout); |
_client.DefaultRequestHeaders.ExpectContinue = false; |
|
_uploadClient = new HttpClient(handler); |
_uploadClient.Timeout = TimeSpan.FromSeconds(Timeout); |
_uploadClient.DefaultRequestHeaders.ExpectContinue = false; |
} |
|
#region WebDAV connection parameters |
|
/// <summary> |
/// Specify the WebDAV hostname (required). |
/// Specify the WebDAV hostname (required). |
/// </summary> |
public string Server |
{ |
get |
{ |
return _server; |
} |
get { return _server; } |
set |
{ |
value = value.TrimEnd('/'); |
@@ -68,14 +89,11 @@ |
} |
|
/// <summary> |
/// Specify the path of a WebDAV directory to use as 'root' (default: /) |
/// Specify the path of a WebDAV directory to use as 'root' (default: /) |
/// </summary> |
public string BasePath |
{ |
get |
{ |
return _basePath; |
} |
get { return _basePath; } |
set |
{ |
value = value.Trim('/'); |
@@ -87,61 +105,31 @@ |
} |
|
/// <summary> |
/// Specify an port (default: null = auto-detect) |
/// Specify an port (default: null = auto-detect) |
/// </summary> |
public int? Port |
{ |
get; set; |
} |
public int? Port { get; set; } |
|
/// <summary> |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// </summary> |
public string UserAgent |
{ |
get; set; |
} |
public string UserAgent { get; set; } |
|
/// <summary> |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// </summary> |
public string UserAgentVersion |
{ |
get; set; |
} |
public string UserAgentVersion { get; set; } |
|
/// <summary> |
/// The HTTP request timeout in seconds. |
/// </summary> |
public int Timeout { get; set; } = 60; |
|
#endregion |
|
|
public Client(NetworkCredential credential = null, TimeSpan? timeout = null, IWebProxy proxy = null) |
{ |
var handler = new HttpClientHandler(); |
if (proxy != null && handler.SupportsProxy) |
handler.Proxy = proxy; |
if (handler.SupportsAutomaticDecompression) |
handler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; |
if (credential != null) |
{ |
handler.Credentials = credential; |
handler.PreAuthenticate = true; |
} |
|
_client = new HttpClient(handler); |
_client.DefaultRequestHeaders.ExpectContinue = false; |
|
if (timeout != null) |
{ |
_uploadClient = new HttpClient(handler); |
_uploadClient.DefaultRequestHeaders.ExpectContinue = false; |
_uploadClient.Timeout = timeout.Value; |
} |
|
} |
|
#region WebDAV operations |
|
/// <summary> |
/// List all files present on the server. |
/// List all files present on the server. |
/// </summary> |
/// <param name="path">List only files in this path</param> |
/// <param name="depth">Recursion depth</param> |
@@ -157,17 +145,19 @@ |
headers.Add("Depth", depth.ToString()); |
} |
|
|
HttpResponseMessage response = null; |
|
try |
{ |
response = await HttpRequest(listUri.Uri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)).ConfigureAwait(false); |
response = |
await |
HttpRequest(listUri.Uri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)) |
.ConfigureAwait(false); |
|
if (response.StatusCode != HttpStatusCode.OK && |
(int)response.StatusCode != HttpStatusCode_MultiStatus) |
(int) response.StatusCode != HttpStatusCode_MultiStatus) |
{ |
throw new wasDAVException((int)response.StatusCode, "Failed retrieving items in folder."); |
throw new wasDAVException((int) response.StatusCode, "Failed retrieving items in folder."); |
} |
|
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) |
@@ -201,7 +191,6 @@ |
} |
return result; |
} |
|
} |
finally |
{ |
@@ -211,7 +200,7 @@ |
} |
|
/// <summary> |
/// List all files present on the server. |
/// List all files present on the server. |
/// </summary> |
/// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
public async Task<Item> GetFolder(string path = "/") |
@@ -221,7 +210,7 @@ |
} |
|
/// <summary> |
/// List all files present on the server. |
/// List all files present on the server. |
/// </summary> |
/// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
public async Task<Item> GetFile(string path = "/") |
@@ -232,12 +221,11 @@ |
|
|
/// <summary> |
/// List all files present on the server. |
/// List all files present on the server. |
/// </summary> |
/// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
private async Task<Item> Get(Uri listUri, string path) |
{ |
|
// Depth header: http://webdav.org/specs/rfc4918.html#rfc.section.9.1.4 |
IDictionary<string, string> headers = new Dictionary<string, string>(); |
headers.Add("Depth", "0"); |
@@ -247,12 +235,16 @@ |
|
try |
{ |
response = await HttpRequest(listUri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)).ConfigureAwait(false); |
response = |
await |
HttpRequest(listUri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)) |
.ConfigureAwait(false); |
|
if (response.StatusCode != HttpStatusCode.OK && |
(int)response.StatusCode != HttpStatusCode_MultiStatus) |
(int) response.StatusCode != HttpStatusCode_MultiStatus) |
{ |
throw new wasDAVException((int)response.StatusCode, string.Format("Failed retrieving item/folder (Status Code: {0})", response.StatusCode)); |
throw new wasDAVException((int) response.StatusCode, |
string.Format("Failed retrieving item/folder (Status Code: {0})", response.StatusCode)); |
} |
|
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) |
@@ -275,7 +267,7 @@ |
} |
|
/// <summary> |
/// Download a file from the server |
/// Download a file from the server |
/// </summary> |
/// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
public async Task<Stream> Download(string remoteFilePath) |
@@ -283,17 +275,17 @@ |
// Should not have a trailing slash. |
var downloadUri = await GetServerUrl(remoteFilePath, false).ConfigureAwait(false); |
|
var dictionary = new Dictionary<string, string> { { "translate", "f" } }; |
var dictionary = new Dictionary<string, string> {{"translate", "f"}}; |
var response = await HttpRequest(downloadUri.Uri, HttpMethod.Get, dictionary).ConfigureAwait(false); |
if (response.StatusCode != HttpStatusCode.OK) |
{ |
throw new wasDAVException((int)response.StatusCode, "Failed retrieving file."); |
throw new wasDAVException((int) response.StatusCode, "Failed retrieving file."); |
} |
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); |
} |
|
/// <summary> |
/// Download a file from the server |
/// Download a file from the server |
/// </summary> |
/// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
/// <param name="content"></param> |
@@ -301,7 +293,8 @@ |
public async Task<bool> Upload(string remoteFilePath, Stream content, string name) |
{ |
// Should not have a trailing slash. |
var uploadUri = await GetServerUrl(remoteFilePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
var uploadUri = |
await GetServerUrl(remoteFilePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
|
HttpResponseMessage response = null; |
|
@@ -313,7 +306,7 @@ |
response.StatusCode != HttpStatusCode.NoContent && |
response.StatusCode != HttpStatusCode.Created) |
{ |
throw new wasDAVException((int)response.StatusCode, "Failed uploading file."); |
throw new wasDAVException((int) response.StatusCode, "Failed uploading file."); |
} |
|
return response.IsSuccessStatusCode; |
@@ -323,12 +316,11 @@ |
if (response != null) |
response.Dispose(); |
} |
|
} |
|
|
/// <summary> |
/// Create a directory on the server |
/// Create a directory on the server |
/// </summary> |
/// <param name="remotePath">Destination path of the directory on the server</param> |
/// <param name="name"></param> |
@@ -335,7 +327,8 @@ |
public async Task<bool> CreateDir(string remotePath, string name) |
{ |
// Should not have a trailing slash. |
var dirUri = await GetServerUrl(remotePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
var dirUri = |
await GetServerUrl(remotePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
|
HttpResponseMessage response = null; |
|
@@ -344,13 +337,13 @@ |
response = await HttpRequest(dirUri.Uri, MkCol).ConfigureAwait(false); |
|
if (response.StatusCode == HttpStatusCode.Conflict) |
throw new wasDAVConflictException((int)response.StatusCode, "Failed creating folder."); |
throw new wasDAVConflictException((int) response.StatusCode, "Failed creating folder."); |
|
if (response.StatusCode != HttpStatusCode.OK && |
response.StatusCode != HttpStatusCode.NoContent && |
response.StatusCode != HttpStatusCode.Created) |
{ |
throw new wasDAVException((int)response.StatusCode, "Failed creating folder."); |
throw new wasDAVException((int) response.StatusCode, "Failed creating folder."); |
} |
|
return response.IsSuccessStatusCode; |
@@ -382,7 +375,7 @@ |
if (response.StatusCode != HttpStatusCode.OK && |
response.StatusCode != HttpStatusCode.NoContent) |
{ |
throw new wasDAVException((int)response.StatusCode, "Failed deleting item."); |
throw new wasDAVException((int) response.StatusCode, "Failed deleting item."); |
} |
} |
|
@@ -393,7 +386,6 @@ |
var dstUri = await GetServerUrl(dstFolderPath, true).ConfigureAwait(false); |
|
return await Move(srcUri.Uri, dstUri.Uri).ConfigureAwait(false); |
|
} |
|
public async Task<bool> MoveFile(string srcFilePath, string dstFilePath) |
@@ -413,12 +405,15 @@ |
IDictionary<string, string> headers = new Dictionary<string, string>(); |
headers.Add("Destination", dstUri.ToString()); |
|
var response = await HttpRequest(srcUri, MoveMethod, headers, Encoding.UTF8.GetBytes(requestContent)).ConfigureAwait(false); |
var response = |
await |
HttpRequest(srcUri, MoveMethod, headers, Encoding.UTF8.GetBytes(requestContent)) |
.ConfigureAwait(false); |
|
if (response.StatusCode != HttpStatusCode.OK && |
response.StatusCode != HttpStatusCode.Created) |
{ |
throw new wasDAVException((int)response.StatusCode, "Failed moving file."); |
throw new wasDAVException((int) response.StatusCode, "Failed moving file."); |
} |
|
return response.IsSuccessStatusCode; |
@@ -429,13 +424,14 @@ |
#region Server communication |
|
/// <summary> |
/// Perform the WebDAV call and fire the callback when finished. |
/// Perform the WebDAV call and fire the callback when finished. |
/// </summary> |
/// <param name="uri"></param> |
/// <param name="method"></param> |
/// <param name="headers"></param> |
/// <param name="content"></param> |
private async Task<HttpResponseMessage> HttpRequest(Uri uri, HttpMethod method, IDictionary<string, string> headers = null, byte[] content = null) |
private async Task<HttpResponseMessage> HttpRequest(Uri uri, HttpMethod method, |
IDictionary<string, string> headers = null, byte[] content = null) |
{ |
using (var request = new HttpRequestMessage(method, uri)) |
{ |
@@ -447,7 +443,7 @@ |
|
if (headers != null) |
{ |
foreach (string key in headers.Keys) |
foreach (var key in headers.Keys) |
{ |
request.Headers.Add(key, headers[key]); |
} |
@@ -465,13 +461,14 @@ |
} |
|
/// <summary> |
/// Perform the WebDAV call and fire the callback when finished. |
/// Perform the WebDAV call and fire the callback when finished. |
/// </summary> |
/// <param name="uri"></param> |
/// <param name="headers"></param> |
/// <param name="method"></param> |
/// <param name="content"></param> |
private async Task<HttpResponseMessage> HttpUploadRequest(Uri uri, HttpMethod method, Stream content, IDictionary<string, string> headers = null) |
private async Task<HttpResponseMessage> HttpUploadRequest(Uri uri, HttpMethod method, Stream content, |
IDictionary<string, string> headers = null) |
{ |
using (var request = new HttpRequestMessage(method, uri)) |
{ |
@@ -483,7 +480,7 @@ |
|
if (headers != null) |
{ |
foreach (string key in headers.Keys) |
foreach (var key in headers.Keys) |
{ |
request.Headers.Add(key, headers[key]); |
} |
@@ -501,11 +498,11 @@ |
} |
|
/// <summary> |
/// Try to create an Uri with kind UriKind.Absolute |
/// This particular implementation also works on Mono/Linux |
/// It seems that on Mono it is expected behaviour that uris |
/// of kind /a/b are indeed absolute uris since it referes to a file in /a/b. |
/// https://bugzilla.xamarin.com/show_bug.cgi?id=30854 |
/// Try to create an Uri with kind UriKind.Absolute |
/// This particular implementation also works on Mono/Linux |
/// It seems that on Mono it is expected behaviour that uris |
/// of kind /a/b are indeed absolute uris since it referes to a file in /a/b. |
/// https://bugzilla.xamarin.com/show_bug.cgi?id=30854 |
/// </summary> |
/// <param name="uriString"></param> |
/// <param name="uriResult"></param> |
@@ -520,7 +517,7 @@ |
// Resolve the base path on the server |
if (_encodedBasePath == null) |
{ |
var baseUri = new UriBuilder(_server) { Path = _basePath }; |
var baseUri = new UriBuilder(_server) {Path = _basePath}; |
var root = await Get(baseUri.Uri, null).ConfigureAwait(false); |
|
_encodedBasePath = root.Href; |
@@ -538,7 +535,7 @@ |
} |
|
// Otherwise, use the resolved base path relatively to the server |
var baseUri = new UriBuilder(_server) { Path = _encodedBasePath }; |
var baseUri = new UriBuilder(_server) {Path = _encodedBasePath}; |
return baseUri; |
} |
|
@@ -585,4 +582,4 @@ |
|
#endregion |
} |
} |
} |
/wasDAVClient/IClient.cs |
@@ -8,32 +8,33 @@ |
public interface IClient |
{ |
/// <summary> |
/// Specify the WebDAV hostname (required). |
/// Specify the WebDAV hostname (required). |
/// </summary> |
string Server { get; set; } |
|
/// <summary> |
/// Specify the path of a WebDAV directory to use as 'root' (default: /) |
/// Specify the path of a WebDAV directory to use as 'root' (default: /) |
/// </summary> |
string BasePath { get; set; } |
|
/// <summary> |
/// Specify an port (default: null = auto-detect) |
/// Specify an port (default: null = auto-detect) |
/// </summary> |
int? Port { get; set; } |
|
/// <summary> |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// </summary> |
string UserAgent { get; set; } |
|
/// <summary> |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// Specify the UserAgent (and UserAgent version) string to use in requests |
/// </summary> |
string UserAgentVersion { get; set; } |
|
|
/// <summary> |
/// List all files present on the server. |
/// List all files present on the server. |
/// </summary> |
/// <param name="path">List only files in this path</param> |
/// <param name="depth">Recursion depth</param> |
@@ -41,25 +42,25 @@ |
Task<IEnumerable<Item>> List(string path = "/", int? depth = 1); |
|
/// <summary> |
/// Get folder information from the server. |
/// Get folder information from the server. |
/// </summary> |
/// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
Task<Item> GetFolder(string path = "/"); |
|
/// <summary> |
/// Get file information from the server. |
/// Get file information from the server. |
/// </summary> |
/// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
Task<Item> GetFile(string path = "/"); |
|
/// <summary> |
/// Download a file from the server |
/// Download a file from the server |
/// </summary> |
/// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
Task<Stream> Download(string remoteFilePath); |
|
/// <summary> |
/// Download a file from the server |
/// Download a file from the server |
/// </summary> |
/// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
/// <param name="content"></param> |
@@ -67,7 +68,7 @@ |
Task<bool> Upload(string remoteFilePath, Stream content, string name); |
|
/// <summary> |
/// Create a directory on the server |
/// Create a directory on the server |
/// </summary> |
/// <param name="remotePath">Destination path of the directory on the server</param> |
/// <param name="name"></param> |
@@ -74,19 +75,19 @@ |
Task<bool> CreateDir(string remotePath, string name); |
|
/// <summary> |
/// Get folder information from the server. |
/// Get folder information from the server. |
/// </summary> |
/// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
Task DeleteFolder(string path = "/"); |
|
/// <summary> |
/// Get file information from the server. |
/// Get file information from the server. |
/// </summary> |
/// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
Task DeleteFile(string path = "/"); |
|
/// <summary> |
/// Move a folder on the server |
/// Move a folder on the server |
/// </summary> |
/// <param name="srcFolderPath">Source path of the folder on the server</param> |
/// <param name="dstFolderPath">Destination path of the folder on the server</param> |
@@ -93,7 +94,7 @@ |
Task<bool> MoveFolder(string srcFolderPath, string dstFolderPath); |
|
/// <summary> |
/// Move a file on the server |
/// Move a file on the server |
/// </summary> |
/// <param name="srcFilePath">Source path and filename of the file on the server</param> |
/// <param name="dstFilePath">Destination path and filename of the file on the server</param> |