/// <summary> /// Checks if content of FlashAir card is updated. /// </summary> /// <returns> /// True: If completed and content found updated. /// False: If completed and content found not updated. /// Null: If failed. /// </returns> private async Task <bool?> CheckUpdateAsync() { try { if (NetworkChecker.IsNetworkConnected(_card)) { OperationStatus = Resources.OperationStatus_Checking; var isUpdated = false; using (var manager = new FileManager()) { isUpdated = _card.CanGetWriteTimeStamp ? (await manager.GetWriteTimeStampAsync(CancellationToken.None) != _card.WriteTimeStamp) : await manager.CheckUpdateStatusAsync(); } OperationStatus = Resources.OperationStatus_Completed; return(isUpdated); } else { OperationStatus = Resources.OperationStatus_ConnectionUnable; return(false); } } catch (Exception ex) { if (ex is RemoteConnectionUnableException) { OperationStatus = Resources.OperationStatus_ConnectionUnable; } else if (ex is RemoteConnectionLostException) { OperationStatus = Resources.OperationStatus_ConnectionLost; } else if (ex is TimeoutException) { OperationStatus = Resources.OperationStatus_TimedOut; } else { OperationStatus = Resources.OperationStatus_Error; Debug.WriteLine($"Failed to check if content is updated.\r\n{ex}"); throw new UnexpectedException("Failed to check if content is updated.", ex); } } return(null); }
/// <summary> /// Checks if ready for operation. /// </summary> /// <returns>True if ready</returns> private bool IsReady() { if (!NetworkChecker.IsNetworkConnected()) { OperationStatus = Resources.OperationStatus_NoNetwork; return(false); } if ((Settings.Current.TargetPeriod == FilePeriod.Select) && !Settings.Current.TargetDates.Any()) { OperationStatus = Resources.OperationStatus_NoDateCalender; return(false); } return(true); }
/// <summary> /// Check if content of FlashAir card is updated. /// </summary> /// <returns> /// True: If completed and content found updated. /// False: If completed and content found not updated. /// Null: If failed. /// </returns> private async Task <bool?> CheckUpdateAsync() { try { if (await NetworkChecker.IsNetworkConnectedAsync(card)) { OperationStatus = Resources.OperationStatus_Checking; var isUpdated = card.CanGetWriteTimeStamp ? (await FileManager.GetWriteTimeStampAsync() != card.WriteTimeStamp) : await FileManager.CheckUpdateStatusAsync(); OperationStatus = Resources.OperationStatus_Completed; return(isUpdated); } else { OperationStatus = Resources.OperationStatus_ConnectionUnable; return(false); } } catch (Exception ex) { if (ex.GetType() == typeof(RemoteConnectionUnableException)) { OperationStatus = Resources.OperationStatus_ConnectionUnable; } else if (ex.GetType() == typeof(RemoteConnectionLostException)) { OperationStatus = Resources.OperationStatus_ConnectionLost; } else if (ex.GetType() == typeof(TimeoutException)) { OperationStatus = Resources.OperationStatus_TimedOut; } else { OperationStatus = Resources.OperationStatus_Error; Debug.WriteLine("Failed to check if content is updated. {0}", ex); throw new UnexpectedException("Failed to check if content is updated.", ex); } } return(null); }
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); } } }
/// <summary> /// Checks files in FlashAir card. /// </summary> private async Task CheckFileBaseAsync() { OperationStatus = Resources.OperationStatus_Checking; try { using (var manager = new FileManager()) { _tokenSourceWorking = new CancellationTokenSourcePlus(); // Check firmware version. _card.FirmwareVersion = await manager.GetFirmwareVersionAsync(_tokenSourceWorking.Token); // Check CID. if (_card.CanGetCid) { _card.Cid = await manager.GetCidAsync(_tokenSourceWorking.Token); } // Check SSID and check if PC is connected to FlashAir card by a wireless connection. _card.Ssid = await manager.GetSsidAsync(_tokenSourceWorking.Token); _card.IsWirelessConnected = NetworkChecker.IsWirelessNetworkConnected(_card.Ssid); // Get all items. var fileListNew = (await manager.GetFileListRootAsync(_card, _tokenSourceWorking.Token)) .Select(fileItem => new FileItemViewModel(fileItem)) .ToList(); fileListNew.Sort(); // Record time stamp of write event. if (_card.CanGetWriteTimeStamp) { _card.WriteTimeStamp = await manager.GetWriteTimeStampAsync(_tokenSourceWorking.Token); } await Task.Run(() => { // Check if any sample is in old items. var isSample = FileListCore.Any(x => x.Size == 0); // Check if FlashAir card is changed. bool isChanged; if (_card.IsChanged.HasValue) { isChanged = _card.IsChanged.Value; } else { var signaturesOld = new HashSet <string>(FileListCore.Select(x => x.Signature)); isChanged = !fileListNew.Select(x => x.Signature).Any(x => signaturesOld.Contains(x)); } if (isSample || isChanged) { FileListCore.Clear(); } // Check old items. foreach (var itemOld in FileListCore) { var itemSameIndex = fileListNew.IndexOf(x => x.FilePath.Equals(itemOld.FilePath, StringComparison.OrdinalIgnoreCase) && (x.Size == itemOld.Size)); if (itemSameIndex >= 0) { itemOld.IsAliveRemote = true; itemOld.FileItem = fileListNew[itemSameIndex].FileItem; fileListNew.RemoveAt(itemSameIndex); continue; } itemOld.IsAliveRemote = false; } // Add new items (This operation may be heavy). var isLeadOff = true; foreach (var itemNew in fileListNew) { if (isLeadOff) { InvokeSafely(() => FileListCoreViewIndex = FileListCoreView.IndexOf(itemNew)); isLeadOff = false; } itemNew.IsAliveRemote = true; FileListCore.Insert(itemNew); // Customized Insert method } // Check all local files. foreach (var item in FileListCore) { var info = GetFileInfoLocal(item); item.IsAliveLocal = IsAliveLocal(info, item.Size); item.IsAvailableLocal = IsAvailableLocal(info); item.Status = item.IsAliveLocal ? FileStatus.Copied : FileStatus.NotCopied; } // Manage deleted items. var itemDeletedPairs = FileListCore .Select((x, Index) => !x.IsAliveRemote ? new { Item = x, Index } : null) .Where(x => x != null) .OrderByDescending(x => x.Index) .ToArray(); foreach (var itemDeletedPair in itemDeletedPairs) { var item = itemDeletedPair.Item; if (Settings.Current.MovesFileToRecycle && item.IsDescendant && (item.Status == FileStatus.Copied)) { try { Recycle.MoveToRecycle(ComposeLocalPath(item)); item.Status = FileStatus.Recycled; } catch (Exception ex) { Debug.WriteLine($"Failed to move a file to Recycle.\r\n{ex}"); item.Status = FileStatus.NotCopied; } } if (!item.IsDescendant || ((item.Status != FileStatus.Copied) && (item.Status != FileStatus.Recycled))) { FileListCore.RemoveAt(itemDeletedPair.Index); } } }); // Get thumbnails (from local). foreach (var item in FileListCore.Where(x => x.IsTarget && !x.HasThumbnail)) { if (!item.IsAliveLocal || !item.IsAvailableLocal || !item.CanLoadDataLocal) { continue; } _tokenSourceWorking.Token.ThrowIfCancellationRequested(); try { if (item.CanReadExif) { item.Thumbnail = await ImageManager.ReadThumbnailAsync(ComposeLocalPath(item)); } else if (item.CanLoadDataLocal) { item.Thumbnail = await ImageManager.CreateThumbnailAsync(ComposeLocalPath(item)); } } catch (FileNotFoundException) { item.Status = FileStatus.NotCopied; item.IsAliveLocal = false; } catch (IOException) { item.IsAvailableLocal = false; } catch (ImageNotSupportedException) { item.CanLoadDataLocal = false; } } // Get thumbnails (from remote). foreach (var item in FileListCore.Where(x => x.IsTarget && !x.HasThumbnail)) { if (!item.IsAliveRemote || !item.CanGetThumbnailRemote) { continue; } _tokenSourceWorking.Token.ThrowIfCancellationRequested(); try { item.Thumbnail = await manager.GetThumbnailAsync(item.FilePath, _card, _tokenSourceWorking.Token); } catch (RemoteFileThumbnailFailedException) { item.CanGetThumbnailRemote = false; } } } OperationStatus = Resources.OperationStatus_CheckCompleted; } finally { FileListCoreViewIndex = -1; // No selection _tokenSourceWorking?.Dispose(); } }
/// <summary> /// Check files in FlashAir card. /// </summary> private async Task CheckFileBaseAsync() { OperationStatus = Resources.OperationStatus_Checking; try { tokenSourceWorking = new CancellationTokenSource(); isTokenSourceWorkingDisposed = false; // Check firmware version. card.FirmwareVersion = await FileManager.GetFirmwareVersionAsync(tokenSourceWorking.Token); // Check CID. if (card.CanGetCid) { card.Cid = await FileManager.GetCidAsync(tokenSourceWorking.Token); } // Check SSID. card.Ssid = await FileManager.GetSsidAsync(tokenSourceWorking.Token); if (!String.IsNullOrWhiteSpace(card.Ssid)) { // Check if PC is connected to FlashAir card by wireless network. var checkTask = Task.Run(async() => card.IsWirelessConnected = await NetworkChecker.IsWirelessNetworkConnectedAsync(card.Ssid)); } // Get all items. var fileListNew = await FileManager.GetFileListRootAsync(tokenSourceWorking.Token, card); fileListNew.Sort(); // Record time stamp of write event. if (card.CanGetWriteTimeStamp) { card.WriteTimeStamp = await FileManager.GetWriteTimeStampAsync(tokenSourceWorking.Token); } // Check if any sample is in old items. var isSample = FileListCore.Any(x => x.Size == 0); // Check if FlashAir card is changed. bool isChanged; if (card.IsChanged.HasValue) { isChanged = card.IsChanged.Value; } else { var signatures = FileListCore.Select(x => x.Signature).ToArray(); isChanged = !fileListNew.Select(x => x.Signature).Any(x => signatures.Contains(x)); } if (isSample || isChanged) { FileListCore.Clear(); } // Check old items. foreach (var itemOld in FileListCore) { var itemBuff = fileListNew.FirstOrDefault(x => x.FilePath == itemOld.FilePath); if ((itemBuff != null) && (itemBuff.Size == itemOld.Size)) { fileListNew.Remove(itemBuff); itemOld.IsAliveRemote = true; itemOld.IsAliveLocal = IsCopiedLocal(itemOld); itemOld.Status = itemOld.IsAliveLocal ? FileStatus.Copied : FileStatus.NotCopied; continue; } itemOld.IsAliveRemote = false; } // Add new items. var isLeadOff = true; foreach (var itemNew in fileListNew) { if (isLeadOff) { FileListCoreViewIndex = FileListCoreView.IndexOf(itemNew); isLeadOff = false; } itemNew.IsAliveRemote = true; itemNew.IsAliveLocal = IsCopiedLocal(itemNew); itemNew.Status = itemNew.IsAliveLocal ? FileStatus.Copied : FileStatus.NotCopied; FileListCore.Insert(itemNew); // Customized Insert method } // Manage deleted items. var itemsDeleted = FileListCore.Where(x => !x.IsAliveRemote && (x.Status != FileStatus.Recycled)).ToArray(); if (itemsDeleted.Any()) { if (Settings.Current.MovesFileToRecycle) { var itemsDeletedCopied = itemsDeleted.Where(x => x.Status == FileStatus.Copied).ToList(); Recycle.MoveToRecycle(itemsDeletedCopied.Select(ComposeLocalPath)); itemsDeletedCopied.ForEach(x => x.Status = FileStatus.Recycled); } for (int i = itemsDeleted.Length - 1; i >= 0; i--) { if ((itemsDeleted[i].Status == FileStatus.Copied) || (itemsDeleted[i].Status == FileStatus.Recycled)) { continue; } FileListCore.Remove(itemsDeleted[i]); } } // Get thumbnails (from local). foreach (var item in FileListCore) { if (!item.IsTarget || item.HasThumbnail || (item.Status != FileStatus.Copied) || !item.IsAliveLocal || !item.CanLoadDataLocal) { continue; } tokenSourceWorking.Token.ThrowIfCancellationRequested(); try { if (item.CanReadExif) { item.Thumbnail = await ImageManager.ReadThumbnailAsync(ComposeLocalPath(item)); } else if (item.CanLoadDataLocal) { item.Thumbnail = await ImageManager.CreateThumbnailAsync(ComposeLocalPath(item)); } } catch (FileNotFoundException) { item.Status = FileStatus.NotCopied; item.IsAliveLocal = false; } catch (IOException) { item.CanLoadDataLocal = false; } catch (ImageNotSupportedException) { item.CanLoadDataLocal = false; } } // Get thumbnails (from remote). foreach (var item in FileListCore) { if (!item.IsTarget || item.HasThumbnail || (item.Status == FileStatus.Copied) || !item.IsAliveRemote || !item.CanGetThumbnailRemote) { continue; } if (!card.CanGetThumbnail) { continue; } tokenSourceWorking.Token.ThrowIfCancellationRequested(); try { item.Thumbnail = await FileManager.GetThumbnailAsync(item.FilePath, tokenSourceWorking.Token, card); } catch (RemoteFileThumbnailFailedException) { item.CanGetThumbnailRemote = false; card.ThumbnailFailedPath = item.FilePath; } } OperationStatus = Resources.OperationStatus_CheckCompleted; } finally { FileListCoreViewIndex = -1; // No selection if (tokenSourceWorking != null) { isTokenSourceWorkingDisposed = true; tokenSourceWorking.Dispose(); } } }