Example #1
0
        private async Task RollBackReadMeAsync(PackageEdit edit, string directory, string originalReadMePath,
                                               CloudBlockBlob activeReadMeSnapshot, CloudBlockBlob activeReadMeBlob)
        {
            if (edit.ReadMeState != null)
            {
                if (edit.ReadMeState == ReadMeChanged)
                {
                    if (edit.HasReadMe)
                    {
                        Logger.LogWarning(
                            "Rolling back ReadMe blob for {EditId} {EditVersion}. Copying snapshot {ActiveReadMeSnapshotUri} to {ActiveReadMeUri}", edit.Id, edit.Version, activeReadMeSnapshot.Uri.AbsoluteUri, activeReadMeBlob.Uri.AbsoluteUri);
                        activeReadMeBlob.StartCopy(activeReadMeSnapshot);
                        while (activeReadMeBlob.CopyState.Status != CopyStatus.Success)
                        {
                            await Task.Delay(1000);
                        }
                        Logger.LogWarning(
                            "Rolled back ReadMe blob for {EditId} {EditVersion}. Copying snapshot {ActiveReadMeSnapshotUri} to {ActiveReadMeUri}",
                            edit.Id, edit.Version, activeReadMeSnapshot.Uri.AbsoluteUri, activeReadMeBlob.Uri.AbsoluteUri);
                    }
                    else
                    {
                        // Delete ReadMes from active
                        Logger.LogInformation("Deleting ReadMe of {EditId} {EditVersion} from {ActiveReadMeUri}",
                                              edit.Id, edit.Version, activeReadMeBlob.Uri.AbsoluteUri);
                        await activeReadMeBlob.DeleteIfExistsAsync();

                        Logger.LogInformation("Deleted ReadMe of {EditId} {EditVersion} from {ActiveReadMeUri}",
                                              edit.Id, edit.Version, activeReadMeBlob.Uri.AbsoluteUri);
                    }
                }
                else if (edit.ReadMeState == ReadMeDeleted)
                {
                    try
                    {
                        // Upload original ReadMe back to active
                        Logger.LogInformation("Uploading old ReadMe for {EditId} {EditVersion} to {ActiveReadMeUri}",
                                              edit.Id, edit.Version, activeReadMeBlob.Uri.AbsoluteUri);
                        await activeReadMeBlob.UploadFromFileAsync(originalReadMePath);

                        Logger.LogInformation("Uploaded old ReadMe for {EditId} {EditVersion} to {ActiveReadMeUri}",
                                              edit.Id, edit.Version, activeReadMeBlob.Uri.AbsoluteUri);
                    }
                    finally
                    {
                        if (!string.IsNullOrEmpty(originalReadMePath) && File.Exists(originalReadMePath))
                        {
                            File.Delete(originalReadMePath);
                        }
                    }
                }
            }
        }
Example #2
0
        private async Task UpdateDatabaseWithEdit(PackageEdit edit, string hash, long size)
        {
            // insert missing authors as empty in authors table for consistency with gallery
            // scenario is metadata edit during verification of package uploaded without authors
            if (string.IsNullOrWhiteSpace(edit.Authors))
            {
                edit.Authors = string.Empty;
            }

            using (var connection = await PackageDatabase.ConnectTo())
            {
                var parameters = new DynamicParameters(new
                {
                    edit.Authors,
                    edit.Copyright,
                    edit.Description,
                    edit.IconUrl,
                    edit.LicenseUrl,
                    edit.ProjectUrl,
                    edit.ReleaseNotes,
                    edit.RequiresLicenseAcceptance,
                    edit.Summary,
                    edit.Title,
                    edit.Tags,
                    edit.Key,
                    edit.PackageKey,
                    edit.UserKey,
                    PackageFileSize = size,
                    Hash            = hash,
                    HashAlgorithm   = HashAlgorithmName
                });

                // Prep SQL for merging in authors
                var loadAuthorsSql = new StringBuilder();
                var authors        = edit.Authors.Split(',');
                for (var i = 0; i < authors.Length; i++)
                {
                    loadAuthorsSql.Append("INSERT INTO [PackageAuthors]([PackageKey],[Name]) VALUES(@PackageKey, @Author" + i + ")");
                    parameters.Add("Author" + i, authors[i]);
                }

                await connection.QueryAsync <int>(@"
                            BEGIN TRANSACTION
                                -- Form a comma-separated list of authors
                                DECLARE @existingAuthors nvarchar(MAX)
                                SELECT @existingAuthors = COALESCE(@existingAuthors + ',', '') + Name
                                FROM PackageAuthors
                                WHERE PackageKey = @PackageKey

                                -- Copy packages data to package history table
                                INSERT INTO [PackageHistories]
                                SELECT      [Key] AS PackageKey,
                                            @UserKey AS UserKey,
                                            GETUTCDATE() AS Timestamp,
                                            Title,
                                            @existingAuthors AS Authors,
                                            Copyright,
                                            Description,
                                            IconUrl,
                                            LicenseUrl,
                                            ProjectUrl,
                                            ReleaseNotes,
                                            RequiresLicenseAcceptance,
                                            Summary,
                                            Tags,
                                            Hash,
                                            HashAlgorithm,
                                            PackageFileSize,
                                            LastUpdated,
                                            Published
                                FROM        [Packages]
                                WHERE       [Key] = @PackageKey

                                -- Update the packages table
                                UPDATE  [Packages]
                                SET     Copyright = @Copyright,
                                        Description = @Description,
                                        IconUrl = @IconUrl,
                                        LicenseUrl = @LicenseUrl,
                                        ProjectUrl = @ProjectUrl,
                                        ReleaseNotes = @ReleaseNotes,
                                        RequiresLicenseAcceptance = @RequiresLicenseAcceptance,
                                        Summary = @Summary,
                                        Title = @Title,
                                        Tags = @Tags,
                                        LastEdited = GETUTCDATE(),
                                        LastUpdated = GETUTCDATE(),
                                        UserKey = @UserKey,
                                        Hash = @Hash,
                                        HashAlgorithm = @HashAlgorithm,
                                        PackageFileSize = @PackageFileSize,
                                        FlattenedAuthors = @Authors
                                WHERE   [Key] = @PackageKey

                                -- Update Authors
                                DELETE FROM [PackageAuthors]
                                WHERE PackageKey = @PackageKey

                                " + loadAuthorsSql + @"

                                -- Clean this edit and all previous edits.
                                DELETE FROM [PackageEdits]
                                WHERE [PackageKey] = @PackageKey
                                AND [Key] <= @Key
                            " + "COMMIT TRANSACTION",
                                                  parameters);
            }
        }
