public async Task DownloadProductFileAsync(long id, string title, string sourceUri, string destination, IStatus status) { if (string.IsNullOrEmpty(sourceUri)) { return; } var sourceUriSansSession = formatUriRemoveSessionDelegate.Format(sourceUri); var destinationUri = formatValidationFileDelegate.Format(sourceUriSansSession); // return early if validation is not expected for this file if (!confirmValidationExpectedDelegate.Confirm(sourceUriSansSession)) { return; } if (fileController.Exists(destinationUri)) { await statusController.InformAsync(status, "Validation file already exists, will not be redownloading"); return; } var validationSourceUri = formatValidationUriDelegate.Format(sourceUri); var downloadValidationFileTask = await statusController.CreateAsync(status, "Download validation file"); await downloadFromUriAsyncDelegate.DownloadFromUriAsync( validationSourceUri, validationDirectoryDelegate.GetDirectory(string.Empty), downloadValidationFileTask); await statusController.CompleteAsync(downloadValidationFileTask); }
public override async Task ProcessActivityAsync(IStatus status) { // This activity will centralize processing and marking updated account products. // Currently the process is the following: // 1) Itemize account products that were created after last updateData-accountProducts // Please note, that every updateData-accountProducts that results in new products will // overwrite this timestamp, so it's expected that updateData-updated is run between the // sessions that produce new account products. // 2) Itemize all account products and confirm is isNew or Updates passes the condition // ... // In the future additional heuristics can be employed - such as using products, not just // account products and other. Currently they are considered as YAGNI var updateDataUpdatedStatus = await statusController.CreateAsync(status, "Process updated account products"); var addNewlyCreatedStatus = await statusController.CreateAsync(updateDataUpdatedStatus, "Add account products created since last data update"); var accountProductsNewOrUpdated = new List <long>(); //var lastUpdatedAccountProductsData = await activityContextCreatedIndexController.GetCreatedAsync( // activityContextController.ToString((A.UpdateData, Context.AccountProducts)), addNewlyCreatedStatus); //var newlyCreatedAccountProducts = await accountProductDataController.ItemizeAsync(lastUpdatedAccountProductsData, addNewlyCreatedStatus); //accountProductsNewOrUpdated.AddRange(newlyCreatedAccountProducts); await statusController.CompleteAsync(addNewlyCreatedStatus); var addUpdatedAccountProductsStatus = await statusController.CreateAsync(updateDataUpdatedStatus, "Add updated account products"); var current = 0; foreach (var id in await accountProductDataController.ItemizeAllAsync(addUpdatedAccountProductsStatus)) { await statusController.UpdateProgressAsync( addUpdatedAccountProductsStatus, ++current, await accountProductDataController.CountAsync(addUpdatedAccountProductsStatus), id.ToString()); var accountProduct = await accountProductDataController.GetByIdAsync(id, status); if (confirmAccountProductUpdatedDelegate.Confirm(accountProduct)) { accountProductsNewOrUpdated.Add(id); } } foreach (var accountProduct in accountProductsNewOrUpdated) { await updatedIndexController.CreateAsync(accountProduct, addUpdatedAccountProductsStatus); } await statusController.CompleteAsync(addUpdatedAccountProductsStatus); await updatedIndexController.CommitAsync(updateDataUpdatedStatus); await statusController.CompleteAsync(updateDataUpdatedStatus); }
public async Task <IFileValidationResult> ValidateFileAsync(string productFileUri, string validationUri, IStatus status) { var fileValidation = new FileValidation { Filename = productFileUri }; if (string.IsNullOrEmpty(productFileUri)) { throw new ArgumentNullException(); } if (string.IsNullOrEmpty(validationUri)) { throw new ArgumentNullException(); } fileValidation.ValidationExpected = confirmValidationExpectedDelegate.Confirm(productFileUri); if (!fileValidation.ValidationExpected) { return(fileValidation); } fileValidation.ValidationFileExists = VerifyValidationFileExists(validationUri); fileValidation.ProductFileExists = VerifyProductFileExists(productFileUri); if (!fileValidation.ValidationFileExists || !fileValidation.ProductFileExists) { return(fileValidation); } try { using (var xmlStream = streamController.OpenReadable(validationUri)) validationXml.Load(xmlStream); fileValidation.ValidationFileIsValid = true; } catch { fileValidation.ValidationFileIsValid = false; return(fileValidation); } var fileElement = validationXml.GetElementsByTagName("file"); if (fileElement == null || fileElement.Count < 1 || fileElement[0] == null || fileElement[0].Attributes == null) { fileValidation.ValidationFileIsValid = false; return(fileValidation); } long expectedSize; string expectedName; int chunks; bool available; try { expectedSize = long.Parse(fileElement[0].Attributes["total_size"]?.Value); expectedName = fileElement[0].Attributes["name"]?.Value; chunks = int.Parse(fileElement[0].Attributes["chunks"]?.Value); available = fileElement[0].Attributes["available"]?.Value == "1"; if (!available) { var notAvailableMessage = fileElement[0].Attributes["notavailablemsg"]?.Value; throw new Exception(notAvailableMessage); } } catch { fileValidation.ValidationFileIsValid = false; return(fileValidation); } fileValidation.FilenameVerified = VerifyFilename(productFileUri, expectedName); fileValidation.SizeVerified = VerifySize(productFileUri, expectedSize); var chunksValidation = new List <IChunkValidation>(); using (var fileStream = streamController.OpenReadable(productFileUri)) { long length = 0; foreach (XmlNode chunkElement in fileElement[0].ChildNodes) { if (chunkElement.Name != "chunk") { continue; } long from, to = 0; string expectedMd5 = string.Empty; from = long.Parse(chunkElement.Attributes["from"]?.Value); to = long.Parse(chunkElement.Attributes["to"]?.Value); length += (to - from); expectedMd5 = chunkElement.FirstChild.Value; chunksValidation.Add(await VerifyChunkAsync(fileStream, from, to, expectedMd5, status)); await statusController.UpdateProgressAsync( status, length, expectedSize, productFileUri, DataUnits.Bytes); } fileValidation.Chunks = chunksValidation.ToArray(); await statusController.UpdateProgressAsync(status, length, expectedSize, productFileUri); } await statusController.InformAsync(status, $"Validation result: {productFileUri} is valid: " + $"{validationResultController.ProductFileIsValid(fileValidation)}"); return(fileValidation); }
public async Task <GameDetails> GetDeserializedAsync(IStatus status, string uri, IDictionary <string, string> parameters = null) { // GOG.com quirk // GameDetails as sent by GOG.com servers have an intersting data structure for downloads: // it's represented as an array, where first element is a string with the language, // followed by actual download details, something like: // [ "English", { // download1 }, { // download2 } ] // Which of course is not a problem for JavaScript, but is a problem for // deserializing into strongly typed C# data structures. // To work around this we wrapped encapsulated usual network requests, // data transformation and desearialization in a sinlge package. // To process downloads we do the following: // - if response contains language downloads, signified by [[ // - extract actual language information and remove it from the string // - deserialize downloads into OperatingSystemsDownloads collection // - assign languages, since we know we should have as many downloads array as languages var data = await getResourceAsyncDelegate.GetResourceAsync(status, uri, parameters); var gameDetails = serializationController.Deserialize <GameDetails>(data); if (gameDetails == null) { return(null); } var gameDetailsLanguageDownloads = new List <OperatingSystemsDownloads>(); if (!confirmStringContainsLanguageDownloadsDelegate.Confirm(data)) { return(gameDetails); } var downloadStrings = itemizeGameDetailsDownloadsDelegate.Itemize(data); foreach (var downloadString in downloadStrings) { var downloadLanguages = itemizeDownloadLanguagesDelegate.Itemize(downloadString); if (downloadLanguages == null) { throw new InvalidOperationException("Cannot find any download languages or download language format changed."); } // ... and remove download lanugage strings from downloads var downloadsStringSansLanguages = replaceMultipleStringsDelegate.ReplaceMultiple( downloadString, string.Empty, downloadLanguages.ToArray()); // now it should be safe to deserialize langugage downloads var downloads = serializationController.Deserialize <OperatingSystemsDownloads[][]>( downloadsStringSansLanguages); // and convert GOG two-dimensional array of downloads to single-dimensional array var languageDownloads = convert2DArrayToArrayDelegate.Convert(downloads); if (languageDownloads.Count() != downloadLanguages.Count()) { throw new InvalidOperationException("Number of extracted language downloads doesn't match number of languages."); } // map language downloads with the language code we extracted earlier var languageDownloadIndex = 0; collectionController.Map(downloadLanguages, language => { var formattedLanguage = formatDownloadLanguageDelegate.Format(language); var languageCode = languageController.GetLanguageCode(formattedLanguage); languageDownloads[languageDownloadIndex++].Language = languageCode; }); gameDetailsLanguageDownloads.AddRange(languageDownloads); } gameDetails.LanguageDownloads = gameDetailsLanguageDownloads.ToArray(); return(gameDetails); }