/// <summary> /// Create intersection with another option /// </summary> /// <param name="option"></param> /// <returns>this if <paramref name="option"/> is null or new instance with intersection</returns> public virtual AllOptions Intersection(IOption option) { if (option == null) { return(this); } AllOptions result = new AllOptions(); result.CanBrowse = this.CanBrowse | option.CanBrowse(); result.CanGetEntry = this.CanGetEntry | option.CanGetEntry(); result.CanObserve = this.CanObserve | option.CanObserve(); result.CanOpen = this.CanOpen | option.CanOpen(); result.CanRead = this.CanRead | option.CanRead(); result.CanWrite = this.CanWrite | option.CanWrite(); result.CanCreateFile = this.CanCreateFile | option.CanCreateFile(); result.CanDelete = this.CanDelete | option.CanDelete(); result.CanSetFileAttribute = this.CanSetFileAttribute | option.CanSetFileAttribute(); result.CanMount = this.CanMount | option.CanMount(); result.CanCreateFile = this.CanCreateFile | option.CanCreateFile(); result.CanDelete = this.CanDelete | option.CanDelete(); result.CanMove = this.CanMove | option.CanMove(); result.CanCreateDirectory = this.CanCreateDirectory | option.CanCreateDirectory(); result.CanMount = this.CanMount | option.CanMount(); result.CanUnmount = this.CanUnmount | option.CanUnmount(); result.CanListMountPoints = this.CanListMountPoints | option.CanListMountPoints(); result.SubPath = this.SubPath ?? option.SubPath(); return(result); }
/// <summary> /// Read options from <paramref name="option"/> and return flattened object. /// </summary> /// <param name="option"></param> /// <returns></returns> public virtual void ReadFrom(IOption option) { this.CanBrowse = option.CanBrowse(); this.CanGetEntry = option.CanGetEntry(); this.CanObserve = option.CanObserve(); this.CanOpen = option.CanOpen(); this.CanRead = option.CanRead(); this.CanWrite = option.CanWrite(); this.CanCreateFile = option.CanCreateFile(); this.CanDelete = option.CanDelete(); this.CanMove = option.CanMove(); this.CanCreateDirectory = option.CanCreateDirectory(); this.CanMount = option.CanMount(); this.CanUnmount = option.CanUnmount(); this.CanListMountPoints = option.CanListMountPoints(); this.SubPath = option.SubPath(); this.CanSetFileAttribute = option.CanSetFileAttribute(); }
/// <summary> /// Open a file for reading or writing. /// /// To GET a file, the combination of <see cref="FileMode.Open"/> and <see cref="FileAccess.Read"/> issues a GET request on <paramref name="uri"/>. /// Returns async stream that may not yet have been fully loaded. /// /// To PUT a file, the combination of <see cref="FileMode.Create"/>, <see cref="FileMode.CreateNew"/> or <see cref="FileMode.Truncate"/>, and <see cref="FileAccess.Write"/> issues a request on <paramref name="uri"/>. /// Returns a memory stream that can be written to. /// /// <paramref name="fileShare"/> is ignored. /// /// Authentication header can be placed in <paramref name="option"/> as instance of <see cref="AuthenticationHeaderValue"/> wrapped in (for example) <see cref="Token"/> or <see cref="TokenList"/>. /// /// Other <see cref="HttpHeaders"/> can also placed in <paramref name="option"/>. /// /// <see cref="CancellationToken"/> can be placed in <paramref name="option"/>. /// </summary> /// <param name="uri">Relative path to file. Directory separator is "/". Root is without preceding "/", e.g. "dir/file.xml"</param> /// <param name="fileMode">determines whether to open or to create the file</param> /// <param name="fileAccess">how to access the file, read, write or read and write</param> /// <param name="fileShare">how the file will be shared by processes</param> /// <param name="option">(optional) Credentials</param> /// <returns>open file stream</returns> /// <exception cref="FileSystemException">On unexpected IO error</exception> /// <exception cref="ArgumentNullException"><paramref name="uri"/> is null</exception> /// <exception cref="ArgumentException"><paramref name="uri"/> is an empty string (""), contains only white space, or contains one or more invalid characters</exception> /// <exception cref="NotSupportedException">The <see cref="IFileSystem"/> doesn't support opening files</exception> /// <exception cref="FileNotFoundException">The file cannot be found, such as when mode is FileMode.Truncate or FileMode.Open, and and the file specified by path does not exist. The file must already exist in these modes.</exception> /// <exception cref="DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception> /// <exception cref="UnauthorizedAccessException">The access requested is not permitted by the operating system for the specified path, such as when access is Write or ReadWrite and the file or directory is set for read-only access.</exception> /// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="fileMode"/>, <paramref name="fileAccess"/> or <paramref name="fileShare"/> contains an invalid value.</exception> /// <exception cref="InvalidOperationException">If <paramref name="uri"/> refers to a non-file device, such as "con:", "com1:", "lpt1:", etc.</exception> /// <exception cref="ObjectDisposedException"/> /// <exception cref="FileSystemExceptionNoReadAccess">No read access</exception> /// <exception cref="FileSystemExceptionNoWriteAccess">No write access</exception> /// <exception cref="OperationCanceledException">operation canceled</exception> public Stream Open(string uri, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, IOption option = null) { // Take reference var _httpClient = httpClient; // Assert not disposed if (_httpClient == null || IsDisposing) { throw new ObjectDisposedException(nameof(HttpFileSystem)); } // Append subpath string _subpath = option.SubPath() ?? this.options.SubPath; if (_subpath != null) { uri = _subpath + uri; } // Cancel token CancellationToken cancel = default; option.TryGetToken(uri, out cancel); try { // Check canceled if (cancel.IsCancellationRequested) { throw new OperationCanceledException(); } // GET if (fileMode == FileMode.Open && fileAccess == FileAccess.Read) { // Assert allowed if (!options.CanOpen || !options.CanRead || !option.CanOpen(true) || !option.CanRead(true)) { throw new NotSupportedException(nameof(Open)); } // Request object HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); // Read token ReadTokenToHeaders(uri, option, request.Headers); // Read authentication token AuthenticationHeaderValue authenticationHeader; if (this.token.TryGetToken(uri, out authenticationHeader) || option.TryGetToken(uri, out authenticationHeader)) { request.Headers.Authorization = authenticationHeader; } // Start GET Task <HttpResponseMessage> t = _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); // Wait for headers to complete t.Wait(cancel); // Get result object HttpResponseMessage response = t.Result; // Assert ok if (!response.IsSuccessStatusCode) { throw new FileSystemException(this, uri, response.StatusCode.ToString()); } // Stream Task Task <Stream> tt = response.Content.ReadAsStreamAsync(); // Wait for stream tt.Wait(cancel); // return stream return(tt.Result); } // PUT else if ((fileMode == FileMode.Create || fileMode == FileMode.CreateNew || fileMode == FileMode.OpenOrCreate || fileMode == FileMode.Truncate) && fileAccess == FileAccess.Write) { // Assert allowed if ((!options.CanOpen && !options.CanCreateFile && !option.CanOpen(true) && !option.CanCreateFile(true)) || !options.CanWrite || !option.CanWrite(true)) { throw new NotSupportedException(nameof(Open)); } // Request HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uri); // Read token ReadTokenToHeaders(uri, option, request.Headers); // Content FileSystemHttpContent httpContent = new FileSystemHttpContent(); // Start PUT Task <HttpResponseMessage> responseTask = _httpClient.PutAsync(uri, httpContent, cancel); responseTask.ContinueWith(t => httpContent.Semaphore.Release()); // Wait for http-content to start writing content httpContent.Semaphore.Wait(cancel); // Get write stream Stream writeStream = httpContent.Stream; // Got an error on t if (writeStream == null) { // Wait responseTask.Wait(cancel); // Get result object HttpResponseMessage response = responseTask.Result; // Assert ok if (!response.IsSuccessStatusCode) { throw new FileSystemException(this, uri, response.StatusCode.ToString()); } // Something went unexpectedly wrong throw new FileSystemException(this, uri, "Operation failed, unknown reason."); } // Wrap write stream Stream wrappedStream = new WriteStream(writeStream, httpContent.tcs, responseTask, this, uri); // Return stream for writing return(wrappedStream); } // Combination not supported else { throw new NotSupportedException($"FileMode={fileMode}, FileAccess={fileAccess}"); } } catch (AggregateException e) { Exception _e = e; if (e.InnerExceptions.Count == 1) { _e = e.InnerExceptions.First(); } throw new FileSystemException(this, uri, _e.Message, _e); } catch (Exception e) when(e is FileSystemException == false) { throw new FileSystemException(this, uri, e.Message, e); } }