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); } } } } }
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); } }
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); } } }
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); }
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); } } }