diff --git a/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs b/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs index 89b3b6ea..49b0c5eb 100644 --- a/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs +++ b/implement/PersistentProcess/PersistentProcess.Common/ElmApp.cs @@ -44,10 +44,9 @@ static public bool FilePathMatchesPatternOfFilesInElmApp(string filePath) => .Where(file => 0 < file.filePath?.Length && FilePathMatchesPatternOfFilesInElmApp(file.filePath)); static public IImmutableDictionary, IImmutableList> ToFlatDictionaryWithPathComparer( - IEnumerable<(IImmutableList filePath, IImmutableList fileContent)> fileList) => - fileList.ToImmutableDictionary( - entry => entry.filePath, entry => entry.fileContent) - .WithComparers(EnumerableExtension.EqualityComparer()); + IEnumerable<(IImmutableList filePath, IImmutableList fileContent)> fileList) => + fileList.ToImmutableDictionary(entry => entry.filePath, entry => entry.fileContent) + .WithComparers(EnumerableExtension.EqualityComparer()); static public IImmutableDictionary, IImmutableList> AsCompletelyLoweredElmApp( IImmutableDictionary, IImmutableList> originalAppFiles, diff --git a/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs b/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs index 647e2391..b58cf5b1 100644 --- a/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs +++ b/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs @@ -1062,11 +1062,11 @@ public void tooling_supports_replicate_process_from_remote_host() using (var replicaHost = replicaSetup.StartWebHost()) { - elm_fullstack.Program.replicateProcess( + elm_fullstack.Program.replicateProcessAndLogToConsole( site: replicaAdminInterfaceUrl, sitePassword: replicaAdminPassword, - sourceSite: testSetup.AdminWebHostUrl, - sourceSitePassword: originalHostAdminPassword); + source: testSetup.AdminWebHostUrl, + sourcePassword: originalHostAdminPassword); } } } diff --git a/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs b/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs index 29ff652f..b573d57b 100644 --- a/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs +++ b/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs @@ -78,10 +78,11 @@ static Composition.TreeComponent SubtreeElmAppFromAppConfig(Composition.TreeComp this.lastSetElmAppStateResult = lastSetElmAppStateResult; } - static public IImmutableDictionary, IImmutableList> GetFilesForRestoreProcess( + static public (IImmutableDictionary, IImmutableList> files, string lastCompositionLogRecordHashBase16) + GetFilesForRestoreProcess( IFileStoreReader fileStoreReader) { - var filesForProcessRestore = new ConcurrentDictionary, IImmutableList>(); + var filesForProcessRestore = new ConcurrentDictionary, IImmutableList>(EnumerableExtension.EqualityComparer()); var recordingReader = new Kalmit.DelegatingFileStoreReader { @@ -105,9 +106,10 @@ dependencies on the store. */ using (var restoredProcess = Restore(new ProcessStoreReaderInFileStore(recordingReader), _ => { })) { + return ( + files: filesForProcessRestore.ToImmutableDictionary(EnumerableExtension.EqualityComparer()), + lastCompositionLogRecordHashBase16: restoredProcess.lastCompositionLogRecordHashBase16); } - - return filesForProcessRestore.ToImmutableDictionary(); } static IEnumerable<(CompositionLogRecordInFile compositionRecord, string compositionRecordHashBase16, LoadedReduction reduction)> @@ -171,7 +173,11 @@ static public PersistentProcessVolatileRepresentation Restore( logger?.Invoke("Begin to restore the process state."); - if (!storeReader.EnumerateSerializedCompositionLogRecordsReverse().Take(1).Any()) + var compositionEventsToLatestReductionReversed = + EnumerateCompositionLogRecordsForRestoreProcess(storeReader) + .ToImmutableList(); + + if (!compositionEventsToLatestReductionReversed.Any()) { logger?.Invoke("Found no composition record, default to initial state."); @@ -182,10 +188,6 @@ static public PersistentProcessVolatileRepresentation Restore( lastSetElmAppStateResult: null); } - var compositionEventsToLatestReductionReversed = - EnumerateCompositionLogRecordsForRestoreProcess(storeReader) - .ToImmutableList(); - logger?.Invoke("Found " + compositionEventsToLatestReductionReversed.Count + " composition log records to use for restore."); var firstCompositionEventRecord = diff --git a/implement/PersistentProcess/PersistentProcess.WebHost/ProcessStoreSupportingMigrations.cs b/implement/PersistentProcess/PersistentProcess.WebHost/ProcessStoreSupportingMigrations.cs index bd6fa74d..68de4ee3 100644 --- a/implement/PersistentProcess/PersistentProcess.WebHost/ProcessStoreSupportingMigrations.cs +++ b/implement/PersistentProcess/PersistentProcess.WebHost/ProcessStoreSupportingMigrations.cs @@ -54,8 +54,8 @@ static public (string parentHashBase16, IEnumerable<(IImmutableList file var fileStoreWriter = new DelegatingFileStoreWriter { SetFileContentDelegate = projectedFiles.Add, - AppendFileContentDelegate = _ => throw new Exception("Unexpeced operation append to file."), - DeleteFileDelegate = _ => throw new Exception("Unexpeced operation delete file."), + AppendFileContentDelegate = _ => throw new Exception("Unexpected operation append to file."), + DeleteFileDelegate = _ => throw new Exception("Unexpected operation delete file."), }; var processStoreWriter = new ProcessStoreWriterInFileStore(fileStoreWriter); diff --git a/implement/PersistentProcess/PersistentProcess.WebHost/Program.cs b/implement/PersistentProcess/PersistentProcess.WebHost/Program.cs index fe32d242..8463957a 100644 --- a/implement/PersistentProcess/PersistentProcess.WebHost/Program.cs +++ b/implement/PersistentProcess/PersistentProcess.WebHost/Program.cs @@ -2,6 +2,6 @@ namespace Kalmit.PersistentProcess.WebHost { public class Program { - static public string AppVersionId => "2020-05-12"; + static public string AppVersionId => "2020-05-16"; } } diff --git a/implement/PersistentProcess/PersistentProcess.WebHost/StartupAdminInterface.cs b/implement/PersistentProcess/PersistentProcess.WebHost/StartupAdminInterface.cs index 131b5d7a..b042cb58 100644 --- a/implement/PersistentProcess/PersistentProcess.WebHost/StartupAdminInterface.cs +++ b/implement/PersistentProcess/PersistentProcess.WebHost/StartupAdminInterface.cs @@ -531,7 +531,7 @@ TruncateProcessHistoryReport truncateProcessHistory(TimeSpan productionBlockDura var filesForRestore = PersistentProcess.PersistentProcessVolatileRepresentation.GetFilesForRestoreProcess( - processStoreFileStore) + processStoreFileStore).files .Select(filePathAndContent => filePathAndContent.Key) .ToImmutableHashSet(EnumerableExtension.EqualityComparer()); diff --git a/implement/elm-fullstack/Program.cs b/implement/elm-fullstack/Program.cs index ff523c70..2340fe22 100644 --- a/implement/elm-fullstack/Program.cs +++ b/implement/elm-fullstack/Program.cs @@ -109,11 +109,11 @@ CommandOption verboseLogOptionFromCommand(CommandLineApplication command) => if (replicateProcessSource != null) { - var filesForRestore = readFilesForRestoreProcessFromAdminInterface( - sourceAdminInterface: replicateProcessSource, - sourceAdminRootPassword: replicateProcessAdminPassword); + var replicateFiles = + LoadFilesForRestoreFromSourceAndLogToConsole( + replicateProcessSource, replicateProcessAdminPassword); - foreach (var file in filesForRestore) + foreach (var file in replicateFiles) processStoreFileStore.SetFileContent(file.Key, file.Value.ToArray()); } @@ -249,6 +249,35 @@ CommandOption verboseLogOptionFromCommand(CommandLineApplication command) => }); }); + app.Command("archive-process", archiveProcessCmd => + { + archiveProcessCmd.Description = "Copy the files needed to restore the process and store those in a zip-archive."; + archiveProcessCmd.ThrowOnUnexpectedArgument = true; + + var siteAndPasswordFromCmd = siteAndSitePasswordOptionsOnCommand(archiveProcessCmd); + + archiveProcessCmd.OnExecute(() => + { + var (site, sitePassword) = siteAndPasswordFromCmd(); + + Console.WriteLine("Begin reading process history from '" + site + "' ..."); + + var restoreResult = + readFilesForRestoreProcessFromAdminInterface(site, sitePassword); + + Console.WriteLine("Completed reading files to restore process " + restoreResult.lastCompositionLogRecordHashBase16 + ". Read " + restoreResult.files.Count + " files from '" + site + "'."); + + var zipArchive = ZipArchive.ZipArchiveFromEntries(restoreResult.files); + + var fileName = "process-" + restoreResult.lastCompositionLogRecordHashBase16 + ".zip"; + var filePath = Path.Combine(Environment.CurrentDirectory, fileName); + + File.WriteAllBytes(filePath, zipArchive); + + Console.WriteLine("Saved process archive to file '" + filePath + "'."); + }); + }); + app.Command("user-secrets", userSecretsCmd => { userSecretsCmd.Description = "Manage passwords for accessing the admin interfaces of servers."; @@ -548,7 +577,7 @@ static TruncateProcessHistoryReport truncateProcessHistory(string site, string s } } - static IImmutableDictionary, IImmutableList> readFilesForRestoreProcessFromAdminInterface( + static (IImmutableDictionary, IImmutableList> files, string lastCompositionLogRecordHashBase16) readFilesForRestoreProcessFromAdminInterface( string sourceAdminInterface, string sourceAdminRootPassword) { @@ -583,26 +612,51 @@ static IImmutableDictionary, IImmutableList> readFi } } - static public void replicateProcess( - string site, - string sitePassword, - string sourceSite, - string sourceSitePassword) + static IImmutableDictionary, IImmutableList> LoadFilesForRestoreFromSourceAndLogToConsole( + string source, string sourcePassword) { - Console.WriteLine("Begin reading process history from '" + sourceSite + "' ..."); + if (Regex.IsMatch(source, "http(|s)://", RegexOptions.IgnoreCase)) + { + Console.WriteLine("Begin reading process history from '" + source + "' ..."); + + var restoreResult = readFilesForRestoreProcessFromAdminInterface( + sourceAdminInterface: source, + sourceAdminRootPassword: sourcePassword); + + Console.WriteLine("Completed reading files to restore process " + restoreResult.lastCompositionLogRecordHashBase16 + ". Read " + restoreResult.files.Count + " files from '" + source + "'."); + + return restoreResult.files; + } + + var archive = File.ReadAllBytes(source); - var processHistoryfilesFromRemoteHost = - readFilesForRestoreProcessFromAdminInterface(sourceSite, sourceSitePassword); + var zipArchiveEntries = ZipArchive.EntriesFromZipArchive(archive); + + return + ElmApp.ToFlatDictionaryWithPathComparer( + Composition.TreeFromSetOfBlobsWithCommonFilePath(zipArchiveEntries) + .EnumerateBlobsTransitive() + .Select(blobPathAndContent => ( + fileName: (IImmutableList)blobPathAndContent.path.Select(name => System.Text.Encoding.UTF8.GetString(name.ToArray())).ToImmutableList(), + fileContent: blobPathAndContent.blobContent))); + } - Console.WriteLine("Completed reading part of process history for restore. Read " + processHistoryfilesFromRemoteHost.Count + " files from " + sourceSite + " during restore."); + static public void replicateProcessAndLogToConsole( + string site, + string sitePassword, + string source, + string sourcePassword) + { + var restoreFiles = + LoadFilesForRestoreFromSourceAndLogToConsole(source, sourcePassword); var processHistoryTree = - Composition.TreeFromSetOfBlobsWithStringPath(processHistoryfilesFromRemoteHost); + Composition.TreeFromSetOfBlobsWithStringPath(restoreFiles); var processHistoryComponentHash = Composition.GetHash(Composition.FromTree(processHistoryTree)); var processHistoryComponentHashBase16 = CommonConversion.StringBase16FromByteArray(processHistoryComponentHash); - var processHistoryZipArchive = ZipArchive.ZipArchiveFromEntries(processHistoryfilesFromRemoteHost); + var processHistoryZipArchive = ZipArchive.ZipArchiveFromEntries(restoreFiles); using (var httpClient = new System.Net.Http.HttpClient()) { diff --git a/implement/elm-fullstack/elm-fullstack.csproj b/implement/elm-fullstack/elm-fullstack.csproj index f3d82acf..dcb69258 100644 --- a/implement/elm-fullstack/elm-fullstack.csproj +++ b/implement/elm-fullstack/elm-fullstack.csproj @@ -5,8 +5,8 @@ netcoreapp3.1 elm_fullstack elm-fullstack - 2020.0512.0.0 - 2020.0512.0.0 + 2020.0516.0.0 + 2020.0516.0.0