/// <summary> /// Processes valid assets to know which buttons to display /// </summary> /// <returns></returns> public async Task ProcessAssets(ContentType storageType, string pathToCheckWith, string storageLocationBase, FGORegion region) { List <string> validSha = new List <string>(); _translations?.Clear(); _guiObjects?.Clear(); var installedScriptString = Preferences.Get($"InstalledScript_{region}", null); if (installedScriptString != null) { _installedBundle = JsonConvert.DeserializeObject <TranslationList>(installedScriptString); foreach (var scriptBundle in _installedBundle.Scripts) { validSha.Add(scriptBundle.Value.TranslatedSHA1); // Add existing } } else { _installedBundle = null; } foreach (var scriptBundleSet in _handshake.Response.Translations) { foreach (var scriptBundle in scriptBundleSet.Scripts) { validSha.Add(scriptBundle.Value.TranslatedSHA1); // Add all valid sha1s } } if (_handshake.Response.Translations.Count > 0) { foreach (var scriptBundleSet in _handshake.Response.Translations) { ConcurrentBag <Tuple <long, long> > results = new ConcurrentBag <Tuple <long, long> >(); await scriptBundleSet.Scripts.ParallelForEachAsync(async scriptBundle => { // Check hashes var filePath = Path.Combine(pathToCheckWith, scriptBundle.Key); var fileContentsResult = await _cm.GetFileContents(storageType, filePath, storageLocationBase); if (scriptBundle.Key.Contains('/') || scriptBundle.Key.Contains('\\')) // for security purposes, don't allow directory traversal { throw new FileNotFoundException(); } TranslationFileStatus status; var fileNotExists = fileContentsResult.Error == FileErrorCode.NotExists; if (!fileNotExists) { var sha1 = ScriptUtil.Sha1(fileContentsResult.FileContents); // SHA of file currently in use if (sha1 == scriptBundle.Value.GameSHA1) // Not modified { status = TranslationFileStatus.NotModified; } else if (sha1 == scriptBundle.Value.TranslatedSHA1) // English is installed { status = TranslationFileStatus.Translated; } else if (validSha.Contains(sha1) && (_installedBundle == null || (_installedBundle != null && scriptBundleSet.Group != _installedBundle.Group))) { status = TranslationFileStatus.DifferentTranslation; } else if (_installedBundle != null && scriptBundleSet.Group == _installedBundle.Group) { status = TranslationFileStatus.UpdateAvailable; } else { status = TranslationFileStatus.Invalid; } } else { status = TranslationFileStatus.Missing; } scriptBundle.Value.Status = status; results.Add(new Tuple <long, long>(scriptBundle.Value.LastModified, scriptBundle.Value.Size)); }, maxDegreeOfParallelism : 4); long lastModified = results.Max(m => m.Item1); long totalSize = results.Sum(s => s.Item2); scriptBundleSet.TotalSize = totalSize; _translations.Add(scriptBundleSet.Group, scriptBundleSet); var statusString = InstallerUtil.GenerateStatusString(scriptBundleSet.Scripts); var timespan = DateTime.UtcNow.Subtract(DateTime.SpecifyKind(DateTimeOffset.FromUnixTimeSeconds((long)lastModified).DateTime, DateTimeKind.Utc)); var lastUpdated = InstallerUtil.PeriodOfTimeOutput(timespan); bool enableButton = statusString.Item1 != AppResources.StatusInstalled; var i1 = scriptBundleSet.Group; if (!scriptBundleSet.Hidden) { _guiObjects.Add(new TranslationGUIObject() { BundleID = scriptBundleSet.Group, BundleHidden = scriptBundleSet.Hidden, InstallEnabled = enableButton, InstallClick = new Command(async() => await Install(region, i1), () => enableButton && ButtonsEnabled), Name = scriptBundleSet.Name, Status = statusString.Item1, TextColor = statusString.Item2, LastUpdated = lastUpdated, ButtonInstallText = statusString.Item1 != AppResources.StatusInstalled ? AppResources.InstallText : AppResources.InstalledText }); } } LoadTranslationList(); } else { LoadingText.Text = String.Format(AppResources.NoScriptsAvailable); SwitchErrorObjects(true); return; } }
public async Task UpdateTranslationList() { _cm.ClearCache(); if (_isCurrentlyUpdating) { return; } _isCurrentlyUpdating = true; try { ReleaseScheduleLayout.IsVisible = false; RetryButton.IsVisible = false; LoadingLayout.IsVisible = true; TranslationListView.IsVisible = false; MasterButtons.IsVisible = false; Refresh.IsRefreshing = true; //SwitchButtons(false); SwitchErrorObjects(false); ActivityIndicatorLoading.IsRunning = true; Refresh.IsEnabled = false; RevertButton.Text = AppResources.UninstallText; ActivityIndicatorLoading.VerticalOptions = LayoutOptions.Center; ActivityIndicatorLoading.HorizontalOptions = LayoutOptions.Center; LoadingText.Text = AppResources.LoadingPleaseWait; LoadingText.VerticalOptions = LayoutOptions.CenterAndExpand; LoadingText.HorizontalOptions = LayoutOptions.CenterAndExpand; //await Task.Delay(1000); _storageLocation = Preferences.Get("StorageLocation", ""); if (!string.IsNullOrEmpty(_storageLocation) || !_cm.CheckBasicAccess()) { _android11Access = true; } if (!_android11Access) { _installedFgoInstances = _cm.GetInstalledGameApps(ContentType.DirectAccess); } else { _installedFgoInstances = _cm.GetInstalledGameApps(ContentType.StorageFramework, _storageLocation); } //TranslationName.Text = Region == FGORegion.Jp //? String.Format(AppResources.InstallerTitle, "JP") + $": {handshake.Data.Response.AppVer}" //: String.Format(AppResources.InstallerTitle, "NA") + $": {handshake.Data.Response.AppVer}"; // Check region is installed if (_installedFgoInstances.All(w => w.Region != Region)) { LoadingText.Text = String.Format(AppResources.NoFGOFound, $"Fate/Grand Order {Region.ToString().ToUpper()}"); SwitchErrorObjects(true); return; } foreach (var instance in _installedFgoInstances.ToList()) { var filePath = _android11Access ? $"data/{instance.Path}/files/data/d713/{_assetList}" : $"{instance.Path}/files/data/d713/{_assetList}"; var assetStorage = await _cm.GetFileContents( _android11Access?ContentType.StorageFramework : ContentType.DirectAccess, filePath, _storageLocation); if (!assetStorage.Successful) { _installedFgoInstances.Remove(instance); } instance.LastModified = assetStorage.LastModified; if (assetStorage?.FileContents != null) { var base64 = ""; using var inputStream = new MemoryStream(assetStorage.FileContents); using (var reader = new StreamReader(inputStream, Encoding.ASCII)) { base64 = await reader.ReadToEndAsync(); } instance.AssetStorage = base64; } else { instance.AssetStorage = null; } } var instanceDict = _installedFgoInstances.OrderByDescending(o => o.LastModified).FirstOrDefault(w => w.Region == Region); if (instanceDict == null) { LoadingText.Text = String.Format(AppResources.NoFGOInstallationFound2, $"Fate/Grand Order {Region.ToString().ToUpper()}"); SwitchErrorObjects(true); return; } var rest = new RestfulAPI(); var handshake = await rest.GetHandshakeApiResponse(Region, instanceDict.AssetStorage); _handshake = handshake.Data; if (handshake.Data == null || handshake.Data.Status != 200) { LoadingText.Text = handshake.Data == null ? AppResources.TryAgainLater : $"{AppResources.TranslateAPIError}\n{handshake.Data?.Message}"; SwitchErrorObjects(true); return; } if (handshake.Data.Response.AssetStatus != HandshakeAssetStatus.Missing && handshake.Data.Response.AssetStatus != HandshakeAssetStatus.UpToDate) { var warningTitle = AppResources.Warning + $" ({Region.ToString().ToUpper()})"; switch (handshake.Data.Response.AssetStatus) { case HandshakeAssetStatus.Missing: break; case HandshakeAssetStatus.UpToDate: break; case HandshakeAssetStatus.UpdateRequired: await DisplayAlert(warningTitle, AppResources.AssetWarningOutOfDate, AppResources.OK); break; case HandshakeAssetStatus.TimeTraveler: await DisplayAlert(warningTitle, AppResources.AssetWarningFutureUnreleased, AppResources.OK); break; case HandshakeAssetStatus.Unrecognized: await DisplayAlert(warningTitle, AppResources.AssetWarningUnrecognised, AppResources.OK); break; case HandshakeAssetStatus.Corrupt: await DisplayAlert(warningTitle, AppResources.AssetWarningCorrupted, AppResources.OK); break; default: throw new ArgumentOutOfRangeException(); } } var baseFilePath = _android11Access ? $"data/{instanceDict.Path}/files/data/d713/" : $"{instanceDict.Path}/files/data/d713/"; await ProcessAssets(_android11Access?ContentType.StorageFramework : ContentType.DirectAccess, baseFilePath, _storageLocation, Region); // Add top bar if (_handshake.Response.LiveStatus != null && _handshake.Response.LiveStatus.Enabled) { ReleaseScheduleLayout.IsVisible = true; ReleaseScheduleChapter.Text = _handshake.Response.LiveStatus.CurrentRelease; if (_handshake.Response.LiveStatus.NextReleaseDate < DateTime.UtcNow) { DisplayNextUpdateTime.IsVisible = false; } else { var timespan = _handshake.Response.LiveStatus.NextReleaseDate.Subtract(DateTime.UtcNow); var lastUpdated = InstallerUtil.PeriodOfTimeOutput(timespan, 0, ""); ReleaseScheduleTimeRemaining.Text = lastUpdated; } ReleaseSchedulePercent.Text = _handshake.Response.LiveStatus.PercentDone; ReleaseScheduleTitle.Text = _handshake.Response.LiveStatus.Title; var announcementJson = Preferences.Get("AnnouncementData", null); if (announcementJson != null) { var json = JsonConvert.DeserializeObject <VersionAPIResponse.TranslationAnnouncements>( announcementJson); if (json.IsSpecialAnnouncement) { ReleaseTap.Tapped += OpenAnnouncementOnClicked; } } } LoadingLayout.IsVisible = false; TranslationListView.IsVisible = true; MasterButtons.IsVisible = true; Refresh.IsRefreshing = false; SwitchErrorObjects(false); ActivityIndicatorLoading.IsRunning = false; Refresh.IsEnabled = true; SwitchButtons(true); } catch (Exception ex) { await DisplayAlert(AppResources.InternalError, String.Format(AppResources.InternalErrorDetails, ex.ToString()), AppResources.OK); _isCurrentlyUpdating = false; SwitchErrorObjects(true); } _isCurrentlyUpdating = false; }