/// <summary> /// Method called when saving. By default, will save the Content object to contents.json /// </summary> /// <param name="writer">Package writer.</param> protected abstract Task OnWrite(PackageWriter writer);
/// <summary> /// Core save functionality. /// </summary> /// <param name="autoSave"> /// Set to true to bypass saving to the SavePath and instead save the current package to the temp directory. /// </param> /// <returns>Result of saving</returns> private async Task <PackageSaveResult> SaveInternal(bool autoSave) { Logger?.ConditionalTrace($"SaveInternal(autoSave:{autoSave})"); PackageSaveResult returnValue = null; try { // TODO: Reuse existing archive when saving from a read-only package. Prevents duplication of the MS. var saveArchiveMemoryStream = new MemoryStream(); using (var archive = new ZipArchive(saveArchiveMemoryStream, ZipArchiveMode.Create, true)) { var writer = new PackageWriter(archive, JsonSerializerOptions, _appName); // Write application version file await using (var packageVersionStream = writer.CreateEntityStream("version", false)) { await using var packageVersionStreamWriter = new StreamWriter(packageVersionStream); await packageVersionStreamWriter.WriteAsync(CurrentPkgVersion.ToString()); } await OnWrite(writer); // Write package version file await writer.Write("version", CurrentAppVersion.ToString()); var log = new ChangelogEntry (autoSave ? ChangelogEntryType.AutoSave : ChangelogEntryType.Save, Username, ComputerName, CurrentDateTimeOffset); // Update the save log. _changelog.Add(log); // Write the save log. await writer.WriteJson("changelog.json", _changelog); // If this is an auto save, we do not want to continually add auto save logs. if (autoSave) { _changelog.Remove(log); } if (_openArchive != null) { foreach (var openedArchiveEntry in _openArchive.Entries) { if (writer.FileList.Contains(openedArchiveEntry.FullName)) { continue; } var saveEntry = archive.CreateEntry(openedArchiveEntry.FullName); // Copy the last write times to be accurate. saveEntry.LastWriteTime = openedArchiveEntry.LastWriteTime; // Copy the streams. await using var openedArchiveStream = openedArchiveEntry.Open(); await using var saveEntryStream = saveEntry.Open(); await openedArchiveStream.CopyToAsync(saveEntryStream); } } } // Only save a backup package if we are not performing an auto save. if (SaveBackupPackage && !autoSave) { Logger?.ConditionalTrace("Saving backup package."); var bakPackage = SavePath + ".bak"; try { Logger?.ConditionalTrace($"Checking for existence of {bakPackage} backup ."); if (File.Exists(bakPackage)) { Logger?.ConditionalTrace("Found backup package."); File.SetAttributes(bakPackage, FileAttributes.Normal); File.Delete(bakPackage); Logger?.ConditionalTrace("Deleted backup package."); } // Close the lock held on the file. On initial save, there is nothing to close. _openPackageStream?.Close(); _openPackageStream = null; Logger?.ConditionalTrace("Closed _openPackageStream."); Logger?.ConditionalTrace($"Checking for existence of existing save package {SavePath}"); if (File.Exists(SavePath)) { File.Move(SavePath, bakPackage); Logger?.ConditionalTrace($"Found existing save package and renamed to {bakPackage}"); } } catch (Exception e) { return(returnValue = new PackageSaveResult(PackageSaveResultType.Failure, e)); } } if (!autoSave) { // Create a new package/overwrite existing if a backup was not created. // Retain the lock on the package. if (_openPackageStream == null) { // Create a new source package. _openPackageStream = new FileStream(SavePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); } else { // Set the package file length to 0 to reset for writing. _openPackageStream.Position = 0; _openPackageStream.SetLength(0); } } if (autoSave) { Logger?.ConditionalTrace($"Creating AutoSave package {AutoSavePath}."); } var destinationStream = autoSave ? File.Create(AutoSavePath) : _openPackageStream; if (autoSave) { Logger?.ConditionalTrace($"Created AutoSave package {AutoSavePath}."); } if (destinationStream == null) { Logger?.ConditionalTrace("Could not create/use destination stream."); return(returnValue = new PackageSaveResult(PackageSaveResultType.Failure)); } saveArchiveMemoryStream.Seek(0, SeekOrigin.Begin); // Copy and flush the contents of the save file. await saveArchiveMemoryStream.CopyToAsync(destinationStream); await destinationStream.FlushAsync(); // If we were auto-saving, close the destination save and return true. // We do not want to effect the current state of the rest of the package. if (autoSave) { destinationStream.Close(); Logger?.ConditionalTrace("Closed AutoSave destination stream."); return(returnValue = PackageSaveResult.Success); } saveArchiveMemoryStream.Seek(0, SeekOrigin.Begin); // This implicitly closes the inner stream. _openArchive?.Dispose(); // Save the new zip archive stream to the opened archive stream. _openArchive = new ZipArchive(saveArchiveMemoryStream, ZipArchiveMode.Read, true); PackageAppVersion = CurrentAppVersion; return(returnValue = PackageSaveResult.Success); } catch (Exception e) { // Catch all for anything going completely crazy. return(returnValue = new PackageSaveResult(PackageSaveResultType.Failure, e)); } finally { // If we were successful in saving, set the modified variable to false. // If we are auto-saving, don't change these variables since the package still needs // to actually be saved to the destination. if (returnValue == PackageSaveResult.Success && autoSave == false) { IsContentModified = false; // Since the package was saved, the package is no longer in read-only mode. IsReadOnly = false; } Logger?.ConditionalTrace($"InternalSave return: {returnValue}"); Logger?.ConditionalTrace("Released _packageOperationSemaphore"); _packageOperationSemaphore.Release(); } }