/// <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, CancellationToken token, CardInfo card) { int retryCount = 0; while (true) { retryCount++; try { try { using (var response = await client.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, token)) { // 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 = token.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(async s => { if (!await NetworkChecker.IsNetworkConnectedAsync(card)) { ((TaskCompletionSource <bool>)s).TrySetResult(true); } }, tcs, monitorLength, monitorLength)) { 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(timeoutLength); 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); // Number of steps to report during downloading if (stepTotal < 6) { stepTotal = 6; // 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(timeoutLength); 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(monitorLength, monitorLength); // Report if read length in total exceeds stepped length. if (stepCurrent / stepTotal * size <= readLengthTotal) { stepCurrent++; progress.Report(new ProgressInfo( currentValue: readLengthTotal, totalValue: size, elapsedTime: DateTime.Now - startTime)); } } } return(bufferTotal); } } } } } // Sort out exceptions. catch (OperationCanceledException) // Including TaskCanceledException { if (!token.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 (token.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) && token.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 < retryLength) { await Task.Delay(retryLength, token); } } }