/// <summary> /// Get a list of all files recursively in a specified directory in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> /// <remarks>This method is part of parent method.</remarks> private static async Task<List<IFileItem>> GetFileListAllAsync(HttpClient client, string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { var itemList = await GetFileListEachAsync(client, remoteDirectoryPath, card, cancellationToken); for (int i = itemList.Count - 1; 0 <= i; i--) { if (itemList[i].IsHidden || itemList[i].IsSystemFile || itemList[i].IsVolume || itemList[i].IsFlashAirSystemFolder) { itemList.Remove(itemList[i]); continue; } if (!itemList[i].IsDirectory) { if (!itemList[i].IsImageFile) { itemList.Remove(itemList[i]); } continue; } var path = itemList[i].FilePath; itemList.Remove(itemList[i]); itemList.AddRange(await GetFileListAllAsync(client, path, card, cancellationToken)); } return itemList; }
/// <summary> /// Check if PC is connected to a network and if applicable, a specified wireless LAN. /// </summary> /// <param name="card">FlashAir card information</param> /// <returns>True if connected</returns> internal static bool IsNetworkConnected(CardInfo card) { if (!NetworkInterface.GetIsNetworkAvailable()) return false; if ((card == null) || String.IsNullOrWhiteSpace(card.Ssid) || !card.IsWirelessConnected) return true; return IsWirelessNetworkConnected(card.Ssid); }
/// <summary> /// Get a list of all files recursively from root folder of FlashAir card. /// </summary> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> internal static async Task<List<IFileItem>> GetFileListRootAsync(CardInfo card, CancellationToken cancellationToken) { try { using (var client = new HttpClient { Timeout = _timeoutDuration }) { return await GetFileListAllAsync(client, Settings.Current.RemoteDescendant, card, cancellationToken).ConfigureAwait(false); } } catch { Debug.WriteLine("Failed to get all file list."); throw; } }
internal Task <byte[]> GetSaveFileAsync(string remoteFilePath, string localFilePath, int size, DateTime itemDate, bool canReadExif, IProgress <ProgressInfo> progress, CardInfo card, CancellationToken cancellationToken) => GetSaveFileAsync(_client, remoteFilePath, localFilePath, size, itemDate, canReadExif, progress, card, cancellationToken);
private static async Task <byte[]> DownloadBytesAsync(HttpClient client, string path, int size, IProgress <ProgressInfo> progress, CardInfo card, CancellationToken cancellationToken) { var timeoutDuration = TimeSpan.FromSeconds(Settings.Current.TimeoutDuration); int retryCount = 0; while (true) { retryCount++; try { try { using (var response = await client.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) { // If HttpResponseMessage.EnsureSuccessStatusCode is set, an exception by this setting // will be thrown in the scope of HttpClient and so cannot be caught in this method. switch (response.StatusCode) { case HttpStatusCode.OK: // None. break; case HttpStatusCode.Unauthorized: case HttpStatusCode.InternalServerError: case HttpStatusCode.BadRequest: throw new RemoteConnectionUnableException(response.StatusCode); case HttpStatusCode.NotFound: // This status code does not always mean that the specified file is missing. throw new RemoteFileNotFoundException("File is missing or request cannot be handled!", path); default: throw new HttpRequestException($"StatusCode: {response.StatusCode}"); } if ((0 < size) && (response.Content.Headers.ContentLength != size)) { throw new RemoteFileInvalidException("Data length does not match!", path); } // Because of HttpCompletionOption.ResponseHeadersRead option, neither CancellationToken // nor HttpClient.Timeout setting works for response content. // Register delegate to CancellationToken because CancellationToken can no longer // directly affect HttpClient. Disposing the HttpResponseMessage will make ReadAsStreamAsync // method throw an ObjectDisposedException and so exit this operation. var ctr = new CancellationTokenRegistration(); try { ctr = cancellationToken.Register(() => response.Dispose()); } catch (ObjectDisposedException ode) { // If CancellationTokenSource has been disposed during operation (it unlikely happens), // this exception will be thrown. Debug.WriteLine($"CancellationTokenSource has been disposed when tried to register delegate.\r\n{ode}"); } using (ctr) { var tcs = new TaskCompletionSource <bool>(); // Start timer to monitor network connection. using (var monitorTimer = new Timer(s => { if (!NetworkChecker.IsNetworkConnected(card)) { ((TaskCompletionSource <bool>)s).TrySetResult(true); } }, tcs, _monitorInterval, _monitorInterval)) { var monitorTask = tcs.Task; if ((size == 0) || (progress == null)) { // Route without progress reporting var readTask = Task.Run(async() => await response.Content.ReadAsByteArrayAsync()); var timeoutTask = Task.Delay(timeoutDuration); var completedTask = await Task.WhenAny(readTask, timeoutTask, monitorTask); if (completedTask == timeoutTask) { throw new TimeoutException("Reading response content timed out!"); } if (completedTask == monitorTask) { throw new RemoteConnectionLostException("Connection lost!"); } var bytes = await readTask; if ((0 < size) && (bytes.Length != size)) { throw new RemoteFileInvalidException("Data length does not match!", path); } return(bytes); } else { // Route with progress reporting int readLength; int readLengthTotal = 0; var buffer = new byte[65536]; // 64KiB var bufferTotal = new byte[size]; const double stepUint = 524288D; // 512KiB double stepTotal = Math.Ceiling(size / stepUint); // The number of steps to report during downloading if (stepTotal < 6) { stepTotal = 6; // The minimum number of steps } double stepCurrent = 1D; var startTime = DateTime.Now; using (var stream = await response.Content.ReadAsStreamAsync()) { while (readLengthTotal != size) { // CancellationToken in overload of ReadAsync method will not work for response content. var readTask = Task.Run(async() => await stream.ReadAsync(buffer, 0, buffer.Length)); var timeoutTask = Task.Delay(timeoutDuration); var completedTask = await Task.WhenAny(readTask, timeoutTask, monitorTask); if (completedTask == timeoutTask) { throw new TimeoutException("Reading response content timed out!"); } if (completedTask == monitorTask) { throw new RemoteConnectionLostException("Connection lost!"); } readLength = await readTask; if ((readLength == 0) || (readLengthTotal + readLength > size)) { throw new RemoteFileInvalidException("Data length does not match!", path); } Buffer.BlockCopy(buffer, 0, bufferTotal, readLengthTotal, readLength); readLengthTotal += readLength; monitorTimer.Change(_monitorInterval, _monitorInterval); // Report if read length in total exceeds stepped length. if (stepCurrent / stepTotal * size <= readLengthTotal) { progress.Report(new ProgressInfo( currentValue: readLengthTotal, totalValue: size, elapsedTime: DateTime.Now - startTime, isFirst: stepCurrent == 1D)); stepCurrent++; } } } return(bufferTotal); } } } } } catch (OperationCanceledException) // Including TaskCanceledException { if (!cancellationToken.IsCancellationRequested) { // If cancellation has not been requested, the reason of this exception must be timeout. // This is for response header only. throw new TimeoutException("Reading response header timed out!"); } throw; } catch (ObjectDisposedException) { if (cancellationToken.IsCancellationRequested) { // If cancellation has been requested, the reason of this exception must be cancellation. // This is for response content only. throw new OperationCanceledException(); } throw; } catch (IOException ie) { if (cancellationToken.IsCancellationRequested) { var we = ie.InnerException as WebException; if (we?.Status == WebExceptionStatus.ConnectionClosed) { // If cancellation has been requested during downloading, this exception may be thrown. throw new OperationCanceledException(); } } throw; } catch (HttpRequestException hre) { var we = hre.InnerException as WebException; if (we != null) { // If unable to connect to FlashAir card, this exception will be thrown. // The status may vary, such as WebExceptionStatus.NameResolutionFailure, // WebExceptionStatus.ConnectFailure. throw new RemoteConnectionUnableException(we.Status); } var ode = hre.InnerException as ObjectDisposedException; if (ode != null) { // If lost connection to FlashAir card, this exception may be thrown. // Error message: Error while copying content to a stream. throw new RemoteConnectionLostException("Connection lost!"); } throw; } } catch (RemoteConnectionUnableException) { if (retryCount >= _retryCountMax) { throw; } } catch (Exception ex) { Debug.WriteLine($"Failed to download byte array.\r\n{ex}"); throw; } // Wait interval before retry. if (TimeSpan.Zero < _retryInterval) { await Task.Delay(_retryInterval, cancellationToken); } } }
internal Task <int> GetFileNumAsync(string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) => GetFileNumAsync(_client, remoteDirectoryPath, card, cancellationToken);
internal Task <IEnumerable <IFileItem> > GetFileListAsync(string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) => GetFileListAsync(_client, remoteDirectoryPath, card, cancellationToken);
/// <summary> /// Gets file data of a specified remote file in FlashAir card and save it in local folder. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteFilePath">Remote file path</param> /// <param name="localFilePath">Local file path</param> /// <param name="size">File size provided by FlashAir card</param> /// <param name="itemDate">Date provided by FlashAir card</param> /// <param name="canReadExif">Whether can read Exif metadata from the file</param> /// <param name="progress">Progress</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>Byte array of file</returns> internal static async Task <byte[]> GetSaveFileAsync(HttpClient client, string remoteFilePath, string localFilePath, int size, DateTime itemDate, bool canReadExif, IProgress <ProgressInfo> progress, CardInfo card, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(remoteFilePath)) { throw new ArgumentNullException(nameof(remoteFilePath)); } if (string.IsNullOrWhiteSpace(localFilePath)) { throw new ArgumentNullException(nameof(localFilePath)); } var remotePath = ComposeRemotePath(FileManagerCommand.None, remoteFilePath); try { var bytes = await DownloadBytesAsync(client, remotePath, size, progress, card, cancellationToken).ConfigureAwait(false); using (var fs = new FileStream(localFilePath, FileMode.Create, FileAccess.Write)) { await fs.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); } // Conform date of copied file in local folder to that of original file in FlashAir card. var localFileInfo = new FileInfo(localFilePath); localFileInfo.CreationTime = itemDate; // Creation time localFileInfo.LastWriteTime = itemDate; // Last write time // Overwrite creation time of copied file by date of image taken from Exif metadata. if (canReadExif) { var exifDateTaken = await ImageManager.GetExifDateTakenAsync(bytes); if (exifDateTaken != default(DateTime)) { localFileInfo.CreationTime = exifDateTaken; } } return(bytes); } catch { Debug.WriteLine("Failed to get and save a file."); throw; } }
/// <summary> /// Gets the number of files in a specified directory in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>The number of files</returns> /// <remarks>This method is not actually used.</remarks> internal static async Task <int> GetFileNumAsync(HttpClient client, string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(remoteDirectoryPath)) { throw new ArgumentNullException(nameof(remoteDirectoryPath)); } var remotePath = ComposeRemotePath(FileManagerCommand.GetFileNum, remoteDirectoryPath); try { var fileNum = await DownloadStringAsync(client, remotePath, card, cancellationToken).ConfigureAwait(false); int num; return(int.TryParse(fileNum, out num) ? num : 0); } catch { Debug.WriteLine("Failed to get the number of files."); throw; } }
/// <summary> /// Gets a list of files in a specified directory in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> /// <remarks>This method is part of parent method.</remarks> private static async Task <List <IFileItem> > GetFileListEachAsync(HttpClient client, string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { var remotePath = ComposeRemotePath(FileManagerCommand.GetFileList, remoteDirectoryPath); var fileEntries = await DownloadStringAsync(client, remotePath, card, cancellationToken); return(fileEntries.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select <string, IFileItem>(fileEntry => new FileItem(fileEntry, remoteDirectoryPath)) .Where(x => x.IsImported) .ToList()); }
/// <summary> /// Get the number of files in a specified directory in FlashAir card. /// </summary> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>The number of files</returns> /// <remarks>This method is not actually used.</remarks> internal static async Task<int> GetFileNumAsync(string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { if (String.IsNullOrWhiteSpace(remoteDirectoryPath)) throw new ArgumentNullException("remoteDirectoryPath"); var remotePath = ComposeRemotePath(FileManagerCommand.GetFileNum, remoteDirectoryPath); try { using (var client = new HttpClient { Timeout = _timeoutDuration }) { var fileNum = await DownloadStringAsync(client, remotePath, card, cancellationToken).ConfigureAwait(false); int num; return int.TryParse(fileNum, out num) ? num : 0; } } catch { Debug.WriteLine("Failed to get the number of files."); throw; } }
/// <summary> /// Get a list of files in a specified directory in FlashAir card. /// </summary> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> /// <remarks>This method is not actually used.</remarks> internal static async Task<List<IFileItem>> GetFileListAsync(string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { if (String.IsNullOrWhiteSpace(remoteDirectoryPath)) throw new ArgumentNullException("remoteDirectoryPath"); var remotePath = ComposeRemotePath(FileManagerCommand.GetFileList, remoteDirectoryPath); try { using (var client = new HttpClient { Timeout = _timeoutDuration }) { var fileEntries = await DownloadStringAsync(client, remotePath, card, cancellationToken).ConfigureAwait(false); return fileEntries.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select<string, IFileItem>(fileEntry => new FileItem(fileEntry, remoteDirectoryPath)) .Where(x => x.IsImported) .ToList(); } } catch { Debug.WriteLine("Failed to get file list."); throw; } }
/// <summary> /// Get a list of files in a specified directory in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> /// <remarks>This method is part of parent method.</remarks> private static async Task<List<IFileItem>> GetFileListEachAsync(HttpClient client, string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { var remotePath = ComposeRemotePath(FileManagerCommand.GetFileList, remoteDirectoryPath); var fileEntries = await DownloadStringAsync(client, remotePath, card, cancellationToken); return fileEntries.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select<string, IFileItem>(fileEntry => new FileItem(fileEntry, remoteDirectoryPath)) .Where(x => x.IsImported) .ToList(); }
/// <summary> /// Gets file data of a specified remote file in FlashAir card and save it in local folder. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteFilePath">Remote file path</param> /// <param name="localFilePath">Local file path</param> /// <param name="size">File size provided by FlashAir card</param> /// <param name="itemDate">Date provided by FlashAir card</param> /// <param name="canReadExif">Whether can read Exif metadata from the file</param> /// <param name="progress">Progress</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>Byte array of file</returns> internal static async Task <byte[]> GetSaveFileAsync(HttpClient client, string remoteFilePath, string localFilePath, int size, DateTime itemDate, bool canReadExif, IProgress <ProgressInfo> progress, CardInfo card, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(remoteFilePath)) { throw new ArgumentNullException(nameof(remoteFilePath)); } if (string.IsNullOrWhiteSpace(localFilePath)) { throw new ArgumentNullException(nameof(localFilePath)); } var remotePath = ComposeRemotePath(FileManagerCommand.None, remoteFilePath); byte[] bytes = null; try { bytes = await DownloadBytesAsync(client, remotePath, size, progress, card, cancellationToken).ConfigureAwait(false); } catch { Debug.WriteLine("Failed to get a file."); throw; } int retryCount = 0; while (true) { try { using (var fs = new FileStream(localFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { var creationTime = itemDate; var lastWriteTime = itemDate; // Overwrite creation time by date of image taken from Exif metadata. if (canReadExif) { var exifDateTaken = await ImageManager.GetExifDateTakenAsync(bytes, DateTimeKind.Local); if (exifDateTaken != default(DateTime)) { creationTime = exifDateTaken; } } FileTime.SetFileTime(fs.SafeFileHandle, creationTime: creationTime, lastWriteTime: lastWriteTime); await fs.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); } return(bytes); } catch (IOException) when(++retryCount < MaxRetryCount) { // Wait interval before retry. if (TimeSpan.Zero < _retryInterval) { await Task.Delay(_retryInterval, cancellationToken); } } catch { Debug.WriteLine("Failed to save a file."); throw; } } }
private static async Task <byte[]> DownloadBytesAsync(HttpClient client, string path, CancellationToken token, CardInfo card) { return(await DownloadBytesAsync(client, path, 0, null, token, card)); }
private static async Task <string> DownloadStringAsync(HttpClient client, string path, CancellationToken token, CardInfo card) { var bytes = await DownloadBytesAsync(client, path, 0, null, token, card); if (recordsDownloadString) { await RecordDownloadStringAsync(path, bytes); } // Response from FlashAir card seems to be ASCII encoded. Not certain though. return(Encoding.ASCII.GetString(bytes)); }
/// <summary> /// Get a list of all files recursively from root of FlashAir card. /// </summary> /// <param name="token">CancellationToken</param> /// <param name="card">FlashAir card information</param> internal static async Task <List <FileItemViewModel> > GetFileListRootAsync(CancellationToken token, CardInfo card) { try { using (var client = new HttpClient() { Timeout = timeoutLength }) { return(await GetFileListAllAsync(client, String.Empty, token, card).ConfigureAwait(false)); } } catch { Debug.WriteLine("Failed to get all file list."); throw; } }
/// <summary> /// Get number of files in a specified directory in FlashAir card. /// </summary> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="token">CancellationToken</param> /// <param name="card">FlashAir card information</param> /// <remarks>This method is not actually used.</remarks> internal static async Task <int> GetFileNumAsync(string remoteDirectoryPath, CancellationToken token, CardInfo card) { if (String.IsNullOrWhiteSpace(remoteDirectoryPath)) { throw new ArgumentNullException("remoteDirectoryPath"); } var remotePath = ComposeRemotePath(FileManagerCommand.GetFileNum, remoteDirectoryPath); try { using (var client = new HttpClient() { Timeout = timeoutLength }) { var itemNum = await DownloadStringAsync(client, remotePath, token, card).ConfigureAwait(false); int num; return(int.TryParse(itemNum, out num) ? num : 0); } } catch { Debug.WriteLine("Failed to get the number of files."); throw; } }
/// <summary> /// Gets a list of all files recursively in a specified directory in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> /// <remarks>This method is part of parent method.</remarks> private static async Task <List <IFileItem> > GetFileListAllAsync(HttpClient client, string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { var itemList = await GetFileListEachAsync(client, remoteDirectoryPath, card, cancellationToken); for (int i = itemList.Count - 1; 0 <= i; i--) { if (itemList[i].IsHidden || itemList[i].IsSystemFile || itemList[i].IsVolume || itemList[i].IsFlashAirSystemFolder) { itemList.RemoveAt(i); continue; } if (!itemList[i].IsDirectory) { if (!itemList[i].IsImageFile) { itemList.RemoveAt(i); } continue; } var path = itemList[i].FilePath; itemList.RemoveAt(i); itemList.AddRange(await GetFileListAllAsync(client, path, card, cancellationToken)); } return(itemList); }
/// <summary> /// Get a thumbnail of a specified image file in FlashAir card. /// </summary> /// <param name="remoteFilePath">Remote file path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>Thumbnail of image file</returns> internal static async Task<BitmapSource> GetThumbnailAsync(string remoteFilePath, CardInfo card, CancellationToken cancellationToken) { if (String.IsNullOrWhiteSpace(remoteFilePath)) throw new ArgumentNullException("remoteFilePath"); var remotePath = ComposeRemotePath(FileManagerCommand.GetThumbnail, remoteFilePath); try { using (var client = new HttpClient { Timeout = _timeoutDuration }) { var bytes = await DownloadBytesAsync(client, remotePath, card, cancellationToken).ConfigureAwait(false); return await ImageManager.ConvertBytesToBitmapSourceAsync(bytes).ConfigureAwait(false); } } catch (ImageNotSupportedException) { // This exception should not be thrown because thumbnail data is directly provided by FlashAir card. return null; } catch (Exception ex) { if ((ex.GetType() == typeof(RemoteFileNotFoundException)) || ((ex.GetType() == typeof(RemoteConnectionUnableException)) && (((RemoteConnectionUnableException)ex).Code == HttpStatusCode.InternalServerError))) { // If image file is not JPEG format or if there is no Exif standardized thumbnail stored, // StatusCode will be HttpStatusCode.NotFound. Or it may be HttpStatusCode.InternalServerError // when image file is non-standard JPEG format. Debug.WriteLine("Image file may not be JPEG format or may contain no thumbnail."); throw new RemoteFileThumbnailFailedException(remotePath); } Debug.WriteLine("Failed to get a thumbnail."); throw; } }
/// <summary> /// Gets a list of files in a specified directory in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> /// <remarks>This method is not actually used.</remarks> internal static async Task <IEnumerable <IFileItem> > GetFileListAsync(HttpClient client, string remoteDirectoryPath, CardInfo card, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(remoteDirectoryPath)) { throw new ArgumentNullException(nameof(remoteDirectoryPath)); } var remotePath = ComposeRemotePath(FileManagerCommand.GetFileList, remoteDirectoryPath); try { var fileEntries = await DownloadStringAsync(client, remotePath, card, cancellationToken).ConfigureAwait(false); return(fileEntries.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select <string, IFileItem>(fileEntry => new FileItem(fileEntry, remoteDirectoryPath)) .Where(x => x.IsImported) .ToList()); } catch { Debug.WriteLine("Failed to get file list."); throw; } }
/// <summary> /// Get file data of a specified remote file in FlashAir card and save it in local folder. /// </summary> /// <param name="remoteFilePath">Remote file path</param> /// <param name="localFilePath">Local file path</param> /// <param name="size">File size provided by FlashAir card</param> /// <param name="itemDate">Date provided by FlashAir card</param> /// <param name="canReadExif">Whether can read Exif metadata from the file</param> /// <param name="progress">Progress</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>Byte array of file</returns> internal static async Task<byte[]> GetSaveFileAsync(string remoteFilePath, string localFilePath, int size, DateTime itemDate, bool canReadExif, IProgress<ProgressInfo> progress, CardInfo card, CancellationToken cancellationToken) { if (String.IsNullOrWhiteSpace(remoteFilePath)) throw new ArgumentNullException("remoteFilePath"); if (String.IsNullOrWhiteSpace(localFilePath)) throw new ArgumentNullException("localFilePath"); var remotePath = ComposeRemotePath(FileManagerCommand.None, remoteFilePath); try { using (var client = new HttpClient { Timeout = _timeoutDuration }) { var bytes = await DownloadBytesAsync(client, remotePath, size, progress, card, cancellationToken).ConfigureAwait(false); using (var fs = new FileStream(localFilePath, FileMode.Create, FileAccess.Write)) { await fs.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); } // Conform date of copied file in local folder to that of original file in FlashAir card. var localFileInfo = new FileInfo(localFilePath); localFileInfo.CreationTime = itemDate; // Creation time localFileInfo.LastWriteTime = itemDate; // Last write time // Overwrite creation time of copied file by date of image taken from Exif metadata. if (canReadExif) { var exifDateTaken = await ImageManager.GetExifDateTakenAsync(bytes); if (exifDateTaken != default(DateTime)) { localFileInfo.CreationTime = exifDateTaken; } } return bytes; } } catch { Debug.WriteLine("Failed to get and save a file."); throw; } }
/// <summary> /// Gets a thumbnail of a specified image file in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteFilePath">Remote file path</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>Thumbnail of image file</returns> internal static async Task <BitmapSource> GetThumbnailAsync(HttpClient client, string remoteFilePath, CardInfo card, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(remoteFilePath)) { throw new ArgumentNullException(nameof(remoteFilePath)); } var remotePath = ComposeRemotePath(FileManagerCommand.GetThumbnail, remoteFilePath); try { var bytes = await DownloadBytesAsync(client, remotePath, card, cancellationToken).ConfigureAwait(false); return(await ImageManager.ConvertBytesToBitmapSourceAsync(bytes).ConfigureAwait(false)); } catch (ImageNotSupportedException) { // This exception should not be thrown because thumbnail data is directly provided by FlashAir card. return(null); } catch (Exception ex) { if ((ex is RemoteFileNotFoundException) || ((ex is RemoteConnectionUnableException) && (((RemoteConnectionUnableException)ex).Code == HttpStatusCode.InternalServerError))) { // If image file is not JPEG format or if there is no Exif standardized thumbnail stored, // StatusCode will be HttpStatusCode.NotFound. Or it may be HttpStatusCode.InternalServerError // when image file is non-standard JPEG format. Debug.WriteLine("Image file may not be JPEG format or may contain no thumbnail."); throw new RemoteFileThumbnailFailedException(remotePath); } Debug.WriteLine("Failed to get a thumbnail."); throw; } }
private static async Task<string> DownloadStringAsync(HttpClient client, string path, CardInfo card, CancellationToken cancellationToken) { var bytes = await DownloadBytesAsync(client, path, 0, null, card, cancellationToken); if (_recordsDownloadString) await RecordDownloadStringAsync(path, bytes); // Response from FlashAir card seems to be encoded by ASCII. return Encoding.ASCII.GetString(bytes); }
internal Task <IEnumerable <IFileItem> > GetFileListRootAsync(CardInfo card, CancellationToken cancellationToken) => GetFileListRootAsync(_client, card, cancellationToken);
private static async Task<byte[]> DownloadBytesAsync(HttpClient client, string path, int size, CardInfo card, CancellationToken cancellationToken) { return await DownloadBytesAsync(client, path, size, null, card, cancellationToken); }
private static async Task <string> DownloadStringAsync(HttpClient client, string path, CardInfo card, CancellationToken cancellationToken) { var bytes = await DownloadBytesAsync(client, path, 0, null, card, cancellationToken); if (_recordsDownloadString) { await RecordDownloadStringAsync(path, bytes); } // Response from FlashAir card seems to be encoded by ASCII. return(Encoding.ASCII.GetString(bytes)); }
private static async Task<byte[]> DownloadBytesAsync(HttpClient client, string path, int size, IProgress<ProgressInfo> progress, CardInfo card, CancellationToken cancellationToken) { int retryCount = 0; while (true) { retryCount++; try { try { using (var response = await client.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) { // If HttpResponseMessage.EnsureSuccessStatusCode is set, an exception by this setting // will be thrown in the scope of HttpClient and so cannot be caught in this method. switch (response.StatusCode) { case HttpStatusCode.OK: // None. break; case HttpStatusCode.Unauthorized: case HttpStatusCode.InternalServerError: case HttpStatusCode.BadRequest: throw new RemoteConnectionUnableException(response.StatusCode); case HttpStatusCode.NotFound: // This exception does not always mean that the specified file is missing. throw new RemoteFileNotFoundException("File is missing or request cannot be handled!", path); default: throw new HttpRequestException(String.Format("StatusCode: {0}", response.StatusCode)); } if ((0 < size) && (!response.Content.Headers.ContentLength.HasValue || (response.Content.Headers.ContentLength.Value != size))) throw new RemoteFileInvalidException("Data length does not match!", path); // Because of HttpCompletionOption.ResponseHeadersRead option, neither CancellationToken // nor HttpClient.Timeout setting works for response content. // Register delegate to CancellationToken because CancellationToken can no longer // directly affect HttpClient. Disposing the HttpResponseMessage will make ReadAsStreamAsync // method throw an ObjectDisposedException and so exit this operation. var ctr = new CancellationTokenRegistration(); try { ctr = cancellationToken.Register(() => response.Dispose()); } catch (ObjectDisposedException ode) { // If CancellationTokenSource has been disposed during operation (it unlikely happens), // this exception will be thrown. Debug.WriteLine("CancellationTokenSource has been disposed when tried to register delegate. {0}", ode); } using (ctr) { var tcs = new TaskCompletionSource<bool>(); // Start timer to monitor network connection. using (var monitorTimer = new Timer(s => { if (!NetworkChecker.IsNetworkConnected(card)) { ((TaskCompletionSource<bool>)s).TrySetResult(true); } }, tcs, _monitorInterval, _monitorInterval)) { var monitorTask = tcs.Task; if ((size == 0) || (progress == null)) { // Route without progress reporting var readTask = Task.Run(async () => await response.Content.ReadAsByteArrayAsync()); var timeoutTask = Task.Delay(_timeoutDuration); var completedTask = await Task.WhenAny(readTask, timeoutTask, monitorTask); if (completedTask == timeoutTask) throw new TimeoutException("Reading response content timed out!"); if (completedTask == monitorTask) throw new RemoteConnectionLostException("Connection lost!"); var bytes = await readTask; if ((0 < size) && (bytes.Length != size)) throw new RemoteFileInvalidException("Data length does not match!", path); return bytes; } else { // Route with progress reporting int readLength; int readLengthTotal = 0; var buffer = new byte[65536]; // 64KiB var bufferTotal = new byte[size]; const double stepUint = 524288D; // 512KiB double stepTotal = Math.Ceiling(size / stepUint); // The number of steps to report during downloading if (stepTotal < 6) stepTotal = 6; // The minimum number of steps double stepCurrent = 1D; var startTime = DateTime.Now; using (var stream = await response.Content.ReadAsStreamAsync()) { while (readLengthTotal != size) { // CancellationToken in overload of ReadAsync method will not work for response content. var readTask = Task.Run(async () => await stream.ReadAsync(buffer, 0, buffer.Length)); var timeoutTask = Task.Delay(_timeoutDuration); var completedTask = await Task.WhenAny(readTask, timeoutTask, monitorTask); if (completedTask == timeoutTask) throw new TimeoutException("Reading response content timed out!"); if (completedTask == monitorTask) throw new RemoteConnectionLostException("Connection lost!"); readLength = await readTask; if ((readLength == 0) || (readLengthTotal + readLength > size)) throw new RemoteFileInvalidException("Data length does not match!", path); Buffer.BlockCopy(buffer, 0, bufferTotal, readLengthTotal, readLength); readLengthTotal += readLength; monitorTimer.Change(_monitorInterval, _monitorInterval); // Report if read length in total exceeds stepped length. if (stepCurrent / stepTotal * size <= readLengthTotal) { progress.Report(new ProgressInfo( currentValue: readLengthTotal, totalValue: size, elapsedTime: DateTime.Now - startTime, isFirst: stepCurrent == 1D)); stepCurrent++; } } } return bufferTotal; } } } } } // Sort out exceptions. catch (OperationCanceledException) // Including TaskCanceledException { if (!cancellationToken.IsCancellationRequested) // If cancellation has not been requested, the reason of this exception must be timeout. // This is for response header only. throw new TimeoutException("Reading response header timed out!"); throw; } catch (ObjectDisposedException) { if (cancellationToken.IsCancellationRequested) // If cancellation has been requested, the reason of this exception must be cancellation. // This is for response content only. throw new OperationCanceledException(); throw; } catch (IOException ie) { var inner = ie.InnerException; if ((inner != null) && (inner.GetType() == typeof(WebException)) && (((WebException)inner).Status == WebExceptionStatus.ConnectionClosed) && cancellationToken.IsCancellationRequested) // If cancellation has been requested during downloading, this exception may be thrown. throw new OperationCanceledException(); throw; } catch (HttpRequestException hre) { var inner = hre.InnerException; if ((inner != null) && (inner.GetType() == typeof(WebException))) // If unable to connect to FlashAir card, this exception will be thrown. // The Status may vary, such as WebExceptionStatus.NameResolutionFailure, // WebExceptionStatus.ConnectFailure. throw new RemoteConnectionUnableException(((WebException)hre.InnerException).Status); throw; } } catch (RemoteConnectionUnableException) { if (retryCount >= _retryCountMax) throw; } catch (Exception ex) { Debug.WriteLine("Failed to download byte array. {0}", ex); throw; } // Wait interval before retry. if (TimeSpan.Zero < _retryInterval) await Task.Delay(_retryInterval, cancellationToken); } }
private static async Task <byte[]> DownloadBytesAsync(HttpClient client, string path, int size, CardInfo card, CancellationToken cancellationToken) { return(await DownloadBytesAsync(client, path, size, null, card, cancellationToken)); }
/// <summary> /// Get a list of files in a specified directory in FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="token">CancellationToken</param> /// <param name="card">FlashAir card information</param> /// <remarks>This method is part of parent method.</remarks> private static async Task <List <FileItemViewModel> > GetFileListEachAsync(HttpClient client, string remoteDirectoryPath, CancellationToken token, CardInfo card) { var remotePath = ComposeRemotePath(FileManagerCommand.GetFileList, remoteDirectoryPath); var items = await DownloadStringAsync(client, remotePath, token, card); return(items.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select(item => new FileItemViewModel(item, remoteDirectoryPath)) .Where(x => x.IsImported) .ToList()); }
internal Task <BitmapSource> GetThumbnailAsync(string remoteFilePath, CardInfo card, CancellationToken cancellationToken) => GetThumbnailAsync(_client, remoteFilePath, card, cancellationToken);
/// <summary> /// Gets a list of all files recursively from root folder of FlashAir card. /// </summary> /// <param name="client">HttpClient</param> /// <param name="card">FlashAir card information</param> /// <param name="cancellationToken">CancellationToken</param> /// <returns>File list</returns> internal static async Task <IEnumerable <IFileItem> > GetFileListRootAsync(HttpClient client, CardInfo card, CancellationToken cancellationToken) { try { return(await GetFileListAllAsync(client, Settings.Current.RemoteDescendant, card, cancellationToken).ConfigureAwait(false)); } catch { Debug.WriteLine("Failed to get all file list."); throw; } }
private static Task <byte[]> DownloadBytesAsync(HttpClient client, string path, CardInfo card, CancellationToken cancellationToken) { return(DownloadBytesAsync(client, path, 0, null, card, cancellationToken)); }
/// <summary> /// Get a list of files in a specified directory in FlashAir card. /// </summary> /// <param name="remoteDirectoryPath">Remote directory path</param> /// <param name="token">CancellationToken</param> /// <param name="card">FlashAir card information</param> /// <remarks>This method is not actually used.</remarks> internal static async Task <List <FileItemViewModel> > GetFileListAsync(string remoteDirectoryPath, CancellationToken token, CardInfo card) { if (String.IsNullOrWhiteSpace(remoteDirectoryPath)) { throw new ArgumentNullException("remoteDirectoryPath"); } var remotePath = ComposeRemotePath(FileManagerCommand.GetFileList, remoteDirectoryPath); try { using (var client = new HttpClient() { Timeout = timeoutLength }) { var items = await DownloadStringAsync(client, remotePath, token, card).ConfigureAwait(false); return(items.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select(item => new FileItemViewModel(item, remoteDirectoryPath)) .Where(x => x.IsImported) .ToList()); } } catch { Debug.WriteLine("Failed to get file list."); throw; } }