async Task <bool> MakeSureLibraryIsInPlace(XamarinBuildDownload xbd, CancellationToken token) { // Skip extraction if the file is already in place var flagFile = xbd.DestinationDir + ".unpacked"; if (File.Exists(flagFile)) { return(true); } try { Directory.CreateDirectory(xbd.DestinationDir); } catch (Exception ex) { LogCodedError(ThisOrThat(xbd.CustomErrorCode, ErrorCodes.DirectoryCreateFailed), ThisOrThat(xbd.CustomErrorMessage, () => string.Format("Failed to create directory '{0}'.", xbd.DestinationDir))); LogMessage("Directory creation failure reason: " + ex.ToString(), MessageImportance.High); return(false); } var lockFile = xbd.DestinationDir + ".locked"; using (var lockStream = DownloadUtils.ObtainExclusiveFileLock(lockFile, Token, TimeSpan.FromSeconds(xbd.ExclusiveLockTimeout), this)) { if (lockStream == null) { LogCodedError(ErrorCodes.ExclusiveLockTimeout, "Timed out waiting for exclusive file lock on: {0}", lockFile); LogMessage("Timed out waiting for an exclusive file lock on: " + lockFile, MessageImportance.High); return(false); } if (!File.Exists(xbd.CacheFile) || !IsValidDownload(xbd.DestinationDir + ".sha1", xbd.CacheFile, xbd.Sha1)) { try { int progress = -1; DownloadProgressChangedEventHandler downloadHandler = (o, e) => { if (e.ProgressPercentage % 10 != 0 || progress == e.ProgressPercentage) { return; } progress = e.ProgressPercentage; LogMessage( "\t({0}/{1}b), total {2:F1}%", e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage ); }; using (var client = new WebClient()) { client.DownloadProgressChanged += downloadHandler; LogMessage(" Downloading {0} to {1}", xbd.Url, xbd.CacheFile); client.DownloadFileTaskAsync(xbd.Url, xbd.CacheFile).Wait(token); LogMessage(" Downloading Complete"); client.DownloadProgressChanged -= downloadHandler; } } catch (Exception e) { LogCodedError(ThisOrThat(xbd.CustomErrorCode, ErrorCodes.DownloadFailed), ThisOrThat(xbd.CustomErrorMessage, () => string.Format("Download failed. Please download {0} to a file called {1}.", xbd.Url, xbd.CacheFile))); LogMessage("Download failure reason: " + e.GetBaseException().Message, MessageImportance.High); File.Delete(xbd.CacheFile); return(false); } } if (!File.Exists(xbd.CacheFile)) { LogCodedError(ThisOrThat(xbd.CustomErrorCode, ErrorCodes.DownloadedFileMissing), ThisOrThat(xbd.CustomErrorMessage, () => string.Format("Downloaded file '{0}' is missing.", xbd.CacheFile))); return(false); } if (xbd.Kind == ArchiveKind.Uncompressed) { var uncompressedCacheFile = xbd.CacheFile; if (!string.IsNullOrEmpty(xbd.ToFile)) { uncompressedCacheFile = xbd.ToFile; } File.Move(xbd.CacheFile, Path.Combine(xbd.DestinationDir, Path.GetFileName(uncompressedCacheFile))); File.WriteAllText(flagFile, "This marks that the extraction completed successfully"); return(true); } else { if (await ExtractArchive(xbd, flagFile, token)) { File.WriteAllText(flagFile, "This marks that the extraction completed successfully"); return(true); } } } // We will attempt to delete the lock file when we're done try { if (File.Exists(lockFile)) { File.Delete(lockFile); } } catch { } return(false); }
async Task DownloadAll(string cacheDirectory, List <PartialZipDownload> parts) { // Get the parts all grouped by their URL so we can batch requests with multiple ranges // instead of making a request to the same url for each part // also only grab the parts that don't already locally exist var uniqueUrls = parts .Where(p => !File.Exists(Path.Combine(cacheDirectory, p.Id, p.ToFile))) .GroupBy(p => p.Url); // For each unique url... foreach (var partsByUrl in uniqueUrls) { var downloadUrl = partsByUrl.Key; LogMessage("Downloading Partial Zip parts from: " + downloadUrl); try { // Create a lock file based on the hash of the URL we are downloading from // Since we could download a multipart request, we are locking on the url from any other process downloading from it var lockFile = Path.Combine(cacheDirectory, DownloadUtils.Crc64(downloadUrl) + ".locked"); using (var lockStream = DownloadUtils.ObtainExclusiveFileLock(lockFile, base.Token, TimeSpan.FromSeconds(30))) { if (lockStream == null) { LogCodedError(ErrorCodes.ExclusiveLockTimeout, "Timed out waiting for exclusive file lock on: {0}", lockFile); LogMessage("Timed out waiting for an exclusive file lock on: " + lockFile, MessageImportance.High); // Log Custom error if one was specified in the partial download info var firstPart = partsByUrl.FirstOrDefault(); if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage)) { LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage); } return; } try { await Download(cacheDirectory, partsByUrl.Key, partsByUrl.ToList()).ConfigureAwait(false); } catch (Exception ex) { LogCodedError(ErrorCodes.PartialDownloadFailed, "Partial Download Failed for one or more parts"); LogErrorFromException(ex); // Log Custom error if one was specified in the partial download info var firstPart = partsByUrl.FirstOrDefault(); if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage)) { LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage); } } } try { if (File.Exists(lockFile)) { File.Delete(lockFile); } } catch { } } catch (Exception ex) { LogCodedError(ErrorCodes.PartialDownloadFailed, "Partial Download Failed for one or more parts"); LogErrorFromException(ex); // Log Custom error if one was specified in the partial download info var firstPart = partsByUrl.FirstOrDefault(); if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage)) { LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage); } } } }