wasDAVClient – Blame information for rev 4
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | using System; |
2 | using System.Collections.Generic; |
||
3 | using System.IO; |
||
4 | using System.Linq; |
||
5 | using System.Net; |
||
6 | using System.Net.Http; |
||
7 | using System.Net.Http.Headers; |
||
8 | using System.Text; |
||
9 | using System.Threading.Tasks; |
||
10 | using wasDAVClient.Helpers; |
||
11 | using wasDAVClient.Model; |
||
12 | |||
13 | namespace wasDAVClient |
||
14 | { |
||
4 | office | 15 | public class Client : IClient, IDisposable |
1 | office | 16 | { |
17 | private const int HttpStatusCode_MultiStatus = 207; |
||
18 | |||
19 | // http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND |
||
20 | private const string PropFindRequestContent = |
||
21 | "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + |
||
22 | "<propfind xmlns=\"DAV:\">" + |
||
23 | "<allprop/>" + |
||
24 | //" <propname/>" + |
||
25 | //" <prop>" + |
||
26 | //" <creationdate/>" + |
||
27 | //" <getlastmodified/>" + |
||
28 | //" <displayname/>" + |
||
29 | //" <getcontentlength/>" + |
||
30 | //" <getcontenttype/>" + |
||
31 | //" <getetag/>" + |
||
32 | //" <resourcetype/>" + |
||
33 | //" </prop> " + |
||
34 | "</propfind>"; |
||
35 | |||
3 | office | 36 | private static readonly HttpMethod PropFind = new HttpMethod("PROPFIND"); |
37 | private static readonly HttpMethod MoveMethod = new HttpMethod("MOVE"); |
||
38 | |||
39 | private static readonly HttpMethod MkCol = new HttpMethod(WebRequestMethods.Http.MkCol); |
||
40 | |||
1 | office | 41 | private static readonly string AssemblyVersion = typeof(IClient).Assembly.GetName().Version.ToString(); |
42 | |||
43 | private readonly HttpClient _client; |
||
44 | private readonly HttpClient _uploadClient; |
||
45 | private string _basePath = "/"; |
||
46 | |||
47 | private string _encodedBasePath; |
||
3 | office | 48 | private string _server; |
1 | office | 49 | |
50 | |||
3 | office | 51 | public Client(NetworkCredential credential = null) |
52 | { |
||
53 | var handler = new HttpClientHandler(); |
||
1 | office | 54 | |
3 | office | 55 | if (handler.SupportsProxy) |
56 | handler.Proxy = WebRequest.DefaultWebProxy; |
||
57 | |||
58 | if (handler.SupportsAutomaticDecompression) |
||
59 | handler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; |
||
60 | |||
61 | if (credential != null) |
||
62 | { |
||
63 | handler.Credentials = credential; |
||
64 | handler.PreAuthenticate = true; |
||
65 | } |
||
66 | |||
67 | _client = new HttpClient(handler); |
||
68 | _client.Timeout = TimeSpan.FromSeconds(Timeout); |
||
69 | _client.DefaultRequestHeaders.ExpectContinue = false; |
||
70 | |||
71 | _uploadClient = new HttpClient(handler); |
||
72 | _uploadClient.Timeout = TimeSpan.FromSeconds(Timeout); |
||
73 | _uploadClient.DefaultRequestHeaders.ExpectContinue = false; |
||
74 | } |
||
75 | |||
1 | office | 76 | #region WebDAV connection parameters |
77 | |||
78 | /// <summary> |
||
3 | office | 79 | /// Specify the WebDAV hostname (required). |
1 | office | 80 | /// </summary> |
81 | public string Server |
||
82 | { |
||
3 | office | 83 | get { return _server; } |
1 | office | 84 | set |
85 | { |
||
86 | value = value.TrimEnd('/'); |
||
87 | _server = value; |
||
88 | } |
||
89 | } |
||
90 | |||
91 | /// <summary> |
||
3 | office | 92 | /// Specify the path of a WebDAV directory to use as 'root' (default: /) |
1 | office | 93 | /// </summary> |
94 | public string BasePath |
||
95 | { |
||
3 | office | 96 | get { return _basePath; } |
1 | office | 97 | set |
98 | { |
||
99 | value = value.Trim('/'); |
||
100 | if (string.IsNullOrEmpty(value)) |
||
101 | _basePath = "/"; |
||
102 | else |
||
103 | _basePath = "/" + value + "/"; |
||
104 | } |
||
105 | } |
||
106 | |||
107 | /// <summary> |
||
3 | office | 108 | /// Specify an port (default: null = auto-detect) |
1 | office | 109 | /// </summary> |
3 | office | 110 | public int? Port { get; set; } |
1 | office | 111 | |
112 | /// <summary> |
||
3 | office | 113 | /// Specify the UserAgent (and UserAgent version) string to use in requests |
1 | office | 114 | /// </summary> |
3 | office | 115 | public string UserAgent { get; set; } |
1 | office | 116 | |
117 | /// <summary> |
||
3 | office | 118 | /// Specify the UserAgent (and UserAgent version) string to use in requests |
1 | office | 119 | /// </summary> |
3 | office | 120 | public string UserAgentVersion { get; set; } |
1 | office | 121 | |
3 | office | 122 | /// <summary> |
123 | /// The HTTP request timeout in seconds. |
||
124 | /// </summary> |
||
125 | public int Timeout { get; set; } = 60; |
||
126 | |||
1 | office | 127 | #endregion |
128 | |||
129 | #region WebDAV operations |
||
130 | |||
131 | /// <summary> |
||
3 | office | 132 | /// List all files present on the server. |
1 | office | 133 | /// </summary> |
134 | /// <param name="path">List only files in this path</param> |
||
135 | /// <param name="depth">Recursion depth</param> |
||
136 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
137 | public async Task<IEnumerable<Item>> List(string path = "/", int? depth = 1) |
||
138 | { |
||
139 | var listUri = await GetServerUrl(path, true).ConfigureAwait(false); |
||
140 | |||
141 | // Depth header: http://webdav.org/specs/rfc4918.html#rfc.section.9.1.4 |
||
142 | IDictionary<string, string> headers = new Dictionary<string, string>(); |
||
143 | if (depth != null) |
||
144 | { |
||
145 | headers.Add("Depth", depth.ToString()); |
||
146 | } |
||
147 | |||
148 | HttpResponseMessage response = null; |
||
149 | |||
150 | try |
||
151 | { |
||
3 | office | 152 | response = |
153 | await |
||
154 | HttpRequest(listUri.Uri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)) |
||
155 | .ConfigureAwait(false); |
||
1 | office | 156 | |
157 | if (response.StatusCode != HttpStatusCode.OK && |
||
3 | office | 158 | (int) response.StatusCode != HttpStatusCode_MultiStatus) |
1 | office | 159 | { |
3 | office | 160 | throw new wasDAVException((int) response.StatusCode, "Failed retrieving items in folder."); |
1 | office | 161 | } |
162 | |||
163 | using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) |
||
164 | { |
||
165 | var items = ResponseParser.ParseItems(stream); |
||
166 | |||
167 | if (items == null) |
||
168 | { |
||
169 | throw new wasDAVException("Failed deserializing data returned from server."); |
||
170 | } |
||
171 | |||
172 | var listUrl = listUri.ToString(); |
||
173 | |||
174 | var result = new List<Item>(items.Count()); |
||
175 | foreach (var item in items) |
||
176 | { |
||
177 | // If it's not a collection, add it to the result |
||
178 | if (!item.IsCollection) |
||
179 | { |
||
180 | result.Add(item); |
||
181 | } |
||
182 | else |
||
183 | { |
||
184 | // If it's not the requested parent folder, add it to the result |
||
185 | var fullHref = await GetServerUrl(item.Href, true).ConfigureAwait(false); |
||
186 | if (!string.Equals(fullHref.ToString(), listUrl, StringComparison.CurrentCultureIgnoreCase)) |
||
187 | { |
||
188 | result.Add(item); |
||
189 | } |
||
190 | } |
||
191 | } |
||
192 | return result; |
||
193 | } |
||
194 | } |
||
195 | finally |
||
196 | { |
||
197 | if (response != null) |
||
198 | response.Dispose(); |
||
199 | } |
||
200 | } |
||
201 | |||
202 | /// <summary> |
||
3 | office | 203 | /// List all files present on the server. |
1 | office | 204 | /// </summary> |
205 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
206 | public async Task<Item> GetFolder(string path = "/") |
||
207 | { |
||
208 | var listUri = await GetServerUrl(path, true).ConfigureAwait(false); |
||
209 | return await Get(listUri.Uri, path).ConfigureAwait(false); |
||
210 | } |
||
211 | |||
212 | /// <summary> |
||
3 | office | 213 | /// List all files present on the server. |
1 | office | 214 | /// </summary> |
215 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
216 | public async Task<Item> GetFile(string path = "/") |
||
217 | { |
||
218 | var listUri = await GetServerUrl(path, false).ConfigureAwait(false); |
||
219 | return await Get(listUri.Uri, path).ConfigureAwait(false); |
||
220 | } |
||
221 | |||
222 | |||
223 | /// <summary> |
||
3 | office | 224 | /// List all files present on the server. |
1 | office | 225 | /// </summary> |
226 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
227 | private async Task<Item> Get(Uri listUri, string path) |
||
228 | { |
||
229 | // Depth header: http://webdav.org/specs/rfc4918.html#rfc.section.9.1.4 |
||
230 | IDictionary<string, string> headers = new Dictionary<string, string>(); |
||
231 | headers.Add("Depth", "0"); |
||
232 | |||
233 | |||
234 | HttpResponseMessage response = null; |
||
235 | |||
236 | try |
||
237 | { |
||
3 | office | 238 | response = |
239 | await |
||
240 | HttpRequest(listUri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)) |
||
241 | .ConfigureAwait(false); |
||
1 | office | 242 | |
243 | if (response.StatusCode != HttpStatusCode.OK && |
||
3 | office | 244 | (int) response.StatusCode != HttpStatusCode_MultiStatus) |
1 | office | 245 | { |
3 | office | 246 | throw new wasDAVException((int) response.StatusCode, |
247 | string.Format("Failed retrieving item/folder (Status Code: {0})", response.StatusCode)); |
||
1 | office | 248 | } |
249 | |||
250 | using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) |
||
251 | { |
||
252 | var result = ResponseParser.ParseItem(stream); |
||
253 | |||
254 | if (result == null) |
||
255 | { |
||
256 | throw new wasDAVException("Failed deserializing data returned from server."); |
||
257 | } |
||
258 | |||
259 | return result; |
||
260 | } |
||
261 | } |
||
262 | finally |
||
263 | { |
||
264 | if (response != null) |
||
265 | response.Dispose(); |
||
266 | } |
||
267 | } |
||
268 | |||
269 | /// <summary> |
||
3 | office | 270 | /// Download a file from the server |
1 | office | 271 | /// </summary> |
272 | /// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
||
273 | public async Task<Stream> Download(string remoteFilePath) |
||
274 | { |
||
275 | // Should not have a trailing slash. |
||
276 | var downloadUri = await GetServerUrl(remoteFilePath, false).ConfigureAwait(false); |
||
277 | |||
3 | office | 278 | var dictionary = new Dictionary<string, string> {{"translate", "f"}}; |
1 | office | 279 | var response = await HttpRequest(downloadUri.Uri, HttpMethod.Get, dictionary).ConfigureAwait(false); |
280 | if (response.StatusCode != HttpStatusCode.OK) |
||
281 | { |
||
3 | office | 282 | throw new wasDAVException((int) response.StatusCode, "Failed retrieving file."); |
1 | office | 283 | } |
284 | return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); |
||
285 | } |
||
286 | |||
287 | /// <summary> |
||
3 | office | 288 | /// Download a file from the server |
1 | office | 289 | /// </summary> |
290 | /// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
||
291 | /// <param name="content"></param> |
||
292 | /// <param name="name"></param> |
||
293 | public async Task<bool> Upload(string remoteFilePath, Stream content, string name) |
||
294 | { |
||
295 | // Should not have a trailing slash. |
||
3 | office | 296 | var uploadUri = |
297 | await GetServerUrl(remoteFilePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
||
1 | office | 298 | |
299 | HttpResponseMessage response = null; |
||
300 | |||
301 | try |
||
302 | { |
||
303 | response = await HttpUploadRequest(uploadUri.Uri, HttpMethod.Put, content).ConfigureAwait(false); |
||
304 | |||
305 | if (response.StatusCode != HttpStatusCode.OK && |
||
306 | response.StatusCode != HttpStatusCode.NoContent && |
||
307 | response.StatusCode != HttpStatusCode.Created) |
||
308 | { |
||
3 | office | 309 | throw new wasDAVException((int) response.StatusCode, "Failed uploading file."); |
1 | office | 310 | } |
311 | |||
312 | return response.IsSuccessStatusCode; |
||
313 | } |
||
314 | finally |
||
315 | { |
||
316 | if (response != null) |
||
317 | response.Dispose(); |
||
318 | } |
||
319 | } |
||
320 | |||
321 | |||
322 | /// <summary> |
||
3 | office | 323 | /// Create a directory on the server |
1 | office | 324 | /// </summary> |
325 | /// <param name="remotePath">Destination path of the directory on the server</param> |
||
326 | /// <param name="name"></param> |
||
327 | public async Task<bool> CreateDir(string remotePath, string name) |
||
328 | { |
||
329 | // Should not have a trailing slash. |
||
3 | office | 330 | var dirUri = |
331 | await GetServerUrl(remotePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
||
1 | office | 332 | |
333 | HttpResponseMessage response = null; |
||
334 | |||
335 | try |
||
336 | { |
||
337 | response = await HttpRequest(dirUri.Uri, MkCol).ConfigureAwait(false); |
||
338 | |||
339 | if (response.StatusCode == HttpStatusCode.Conflict) |
||
3 | office | 340 | throw new wasDAVConflictException((int) response.StatusCode, "Failed creating folder."); |
1 | office | 341 | |
342 | if (response.StatusCode != HttpStatusCode.OK && |
||
343 | response.StatusCode != HttpStatusCode.NoContent && |
||
344 | response.StatusCode != HttpStatusCode.Created) |
||
345 | { |
||
3 | office | 346 | throw new wasDAVException((int) response.StatusCode, "Failed creating folder."); |
1 | office | 347 | } |
348 | |||
349 | return response.IsSuccessStatusCode; |
||
350 | } |
||
351 | finally |
||
352 | { |
||
353 | if (response != null) |
||
354 | response.Dispose(); |
||
355 | } |
||
356 | } |
||
357 | |||
358 | public async Task DeleteFolder(string href) |
||
359 | { |
||
360 | var listUri = await GetServerUrl(href, true).ConfigureAwait(false); |
||
361 | await Delete(listUri.Uri).ConfigureAwait(false); |
||
362 | } |
||
363 | |||
364 | public async Task DeleteFile(string href) |
||
365 | { |
||
366 | var listUri = await GetServerUrl(href, false).ConfigureAwait(false); |
||
367 | await Delete(listUri.Uri).ConfigureAwait(false); |
||
368 | } |
||
369 | |||
370 | |||
371 | private async Task Delete(Uri listUri) |
||
372 | { |
||
373 | var response = await HttpRequest(listUri, HttpMethod.Delete).ConfigureAwait(false); |
||
374 | |||
375 | if (response.StatusCode != HttpStatusCode.OK && |
||
376 | response.StatusCode != HttpStatusCode.NoContent) |
||
377 | { |
||
3 | office | 378 | throw new wasDAVException((int) response.StatusCode, "Failed deleting item."); |
1 | office | 379 | } |
380 | } |
||
381 | |||
382 | public async Task<bool> MoveFolder(string srcFolderPath, string dstFolderPath) |
||
383 | { |
||
384 | // Should have a trailing slash. |
||
385 | var srcUri = await GetServerUrl(srcFolderPath, true).ConfigureAwait(false); |
||
386 | var dstUri = await GetServerUrl(dstFolderPath, true).ConfigureAwait(false); |
||
387 | |||
388 | return await Move(srcUri.Uri, dstUri.Uri).ConfigureAwait(false); |
||
389 | } |
||
390 | |||
391 | public async Task<bool> MoveFile(string srcFilePath, string dstFilePath) |
||
392 | { |
||
393 | // Should not have a trailing slash. |
||
394 | var srcUri = await GetServerUrl(srcFilePath, false).ConfigureAwait(false); |
||
395 | var dstUri = await GetServerUrl(dstFilePath, false).ConfigureAwait(false); |
||
396 | |||
397 | return await Move(srcUri.Uri, dstUri.Uri).ConfigureAwait(false); |
||
398 | } |
||
399 | |||
400 | |||
401 | private async Task<bool> Move(Uri srcUri, Uri dstUri) |
||
402 | { |
||
403 | const string requestContent = "MOVE"; |
||
404 | |||
405 | IDictionary<string, string> headers = new Dictionary<string, string>(); |
||
406 | headers.Add("Destination", dstUri.ToString()); |
||
407 | |||
3 | office | 408 | var response = |
409 | await |
||
410 | HttpRequest(srcUri, MoveMethod, headers, Encoding.UTF8.GetBytes(requestContent)) |
||
411 | .ConfigureAwait(false); |
||
1 | office | 412 | |
413 | if (response.StatusCode != HttpStatusCode.OK && |
||
414 | response.StatusCode != HttpStatusCode.Created) |
||
415 | { |
||
3 | office | 416 | throw new wasDAVException((int) response.StatusCode, "Failed moving file."); |
1 | office | 417 | } |
418 | |||
419 | return response.IsSuccessStatusCode; |
||
420 | } |
||
421 | |||
422 | #endregion |
||
423 | |||
424 | #region Server communication |
||
425 | |||
426 | /// <summary> |
||
3 | office | 427 | /// Perform the WebDAV call and fire the callback when finished. |
1 | office | 428 | /// </summary> |
429 | /// <param name="uri"></param> |
||
430 | /// <param name="method"></param> |
||
431 | /// <param name="headers"></param> |
||
432 | /// <param name="content"></param> |
||
3 | office | 433 | private async Task<HttpResponseMessage> HttpRequest(Uri uri, HttpMethod method, |
434 | IDictionary<string, string> headers = null, byte[] content = null) |
||
1 | office | 435 | { |
436 | using (var request = new HttpRequestMessage(method, uri)) |
||
437 | { |
||
438 | request.Headers.Connection.Add("Keep-Alive"); |
||
439 | if (!string.IsNullOrWhiteSpace(UserAgent)) |
||
440 | request.Headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent, UserAgentVersion)); |
||
441 | else |
||
442 | request.Headers.UserAgent.Add(new ProductInfoHeaderValue("WebDAVClient", AssemblyVersion)); |
||
443 | |||
444 | if (headers != null) |
||
445 | { |
||
3 | office | 446 | foreach (var key in headers.Keys) |
1 | office | 447 | { |
448 | request.Headers.Add(key, headers[key]); |
||
449 | } |
||
450 | } |
||
451 | |||
452 | // Need to send along content? |
||
453 | if (content != null) |
||
454 | { |
||
455 | request.Content = new ByteArrayContent(content); |
||
456 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml"); |
||
457 | } |
||
458 | |||
459 | return await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); |
||
460 | } |
||
461 | } |
||
462 | |||
463 | /// <summary> |
||
3 | office | 464 | /// Perform the WebDAV call and fire the callback when finished. |
1 | office | 465 | /// </summary> |
466 | /// <param name="uri"></param> |
||
467 | /// <param name="headers"></param> |
||
468 | /// <param name="method"></param> |
||
469 | /// <param name="content"></param> |
||
3 | office | 470 | private async Task<HttpResponseMessage> HttpUploadRequest(Uri uri, HttpMethod method, Stream content, |
471 | IDictionary<string, string> headers = null) |
||
1 | office | 472 | { |
473 | using (var request = new HttpRequestMessage(method, uri)) |
||
474 | { |
||
475 | request.Headers.Connection.Add("Keep-Alive"); |
||
476 | if (!string.IsNullOrWhiteSpace(UserAgent)) |
||
477 | request.Headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent, UserAgentVersion)); |
||
478 | else |
||
479 | request.Headers.UserAgent.Add(new ProductInfoHeaderValue("WebDAVClient", AssemblyVersion)); |
||
480 | |||
481 | if (headers != null) |
||
482 | { |
||
3 | office | 483 | foreach (var key in headers.Keys) |
1 | office | 484 | { |
485 | request.Headers.Add(key, headers[key]); |
||
486 | } |
||
487 | } |
||
488 | |||
489 | // Need to send along content? |
||
490 | if (content != null) |
||
491 | { |
||
492 | request.Content = new StreamContent(content); |
||
493 | } |
||
494 | |||
495 | var client = _uploadClient ?? _client; |
||
496 | return await client.SendAsync(request).ConfigureAwait(false); |
||
497 | } |
||
498 | } |
||
499 | |||
500 | /// <summary> |
||
3 | office | 501 | /// Try to create an Uri with kind UriKind.Absolute |
502 | /// This particular implementation also works on Mono/Linux |
||
503 | /// It seems that on Mono it is expected behaviour that uris |
||
504 | /// of kind /a/b are indeed absolute uris since it referes to a file in /a/b. |
||
505 | /// https://bugzilla.xamarin.com/show_bug.cgi?id=30854 |
||
1 | office | 506 | /// </summary> |
507 | /// <param name="uriString"></param> |
||
508 | /// <param name="uriResult"></param> |
||
509 | /// <returns></returns> |
||
510 | private static bool TryCreateAbsolute(string uriString, out Uri uriResult) |
||
511 | { |
||
512 | return Uri.TryCreate(uriString, UriKind.Absolute, out uriResult) && uriResult.Scheme != Uri.UriSchemeFile; |
||
513 | } |
||
514 | |||
515 | private async Task<UriBuilder> GetServerUrl(string path, bool appendTrailingSlash) |
||
516 | { |
||
517 | // Resolve the base path on the server |
||
518 | if (_encodedBasePath == null) |
||
519 | { |
||
3 | office | 520 | var baseUri = new UriBuilder(_server) {Path = _basePath}; |
1 | office | 521 | var root = await Get(baseUri.Uri, null).ConfigureAwait(false); |
522 | |||
523 | _encodedBasePath = root.Href; |
||
524 | } |
||
525 | |||
526 | |||
527 | // If we've been asked for the "root" folder |
||
528 | if (string.IsNullOrEmpty(path)) |
||
529 | { |
||
530 | // If the resolved base path is an absolute URI, use it |
||
531 | Uri absoluteBaseUri; |
||
532 | if (TryCreateAbsolute(_encodedBasePath, out absoluteBaseUri)) |
||
533 | { |
||
534 | return new UriBuilder(absoluteBaseUri); |
||
535 | } |
||
536 | |||
537 | // Otherwise, use the resolved base path relatively to the server |
||
3 | office | 538 | var baseUri = new UriBuilder(_server) {Path = _encodedBasePath}; |
1 | office | 539 | return baseUri; |
540 | } |
||
541 | |||
542 | // If the requested path is absolute, use it |
||
543 | Uri absoluteUri; |
||
544 | if (TryCreateAbsolute(path, out absoluteUri)) |
||
545 | { |
||
546 | var baseUri = new UriBuilder(absoluteUri); |
||
547 | return baseUri; |
||
548 | } |
||
549 | else |
||
550 | { |
||
551 | // Otherwise, create a URI relative to the server |
||
552 | UriBuilder baseUri; |
||
553 | if (TryCreateAbsolute(_encodedBasePath, out absoluteUri)) |
||
554 | { |
||
555 | baseUri = new UriBuilder(absoluteUri); |
||
556 | |||
557 | baseUri.Path = baseUri.Path.TrimEnd('/') + "/" + path.TrimStart('/'); |
||
558 | |||
559 | if (appendTrailingSlash && !baseUri.Path.EndsWith("/")) |
||
560 | baseUri.Path += "/"; |
||
561 | } |
||
562 | else |
||
563 | { |
||
564 | baseUri = new UriBuilder(_server); |
||
565 | |||
566 | // Ensure we don't add the base path twice |
||
567 | var finalPath = path; |
||
568 | if (!finalPath.StartsWith(_encodedBasePath, StringComparison.InvariantCultureIgnoreCase)) |
||
569 | { |
||
570 | finalPath = _encodedBasePath.TrimEnd('/') + "/" + path; |
||
571 | } |
||
572 | if (appendTrailingSlash) |
||
573 | finalPath = finalPath.TrimEnd('/') + "/"; |
||
574 | |||
575 | baseUri.Path = finalPath; |
||
576 | } |
||
577 | |||
578 | |||
579 | return baseUri; |
||
580 | } |
||
581 | } |
||
582 | |||
4 | office | 583 | public void Dispose() |
584 | { |
||
585 | if(_client != null) |
||
586 | _client.Dispose(); |
||
587 | if (_uploadClient != null) |
||
588 | _uploadClient.Dispose(); |
||
589 | } |
||
590 | |||
1 | office | 591 | #endregion |
592 | } |
||
3 | office | 593 | } |