Example #3
0
        private async Task ApplyEdit(PackageEdit edit)
        {
            string originalPath = null;

            try
            {
                TempDirectory = Path.Combine(Path.GetTempPath(), "NuGetService", "HandlePackageEdits");
                var directory = Path.Combine(TempDirectory, edit.Id, edit.Version);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                originalPath = Path.Combine(directory, "original.nupkg");

                var sourceItem = FileStorage.GetFile(edit.Id, edit.Version);
                Trace.TraceInformation($"Name is {sourceItem.Name}, storage uri is {sourceItem.Uri}");

                // Download the original file
                Trace.TraceInformation($"Downloading original copy of {edit.Id} {edit.Version}");
                await FileStorage.DownloadAsync(sourceItem, originalPath);

                Trace.TraceInformation($"Downloaded original copy of {edit.Id} {edit.Version}");

                // Check that a backup exists
                Trace.TraceInformation($"Backing up original copy of {edit.Id} {edit.Version}");
                await FileStorage.BackupAsync(originalPath, edit.Id, edit.Version, edit.Hash);

                Trace.TraceInformation($"Backed up original copy of {edit.Id} {edit.Version}");

                // Update the nupkg manifest with the new metadata
                using (var originalStream = File.Open(originalPath, FileMode.Open, FileAccess.ReadWrite))
                {
                    Trace.TraceInformation($"Rewriting package file for {edit.Id} {edit.Version}");

                    var editActionList = edit.GetEditsAsActionList();
                    NupkgRewriter.RewriteNupkgManifest(originalStream, editActionList);

                    Trace.TraceInformation($"Rewrote package file for {edit.Id} {edit.Version}");
                }

                // Snapshot the original blob
                Trace.TraceInformation($"Snapshotting original blob for {edit.Id} {edit.Version} ({sourceItem.Uri.AbsoluteUri}).");
                var snapshot = await FileStorage.SnapshotService.CreateSnapshot(sourceItem);

                Trace.TraceInformation($"Snapshotted original blob for {edit.Id} {edit.Version} ({sourceItem.Uri.AbsoluteUri}).");

                // Upload the updated file
                Trace.TraceInformation($"Uploading modified package file for {edit.Id} {edit.Version} to {sourceItem.Uri.AbsoluteUri}");
                await FileStorage.UploadAsync(sourceItem, originalPath);

                Trace.TraceInformation($"Uploaded modified package file for {edit.Id} {edit.Version} to {sourceItem.Uri.AbsoluteUri}");

                // Calculate new size and hash
                string hash;
                long   size;
                using (var originalStream = File.OpenRead(originalPath))
                {
                    size = originalStream.Length;

                    var hashAlgorithm = HashAlgorithm.Create(HashAlgorithmName);
                    hash = Convert.ToBase64String(
                        hashAlgorithm.ComputeHash(originalStream));
                }

                // Update the database
                try
                {
                    Trace.TraceInformation($"Updating package record for {edit.Id} {edit.Version}");
                    await UpdateDatabaseWithEdit(edit, hash, size);

                    Trace.TraceInformation($"Updated package record for {edit.Id} {edit.Version}");
                }
                catch (Exception exception)
                {
                    // Error occurred while updaing database, roll back the blob to the snapshot
                    // Can't do "await" in a catch block, but this should be pretty quick since it just starts the copy
                    Trace.TraceError($"Failed to update database! {exception}");
                    Trace.TraceWarning(
                        $"Rolling back updated blob for {edit.Id} {edit.Version}. Copying snapshot {snapshot.Uri.AbsoluteUri} to {sourceItem.Uri.AbsoluteUri}");
                    FileStorage.SnapshotService.RestoreSnapshot(sourceItem, snapshot);
                    Trace.TraceWarning(
                        $"Rolled back updated blob for {edit.Id} {edit.Version}. Copying snapshot {snapshot.Uri.AbsoluteUri} to {sourceItem.Uri.AbsoluteUri}");

                    throw;
                }

                Trace.TraceInformation("Deleting snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, snapshot.Uri.AbsoluteUri);
                await FileStorage.SnapshotService.DeleteSnapshotAsync(snapshot);

                Trace.TraceInformation("Deleted snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, snapshot.Uri.AbsoluteUri);
            }
            finally
            {
                if (!string.IsNullOrEmpty(originalPath) && File.Exists(originalPath))
                {
                    File.Delete(originalPath);
                }
            }
        }
Example #4
0
        private async Task <ReadMeBlobs> UpdateReadMeAsync(PackageEdit edit,
                                                           string directory, string originalReadMePath, string readMeExtension)
        {
            ReadMeBlobs currentBlob = new ReadMeBlobs();

            // ReadMeState of null means unmodified
            if (edit.ReadMeState == null)
            {
                return(currentBlob);
            }

            originalReadMePath = Path.Combine(directory, "readme" + readMeExtension);
            Logger.LogInformation("Attempting to save ReadMe at {OriginalReadMePath}", originalReadMePath);

            currentBlob.activeBlob = ReadMeContainer.GetBlockBlobReference(
                StorageHelpers.GetActiveReadMeBlobNamePath(edit.Id, edit.Version, readMeExtension));
            Logger.LogInformation("Found active ReadMe {ActiveBlobName} at storage URI {ActiveReadMeUri}", currentBlob.activeBlob.Name, currentBlob.activeBlob.StorageUri);

            // Update ReadMe in blob storage
            if (edit.ReadMeState == ReadMeChanged)
            {
                // Do the blob update
                try
                {
                    currentBlob.pendingBlob = ReadMeContainer.GetBlockBlobReference(
                        StorageHelpers.GetPendingReadMeBlobNamePath(edit.Id, edit.Version, readMeExtension));
                    Logger.LogInformation("Found pending ReadMe {PendingBlobName} at storage URI {PendingReadMeUri}", currentBlob.pendingBlob.Name, currentBlob.pendingBlob.StorageUri);

                    if (edit.HasReadMe)
                    {
                        // Snapshot the original blob if it exists (so if it's an edit, not an upload)
                        Logger.LogInformation("Snapshotting original blob for {EditId} {EditVersion} ({ActiveReadMeUri}).", edit.Id, edit.Version, currentBlob.activeBlob.Uri.AbsoluteUri);
                        currentBlob.activeSnapshot = await currentBlob.activeBlob.CreateSnapshotAsync();

                        Logger.LogInformation("Snapshotted original blob for {EditId} {EditVersion} ({ActiveReadMeUri}).", edit.Id, edit.Version, currentBlob.activeBlob.Uri.AbsoluteUri);
                    }

                    // Download pending ReadMe
                    Logger.LogInformation("Downloading new ReadMe for {EditId} {EditVersion}", edit.Id, edit.Version);
                    await currentBlob.pendingBlob.DownloadToFileAsync(originalReadMePath, FileMode.Create);

                    Logger.LogInformation("Downloaded new ReadMe for {EditId} {EditVersion}", edit.Id, edit.Version);

                    // Upload pending ReadMe to active
                    Logger.LogInformation("Uploading new ReadMe for {EditId} {EditVersion} to {ActiveReadMeUri}", edit.Id, edit.Version, currentBlob.activeBlob.Uri.AbsoluteUri);
                    await currentBlob.activeBlob.UploadFromFileAsync(originalReadMePath);

                    Logger.LogInformation("Uploaded new ReadMe for {EditId} {EditVersion} to {ActiveReadMeUri}", edit.Id, edit.Version, currentBlob.activeBlob.Uri.AbsoluteUri);
                }
                finally
                {
                    if (!string.IsNullOrEmpty(originalReadMePath) && File.Exists(originalReadMePath))
                    {
                        File.Delete(originalReadMePath);
                    }
                }
            }
            // Delete ReadMe in blob storage
            else if (edit.ReadMeState == ReadMeDeleted)
            {
                // Download active ReadMe
                Logger.LogInformation("Downloading old ReadMe for {EditId} {EditVersion}", edit.Id, edit.Version);
                await currentBlob.activeBlob.DownloadToFileAsync(originalReadMePath, FileMode.Create);

                Logger.LogInformation("Downloaded old ReadMe for {EditId} {EditVersion}", edit.Id, edit.Version);

                // Delete active ReadMe
                Logger.LogInformation("Deleting ReadMe of {EditId} {EditVersion} from {ActiveReadMeUri}", edit.Id, edit.Version, currentBlob.activeBlob.Uri.AbsoluteUri);
                await currentBlob.activeBlob.DeleteIfExistsAsync();

                Logger.LogInformation("Deleted ReadMe of {EditId} {EditVersion} from {ActiveReadMeUri}", edit.Id, edit.Version, currentBlob.activeBlob.Uri.AbsoluteUri);
            }

            return(currentBlob);
        }
Example #5
0
        private async Task ApplyEdit(PackageEdit edit)
        {
            string originalPath         = null;
            string originalReadMeMDPath = null;

            try
            {
                TempDirectory = Path.Combine(Path.GetTempPath(), "NuGetService", "HandlePackageEdits");
                var directory = Path.Combine(TempDirectory, edit.Id, edit.Version);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                originalPath = Path.Combine(directory, "original.nupkg");
                Logger.LogInformation("Downloading nupkg to {OriginalPath}", originalPath);

                var sourceBlob = SourceContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBlobName(edit.Id, edit.Version));
                Logger.LogInformation("Name is {SourceBlobName}, storage uri is {StorageUri}", sourceBlob.Name, sourceBlob.StorageUri);

                // Download the original file
                Logger.LogInformation("Downloading original copy of {EditId} {EditVersion}", edit.Id, edit.Version);
                await sourceBlob.DownloadToFileAsync(originalPath, FileMode.Create);

                Logger.LogInformation("Downloaded original copy of {EditId} {EditVersion}", edit.Id, edit.Version);

                // Check that a backup exists
                var backupBlob = BackupsContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBackupBlobName(edit.Id, edit.Version, edit.Hash));
                if (!await backupBlob.ExistsAsync())
                {
                    Logger.LogInformation("Backing up original copy of {EditId} {EditVersion}", edit.Id, edit.Version);
                    await backupBlob.UploadFromFileAsync(originalPath);

                    Logger.LogInformation("Backed up original copy of {EditId} {EditVersion}", edit.Id, edit.Version);
                }

                // Update the nupkg manifest with the new metadata
                using (var originalStream = File.Open(originalPath, FileMode.Open, FileAccess.ReadWrite))
                {
                    Logger.LogInformation("Rewriting package file for {EditId} {EditVersion}", edit.Id, edit.Version);

                    var editActionList = edit.GetEditsAsActionList();
                    NupkgRewriter.RewriteNupkgManifest(originalStream, editActionList);

                    Logger.LogInformation("Rewrote package file for {EditId} {EditVersion}", edit.Id, edit.Version);
                }

                // Snapshot the original blob
                Logger.LogInformation("Snapshotting original blob for {EditId} {EditVersion} ({BlobUri}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);
                var sourceSnapshot = await sourceBlob.CreateSnapshotAsync();

                Logger.LogInformation("Snapshotted original blob for {EditId} {EditVersion} ({BlobUri}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);

                // Upload the updated file
                Logger.LogInformation("Uploading modified package file for {EditId} {EditVersion} to {BlobUri}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);
                await sourceBlob.UploadFromFileAsync(originalPath);

                Logger.LogInformation("Uploaded modified package file for {EditId} {EditVersion} to {BlobUri}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);

                // Calculate new size and hash
                string hash;
                long   size;
                using (var originalStream = File.OpenRead(originalPath))
                {
                    size = originalStream.Length;

                    var hashAlgorithm = HashAlgorithm.Create(HashAlgorithmName);
                    hash = Convert.ToBase64String(
                        hashAlgorithm.ComputeHash(originalStream));
                }

                // ReadMe update
                var readMeMDBlob = await UpdateReadMeAsync(edit, directory, originalReadMeMDPath, MarkdownExtension);

                try
                {
                    Logger.LogInformation("Updating package record for {EditId} {EditVersion}", edit.Id, edit.Version);
                    await UpdateDatabaseWithEdit(edit, hash, size);

                    Logger.LogInformation("Updated package record for {EditId} {EditVersion}", edit.Id, edit.Version);
                }
                catch (Exception exception)
                {
                    // Error occurred while updaing database, roll back the blob to the snapshot
                    Logger.LogError("Failed to update database! {Exception}", exception);
                    Logger.LogWarning(
                        "Rolling back updated blob for {EditId} {EditVersion}. Copying snapshot {SnapshotUri} to {BlobUri}",
                        edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri);
                    await sourceBlob.StartCopyAsync(sourceSnapshot);

                    while (sourceBlob.CopyState.Status != CopyStatus.Success)
                    {
                        await Task.Delay(1000);
                    }
                    Logger.LogWarning(
                        "Rolled back updated blob for {EditId} {EditVersion}. Copying snapshot {SnapshotUri} to {BlobUri}",
                        sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri);

                    await RollBackReadMeAsync(edit, directory, originalReadMeMDPath, readMeMDBlob.activeSnapshot, readMeMDBlob.activeBlob);

                    throw;
                }

                if (edit.ReadMeState == ReadMeChanged)
                {
                    // Delete pending ReadMes
                    Logger.LogInformation("Deleting pending ReadMe for {EditId} {EditVersion} from {PendingReadMeMdUri}", edit.Id, edit.Version, readMeMDBlob.pendingBlob.Uri.AbsoluteUri);
                    await readMeMDBlob.pendingBlob.DeleteIfExistsAsync();

                    Logger.LogInformation($"Deleted pending ReadMe for {edit.Id} {edit.Version} from {readMeMDBlob.pendingBlob.Uri.AbsoluteUri}");
                }

                Logger.LogInformation("Deleting snapshot blob {SnapshotUri} for {EditId} {EditVersion}.", sourceSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);
                await sourceSnapshot.DeleteAsync();

                Logger.LogInformation("Deleted snapshot blob {SnapshotUri} for {EditId} {EditVersion}.", sourceSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);

                if (readMeMDBlob.activeSnapshot != null)
                {
                    Logger.LogInformation("Deleting snapshot ReadMe Md {ActiveReadMeMdUri} for {EditId} {EditVersion}.", readMeMDBlob.activeSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);
                    await readMeMDBlob.activeSnapshot.DeleteAsync();

                    Logger.LogInformation("Deleted snapshot ReadMe Md {ActiveReadMeMdUri} for {EditId} {EditVersion}.", readMeMDBlob.activeSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);
                }
            }
            finally
            {
                if (!string.IsNullOrEmpty(originalPath) && File.Exists(originalPath))
                {
                    File.Delete(originalPath);
                }
            }
        }
        private async Task ApplyEdit(PackageEdit edit)
        {
            // Download the original file
            string originalPath = null;
            TempDirectory = Path.Combine(Path.GetTempPath(), "NuGetService", "HandlePackageEdits");
                
            try
            {
                string directory = Path.Combine(TempDirectory, edit.Id, edit.Version);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                originalPath = Path.Combine(directory, "original.nupkg");
                var sourceBlob = SourceContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBlobName(edit.Id, edit.Version));
                Trace.TraceInformation(string.Format("Name is {0}, storage uri is {1}", sourceBlob.Name, sourceBlob.StorageUri));
                Trace.TraceInformation(string.Format("Downloading original copy of {0} {1}", edit.Id, edit.Version));
                await sourceBlob.DownloadToFileAsync(originalPath, FileMode.Create);
                Trace.TraceInformation(string.Format("Downloaded original copy of {0} {1}", edit.Id, edit.Version));

                // Check that a backup exists
                var backupBlob = BackupsContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBackupBlobName(edit.Id, edit.Version, edit.Hash));
                if (!await backupBlob.ExistsAsync())
                {
                    Trace.TraceInformation(string.Format("Backing up original copy of {0} {1}", edit.Id, edit.Version));
                    await backupBlob.UploadFromFileAsync(originalPath, FileMode.Open);
                    Trace.TraceInformation(string.Format("Backed up original copy of {0} {1}", edit.Id, edit.Version));
                }

                // Load the zip file and find the manifest
                using (var originalStream = File.Open(originalPath, FileMode.Open, FileAccess.ReadWrite))
                using (var archive = new ZipArchive(originalStream, ZipArchiveMode.Update))
                {
                    // Find the nuspec
                    var nuspecEntries = archive.Entries.Where(e => ManifestSelector.IsMatch(e.FullName)).ToArray();
                    if (nuspecEntries.Length == 0)
                    {
                        throw new InvalidDataException(string.Format(
                            CultureInfo.CurrentCulture,
                            Strings.HandlePackageEditsJob_MissingManifest,
                            edit.Id,
                            edit.Version,
                            backupBlob.Uri.AbsoluteUri));
                    }
                    else if (nuspecEntries.Length > 1)
                    {
                        throw new InvalidDataException(string.Format(
                            CultureInfo.CurrentCulture,
                            Strings.HandlePackageEditsJob_MultipleManifests,
                            edit.Id,
                            edit.Version,
                            backupBlob.Uri.AbsoluteUri));
                    }

                    // We now have the nuspec
                    var manifestEntry = nuspecEntries.Single();

                    // Load the manifest with a constrained stream
                    Trace.TraceInformation(string.Format("Rewriting package file for {0} {1}", edit.Id, edit.Version));
                    Manifest manifest;
                    using (var manifestStream = manifestEntry.Open())
                    {
                        manifest = Manifest.ReadFrom(manifestStream, validateSchema: false);

                        // Modify the manifest as per the edit
                        edit.ApplyTo(manifest.Metadata);

                        // Save the manifest back
                        manifestStream.Seek(0, SeekOrigin.Begin);
                        manifestStream.SetLength(0);
                        manifest.Save(manifestStream);
                    }
                    Trace.TraceInformation(string.Format("Rewrote package file for {0} {1}", edit.Id, edit.Version));
                }

                // Snapshot the original blob
                Trace.TraceInformation(string.Format("Snapshotting original blob for {0} {1} ({2}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));
                var sourceSnapshot = await sourceBlob.CreateSnapshotAsync();
                Trace.TraceInformation(string.Format("Snapshotted original blob for {0} {1} ({2}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));

                // Upload the updated file
                Trace.TraceInformation(string.Format("Uploading modified package file for {0} {1} to {2}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));
                await sourceBlob.UploadFromFileAsync(originalPath, FileMode.Open);
                Trace.TraceInformation(string.Format("Uploaded modified package file for {0} {1} to {2}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));

                // Calculate new size and hash
                string hash;
                long size;
                using (var originalStream = File.OpenRead(originalPath))
                {
                    size = originalStream.Length;

                    var hashAlgorithm = HashAlgorithm.Create(HashAlgorithmName);
                    hash = Convert.ToBase64String(
                        hashAlgorithm.ComputeHash(originalStream));
                }

                // Update the database
                try
                {
                    Trace.TraceInformation(string.Format("Updating package record for {0} {1}", edit.Id, edit.Version));
                    using (var connection = await PackageDatabase.ConnectTo())
                    {
                        var parameters = new DynamicParameters(new
                        {
                            edit.Authors,
                            edit.Copyright,
                            edit.Description,
                            edit.IconUrl,
                            edit.LicenseUrl,
                            edit.ProjectUrl,
                            edit.ReleaseNotes,
                            edit.RequiresLicenseAcceptance,
                            edit.Summary,
                            edit.Title,
                            edit.Tags,
                            edit.Key,
                            edit.PackageKey,
                            edit.UserKey,
                            PackageFileSize = size,
                            Hash = hash,
                            HashAlgorithm = HashAlgorithmName
                        });

                        // Prep SQL for merging in authors
                        StringBuilder loadAuthorsSql = new StringBuilder();
                        var authors = edit.Authors.Split(',');
                        for (int i = 0; i < authors.Length; i++)
                        {
                            loadAuthorsSql.Append("INSERT INTO [PackageAuthors]([PackageKey],[Name]) VALUES(@PackageKey, @Author" + i.ToString() + ")");
                            parameters.Add("Author" + i.ToString(), authors[i]);
                        }

                        await connection.QueryAsync<int>(@"
                            BEGIN TRANSACTION
                                -- Form a comma-separated list of authors
                                DECLARE @existingAuthors nvarchar(MAX)
                                SELECT @existingAuthors = COALESCE(@existingAuthors + ',', '') + Name
                                FROM PackageAuthors
                                WHERE PackageKey = @PackageKey

                                -- Copy packages data to package history table
                                INSERT INTO [PackageHistories]
                                SELECT      [Key] AS PackageKey,
                                            @UserKey AS UserKey,
                                            GETUTCDATE() AS Timestamp,
                                            Title,
                                            @existingAuthors AS Authors,
                                            Copyright,
                                            Description,
                                            IconUrl,
                                            LicenseUrl,
                                            ProjectUrl,
                                            ReleaseNotes,
                                            RequiresLicenseAcceptance,
                                            Summary,
                                            Tags,
                                            Hash,
                                            HashAlgorithm,
                                            PackageFileSize,
                                            LastUpdated,
                                            Published
                                FROM        [Packages]
                                WHERE       [Key] = @PackageKey

                                -- Update the packages table
                                UPDATE  [Packages]
                                SET     Copyright = @Copyright,
                                        Description = @Description,
                                        IconUrl = @IconUrl,
                                        LicenseUrl = @LicenseUrl,
                                        ProjectUrl = @ProjectUrl,
                                        ReleaseNotes = @ReleaseNotes,
                                        RequiresLicenseAcceptance = @RequiresLicenseAcceptance,
                                        Summary = @Summary,
                                        Title = @Title,
                                        Tags = @Tags,
                                        LastEdited = GETUTCDATE(),
                                        LastUpdated = GETUTCDATE(),
                                        UserKey = @UserKey,
                                        Hash = @Hash,
                                        HashAlgorithm = @HashAlgorithm,
                                        PackageFileSize = @PackageFileSize,
                                        FlattenedAuthors = @Authors
                                WHERE   [Key] = @PackageKey

                                -- Update Authors
                                DELETE FROM [PackageAuthors] 
                                WHERE PackageKey = @PackageKey

                                " + loadAuthorsSql.ToString() + @"
                            
                                -- Clean this edit and all previous edits.
                                DELETE FROM [PackageEdits]
                                WHERE [PackageKey] = @PackageKey
                                AND [Key] <= @Key
                            " +  "COMMIT TRANSACTION",
                            parameters);
                    }
                    Trace.TraceInformation(string.Format("Updated package record for {0} {1}", edit.Id, edit.Version));
                }
                catch (Exception)
                {
                    // Error occurred while updaing database, roll back the blob to the snapshot
                    // Can't do "await" in a catch block, but this should be pretty quick since it just starts the copy
                    Trace.TraceInformation(string.Format("Rolling back updated blob for {0} {1}. Copying snapshot {2} to {3}", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri));
                    sourceBlob.StartCopyFromBlob(sourceSnapshot);
                    Trace.TraceInformation(string.Format("Rolled back updated blob for {0} {1}. Copying snapshot {2} to {3}", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri));
                    throw;
                }

                Trace.TraceInformation(string.Format("Deleting snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri));
                await sourceSnapshot.DeleteAsync();
                Trace.TraceInformation(string.Format("Deleted snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri));
            }
            finally
            {
                if (!string.IsNullOrEmpty(originalPath) && File.Exists(originalPath))
                {
                    File.Delete(originalPath);
                }
            }
        }
        private async Task ApplyEdit(PackageEdit edit)
        {
            // Download the original file
            string originalPath = null;

            TempDirectory = Path.Combine(Path.GetTempPath(), "NuGetService", "HandlePackageEdits");

            try
            {
                string directory = Path.Combine(TempDirectory, edit.Id, edit.Version);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                originalPath = Path.Combine(directory, "original.nupkg");
                var sourceBlob = SourceContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBlobName(edit.Id, edit.Version));
                Trace.TraceInformation(string.Format("Name is {0}, storage uri is {1}", sourceBlob.Name, sourceBlob.StorageUri));
                Trace.TraceInformation(string.Format("Downloading original copy of {0} {1}", edit.Id, edit.Version));
                await sourceBlob.DownloadToFileAsync(originalPath, FileMode.Create);

                Trace.TraceInformation(string.Format("Downloaded original copy of {0} {1}", edit.Id, edit.Version));

                // Check that a backup exists
                var backupBlob = BackupsContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBackupBlobName(edit.Id, edit.Version, edit.Hash));
                if (!await backupBlob.ExistsAsync())
                {
                    Trace.TraceInformation(string.Format("Backing up original copy of {0} {1}", edit.Id, edit.Version));
                    await backupBlob.UploadFromFileAsync(originalPath, FileMode.Open);

                    Trace.TraceInformation(string.Format("Backed up original copy of {0} {1}", edit.Id, edit.Version));
                }

                // Load the zip file and find the manifest
                using (var originalStream = File.Open(originalPath, FileMode.Open, FileAccess.ReadWrite))
                    using (var archive = new ZipArchive(originalStream, ZipArchiveMode.Update))
                    {
                        // Find the nuspec
                        var nuspecEntries = archive.Entries.Where(e => ManifestSelector.IsMatch(e.FullName)).ToArray();
                        if (nuspecEntries.Length == 0)
                        {
                            throw new InvalidDataException(string.Format(
                                                               CultureInfo.CurrentCulture,
                                                               Strings.HandlePackageEditsJob_MissingManifest,
                                                               edit.Id,
                                                               edit.Version,
                                                               backupBlob.Uri.AbsoluteUri));
                        }
                        else if (nuspecEntries.Length > 1)
                        {
                            throw new InvalidDataException(string.Format(
                                                               CultureInfo.CurrentCulture,
                                                               Strings.HandlePackageEditsJob_MultipleManifests,
                                                               edit.Id,
                                                               edit.Version,
                                                               backupBlob.Uri.AbsoluteUri));
                        }

                        // We now have the nuspec
                        var manifestEntry = nuspecEntries.Single();

                        // Load the manifest with a constrained stream
                        Trace.TraceInformation(string.Format("Rewriting package file for {0} {1}", edit.Id, edit.Version));
                        Manifest manifest;
                        using (var manifestStream = manifestEntry.Open())
                        {
                            manifest = Manifest.ReadFrom(manifestStream, validateSchema: false);

                            // Modify the manifest as per the edit
                            edit.ApplyTo(manifest.Metadata);

                            // Save the manifest back
                            manifestStream.Seek(0, SeekOrigin.Begin);
                            manifestStream.SetLength(0);
                            manifest.Save(manifestStream);
                        }
                        Trace.TraceInformation(string.Format("Rewrote package file for {0} {1}", edit.Id, edit.Version));
                    }

                // Snapshot the original blob
                Trace.TraceInformation(string.Format("Snapshotting original blob for {0} {1} ({2}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));
                var sourceSnapshot = await sourceBlob.CreateSnapshotAsync();

                Trace.TraceInformation(string.Format("Snapshotted original blob for {0} {1} ({2}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));

                // Upload the updated file
                Trace.TraceInformation(string.Format("Uploading modified package file for {0} {1} to {2}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));
                await sourceBlob.UploadFromFileAsync(originalPath, FileMode.Open);

                Trace.TraceInformation(string.Format("Uploaded modified package file for {0} {1} to {2}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri));

                // Calculate new size and hash
                string hash;
                long   size;
                using (var originalStream = File.OpenRead(originalPath))
                {
                    size = originalStream.Length;

                    var hashAlgorithm = HashAlgorithm.Create(HashAlgorithmName);
                    hash = Convert.ToBase64String(
                        hashAlgorithm.ComputeHash(originalStream));
                }

                // Update the database
                try
                {
                    Trace.TraceInformation(string.Format("Updating package record for {0} {1}", edit.Id, edit.Version));
                    using (var connection = await PackageDatabase.ConnectTo())
                    {
                        var parameters = new DynamicParameters(new
                        {
                            edit.Authors,
                            edit.Copyright,
                            edit.Description,
                            edit.IconUrl,
                            edit.LicenseUrl,
                            edit.ProjectUrl,
                            edit.ReleaseNotes,
                            edit.RequiresLicenseAcceptance,
                            edit.Summary,
                            edit.Title,
                            edit.Tags,
                            edit.Key,
                            edit.PackageKey,
                            edit.UserKey,
                            PackageFileSize = size,
                            Hash            = hash,
                            HashAlgorithm   = HashAlgorithmName
                        });

                        // Prep SQL for merging in authors
                        StringBuilder loadAuthorsSql = new StringBuilder();
                        var           authors        = edit.Authors.Split(',');
                        for (int i = 0; i < authors.Length; i++)
                        {
                            loadAuthorsSql.Append("INSERT INTO [PackageAuthors]([PackageKey],[Name]) VALUES(@PackageKey, @Author" + i.ToString() + ")");
                            parameters.Add("Author" + i.ToString(), authors[i]);
                        }

                        await connection.QueryAsync <int>(@"
                            BEGIN TRANSACTION
                                -- Form a comma-separated list of authors
                                DECLARE @existingAuthors nvarchar(MAX)
                                SELECT @existingAuthors = COALESCE(@existingAuthors + ',', '') + Name
                                FROM PackageAuthors
                                WHERE PackageKey = @PackageKey

                                -- Copy packages data to package history table
                                INSERT INTO [PackageHistories]
                                SELECT      [Key] AS PackageKey,
                                            @UserKey AS UserKey,
                                            GETUTCDATE() AS Timestamp,
                                            Title,
                                            @existingAuthors AS Authors,
                                            Copyright,
                                            Description,
                                            IconUrl,
                                            LicenseUrl,
                                            ProjectUrl,
                                            ReleaseNotes,
                                            RequiresLicenseAcceptance,
                                            Summary,
                                            Tags,
                                            Hash,
                                            HashAlgorithm,
                                            PackageFileSize,
                                            LastUpdated,
                                            Published
                                FROM        [Packages]
                                WHERE       [Key] = @PackageKey

                                -- Update the packages table
                                UPDATE  [Packages]
                                SET     Copyright = @Copyright,
                                        Description = @Description,
                                        IconUrl = @IconUrl,
                                        LicenseUrl = @LicenseUrl,
                                        ProjectUrl = @ProjectUrl,
                                        ReleaseNotes = @ReleaseNotes,
                                        RequiresLicenseAcceptance = @RequiresLicenseAcceptance,
                                        Summary = @Summary,
                                        Title = @Title,
                                        Tags = @Tags,
                                        LastEdited = GETUTCDATE(),
                                        LastUpdated = GETUTCDATE(),
                                        UserKey = @UserKey,
                                        Hash = @Hash,
                                        HashAlgorithm = @HashAlgorithm,
                                        PackageFileSize = @PackageFileSize,
                                        FlattenedAuthors = @Authors
                                WHERE   [Key] = @PackageKey

                                -- Update Authors
                                DELETE FROM [PackageAuthors] 
                                WHERE PackageKey = @PackageKey

                                " + loadAuthorsSql.ToString() + @"
                            
                                -- Clean this edit and all previous edits.
                                DELETE FROM [PackageEdits]
                                WHERE [PackageKey] = @PackageKey
                                AND [Key] <= @Key
                            " + "COMMIT TRANSACTION",
                                                          parameters);
                    }
                    Trace.TraceInformation(string.Format("Updated package record for {0} {1}", edit.Id, edit.Version));
                }
                catch (Exception)
                {
                    // Error occurred while updaing database, roll back the blob to the snapshot
                    // Can't do "await" in a catch block, but this should be pretty quick since it just starts the copy
                    Trace.TraceInformation(string.Format("Rolling back updated blob for {0} {1}. Copying snapshot {2} to {3}", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri));
                    sourceBlob.StartCopyFromBlob(sourceSnapshot);
                    Trace.TraceInformation(string.Format("Rolled back updated blob for {0} {1}. Copying snapshot {2} to {3}", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri));
                    throw;
                }

                Trace.TraceInformation(string.Format("Deleting snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri));
                await sourceSnapshot.DeleteAsync();

                Trace.TraceInformation(string.Format("Deleted snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri));
            }
            finally
            {
                if (!string.IsNullOrEmpty(originalPath) && File.Exists(originalPath))
                {
                    File.Delete(originalPath);
                }
            }
        }