public (Script[], LogInfo[]) UpdateScripts(IEnumerable <Script> scripts, bool preserveScriptState) { // Get updateable scripts urls Script[] updateableScripts = scripts.Where(s => s.IsUpdateable).ToArray(); List <Script> newScripts = new List <Script>(updateableScripts.Length); List <LogInfo> logs = new List <LogInfo>(updateableScripts.Length); if (_m != null) { _m.BuildScriptProgressVisibility = Visibility.Collapsed; } _m?.SetBuildCommandProgress("Download Progress", updateableScripts.Length); try { int i = 0; foreach (Script sc in updateableScripts) { i++; ScriptStateBackup stateBackup = null; if (preserveScriptState) { stateBackup = BackupScriptState(sc); Debug.Assert(stateBackup != null, "ScriptStateBackup is null"); } if (_m != null) { _m.BuildEchoMessage = $"Updating script [{sc.Title}]... ({i}/{updateableScripts.Length})"; } ResultReport <Script> report = InternalUpdateOneScript(sc, stateBackup); if (report.Success && report.Result != null) { newScripts.Add(report.Result); } logs.Add(report.ToLogInfo()); if (_m != null) { _m.BuildCommandProgressValue += 1; } } } finally { _m?.ResetBuildCommandProgress(); if (_m != null) { _m.BuildEchoMessage = string.Empty; _m.BuildScriptProgressVisibility = Visibility.Visible; } } return(newScripts.ToArray(), logs.ToArray()); }
/// <summary> /// To the best-effort to restore script state /// </summary> /// <remarks> /// This method does not refresh the Script instance, it is the responsibility of a callee /// </remarks> private static void RestoreScriptState(Script sc, ScriptStateBackup backup) { List <string> ifaceSectionNames = sc.GetInterfaceSectionNames(false); // Restore selected state IniReadWriter.WriteKey(sc.RealPath, ScriptSection.Names.Main, Script.Const.Selected, backup.Selected.ToString()); // Restore interfaces List <UIControl> newCtrls = new List <UIControl>(); foreach (var kv in backup.IfaceSectionDict) { string ifaceSectionName = kv.Key; List <UIControl> bakCtrls = kv.Value; if (!ifaceSectionNames.Contains(ifaceSectionName)) { continue; } (List <UIControl> uiCtrls, _) = sc.GetInterfaceControls(ifaceSectionName); foreach (UIControl uiCtrl in uiCtrls) { // Get old uiCtrl, equality identified by Type and Key. UIControl bakCtrl = bakCtrls.FirstOrDefault(bak => bak.Type == uiCtrl.Type && bak.Key.Equals(uiCtrl.Key, StringComparison.OrdinalIgnoreCase)); if (bakCtrl == null) { continue; } // Get old value string bakValue = bakCtrl.GetValue(false); Debug.Assert(bakValue != null, "Internal Logic Error at FileUpdater.RestoreInterface"); // Add to newCtrls only if apply was successful if (uiCtrl.SetValue(bakValue, false, out _)) { newCtrls.Add(uiCtrl); } } } // Write to file UIControl.Update(newCtrls); }
public (Script, LogInfo) UpdateScript(Script sc, bool preserveScriptState) { if (!sc.IsUpdateable) { return(null, new LogInfo(LogState.Error, $"Script [{sc.Title} is not updateable")); } // Backup interface state of original script ScriptStateBackup stateBackup = null; if (preserveScriptState) { stateBackup = BackupScriptState(sc); Debug.Assert(stateBackup != null, "ScriptStateBackup is null"); } ResultReport <Script> report; _m?.SetBuildCommandProgress("Download Progress", 1); try { if (_m != null) { _m.BuildEchoMessage = $"Updating script [{sc.Title}]..."; } report = InternalUpdateOneScript(sc, stateBackup); if (_m != null) { _m.BuildCommandProgressValue = 1; } } finally { _m?.ResetBuildCommandProgress(); if (_m != null) { _m.BuildEchoMessage = string.Empty; } } return(report.Result, report.ToLogInfo()); }
private ResultReport <Script> InternalUpdateOneScript(Script sc, ScriptStateBackup stateBackup) { // Never should be triggered, because Script class constructor check it Debug.Assert(sc.ParsedVersion != null, $"Local script [{sc.Title}] does not provide proper version information"); string updateUrl = sc.UpdateUrl; string metaJsonUrl = Path.ChangeExtension(updateUrl, ".meta.json"); string metaJsonFile = FileHelper.GetTempFile(".meta.json"); string tempScriptFile = FileHelper.GetTempFile(".script"); try { // Download .meta.json HttpFileDownloader.Report httpReport = DownloadFile(metaJsonUrl, metaJsonFile); if (!httpReport.Result) { // Failed to send a request, such as network not available if (httpReport.StatusCode == 0) { return(new ResultReport <Script>(false, null, $"Unable to connect to the server")); } // Try downloading .deleted to check if a script is deleted string errorMsg; string deletedUrl = Path.ChangeExtension(updateUrl, ".deleted"); string deletedFile = Path.ChangeExtension(metaJsonFile, ".deleted"); try { httpReport = DownloadFile(deletedUrl, deletedFile); if (httpReport.Result) { // Successfully received response if (httpReport.StatusCode == 200) // .deleted file exists in the server { errorMsg = $"[{sc.Title}] was deleted from the server"; } else // There is no .deleted file in the server { errorMsg = $"Update is not available for [{sc.Title}]"; } } else { if (httpReport.StatusCode == 0) // Failed to send a request, such as network not available { errorMsg = $"Unable to connect to the server"; } else { errorMsg = $"Update is not available for [{sc.Title}]"; } } } finally { if (File.Exists(deletedFile)) { File.Delete(deletedFile); } } return(new ResultReport <Script>(false, null, errorMsg)); } // Check and read .meta.json ResultReport <UpdateJson.Root> jsonReport = UpdateJson.ReadUpdateJson(metaJsonFile); if (!jsonReport.Success) { return(new ResultReport <Script>(false, null, jsonReport.Message)); } UpdateJson.Root metaJson = jsonReport.Result; UpdateJson.FileIndex index = metaJson.Index; if (index.Kind != UpdateJson.IndexEntryKind.Script) { return(new ResultReport <Script>(false, null, "Update json is not of a script file")); } UpdateJson.ScriptInfo scInfo = index.ScriptInfo; if (scInfo.Format != UpdateJson.ScriptFormat.IniBased) { return(new ResultReport <Script>(false, null, $"Format [{scInfo.Format}] of remote script [{sc.Title}] is not supported")); } UpdateJson.IniBasedScript iniScInfo = scInfo.IniBased; if (iniScInfo.Version <= sc.ParsedVersion) { return(new ResultReport <Script>(false, null, $"You are using the lastest version of script [{sc.Title}]")); } // Download .script file httpReport = DownloadFile(updateUrl, tempScriptFile); if (!httpReport.Result) { return(new ResultReport <Script>(false, null, httpReport.ErrorMsg)); } // Verify downloaded .script file with FileMetadata ResultReport verifyReport = index.FileMetadata.VerifyFile(tempScriptFile); if (!verifyReport.Success) { return(new ResultReport <Script>(false, null, $"Remote script [{sc.Title}] is corrupted")); } // Check downloaded script's version and check // Must have been checked with the UpdateJson string remoteVerStr = IniReadWriter.ReadKey(tempScriptFile, "Main", "Version"); VersionEx remoteVer = VersionEx.Parse(remoteVerStr); if (remoteVer == null) { return(new ResultReport <Script>(false, null, $"Version of remote script [{sc.Title}] is corrupted")); } if (!remoteVer.Equals(iniScInfo.Version)) { return(new ResultReport <Script>(false, null, $"Version of remote script [{sc.Title}] is corrupted")); } if (remoteVer <= sc.ParsedVersion) { return(new ResultReport <Script>(false, null, $"Version of remote script [{sc.Title}] is corrupted")); } // Check if remote script is valid Script remoteScript = _p.LoadScriptRuntime(tempScriptFile, new LoadScriptRuntimeOptions { IgnoreMain = false, AddToProjectTree = false, OverwriteToProjectTree = false, }); if (remoteScript == null) { return(new ResultReport <Script>(false, null, $"Remote script [{sc.Title}] is corrupted")); } // Overwrite backup state to new script if (stateBackup != null) { RestoreScriptState(remoteScript, stateBackup); // Let's be extra careful remoteScript = _p.RefreshScript(remoteScript); if (remoteScript == null) { return(new ResultReport <Script>(false, null, $"Internal error at {nameof(FileUpdater)}.{nameof(RestoreScriptState)}")); } } // Copy downloaded remote script into new script File.Copy(tempScriptFile, sc.DirectRealPath, true); Script newScript = _p.RefreshScript(sc); // Return updated script instance return(new ResultReport <Script>(true, newScript, $"Updated script [{sc.Title}] to [v{sc.RawVersion}] from [v{newScript.RawVersion}]")); } finally { if (File.Exists(metaJsonFile)) { File.Delete(metaJsonFile); } if (File.Exists(tempScriptFile)) { File.Delete(tempScriptFile); } } }