Esempio n. 1
0
 /// <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);
Esempio n. 2
0
        /// <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();
            }
        }