Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit 5e7d213

Browse files
authored
HandlePackageEdits (#181)
* Update RepositoryUrl in DB and ReadMe in blob storage on package edit (#174) * Update HasReadMe depending on ReadMeState * Edits/deletes ReadMe from blob storage * remove unneeded commented code * make blob readme id folder lowercase * code clean up * moved ReadMe blob storage update to its own function * Scott's updates * changed HasReadMe update location + other minor fixes * PR quick changes * changed back to updating ReadMe, then DB * added rollback if DB changes fail * moved readme to a different function * moved code to rollback function * minor text fixes * include modification of both .md and .html file * indentation * cleaned up template * changed GetReadMeBlobPath to private * tuple to private class * roll back trace error and while not completed loop * Added ReadMeState for upload and minor text fixes (#180) * Update HasReadMe depending on ReadMeState * Edits/deletes ReadMe from blob storage * remove unneeded commented code * make blob readme id folder lowercase * code clean up * moved ReadMe blob storage update to its own function * Scott's updates * changed HasReadMe update location + other minor fixes * PR quick changes * changed back to updating ReadMe, then DB * added rollback if DB changes fail * moved readme to a different function * moved code to rollback function * minor text fixes * include modification of both .md and .html file * indentation * cleaned up template * changed GetReadMeBlobPath to private * tuple to private class * roll back trace error and while not completed loop * added uploaded ReadMeState because of snapshots and rollback scenarios * delete upload state and check table instead
1 parent 7ff1d9c commit 5e7d213

4 files changed

Lines changed: 236 additions & 11 deletions

File tree

src/HandlePackageEdits/HandlePackageEdits.Job.cs

Lines changed: 205 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,21 @@ internal class Job : JobBase
2626
public static readonly long DefaultMaxAllowedManifestBytes = 10 /* Mb */ * 1024 /* Kb */ * 1024; /* b */
2727

2828
public static readonly string GetEditsBaseSql = @"
29-
SELECT pr.Id, p.NormalizedVersion AS Version, p.Hash, e.*
29+
SELECT pr.Id, p.NormalizedVersion AS Version, p.Hash, p.HasReadMe, e.*
3030
FROM PackageEdits e
3131
INNER JOIN Packages p ON p.[Key] = e.PackageKey
3232
INNER JOIN PackageRegistrations pr ON pr.[Key] = p.PackageRegistrationKey";
3333

3434
public const string DefaultSourceContainerName = "packages";
3535
public const string DefaultBackupContainerName = "package-backups";
36+
public const string DefaultReadMeContainerName = "readmes";
3637
public const int DefaultMaxRetryCount = 10;
3738

39+
private const string ReadMeChanged = "changed";
40+
private const string ReadMeDeleted = "deleted";
41+
private const string MarkdownExtension = "md";
42+
private const string HtmlExtension = "html";
43+
3844
/// <summary>
3945
/// Gets or sets an Azure Storage Uri referring to a container to use as the source for package blobs
4046
/// </summary>
@@ -52,18 +58,31 @@ FROM PackageEdits e
5258
public CloudStorageAccount Backups { get; set; }
5359
public string BackupsContainerName { get; set; }
5460

61+
/// <summary>
62+
/// Gets or sets an Azure Storage Uri referring to a container to use as the storage for ReadMes
63+
/// </summary>
64+
public string ReadMeContainerName { get; set; }
65+
5566
/// <summary>
5667
/// Gets or sets the maximum number of tries that are allowed before considering an edit failed.
5768
/// </summary>
5869
public int? MaxTryCount { get; set; }
5970

6071
public long? MaxAllowedManifestBytes { get; set; }
6172

62-
protected CloudBlobContainer SourceContainer { get; set; }
63-
protected CloudBlobContainer BackupsContainer { get; set; }
73+
private CloudBlobContainer SourceContainer { get; set; }
74+
private CloudBlobContainer BackupsContainer { get; set; }
75+
private CloudBlobContainer ReadMeContainer { get; set; }
6476
protected long MaxManifestSize { get; set; }
6577
private ILogger _logger;
6678

79+
private class ReadMeBlobs
80+
{
81+
public CloudBlockBlob activeBlob = null;
82+
public CloudBlockBlob pendingBlob = null;
83+
public CloudBlockBlob activeSnapshot = null;
84+
}
85+
6786
public override bool Init(IDictionary<string, string> jobArgsDictionary)
6887
{
6988
try
@@ -86,12 +105,13 @@ public override bool Init(IDictionary<string, string> jobArgsDictionary)
86105
JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.SourceStorage));
87106
Backups = CloudStorageAccount.Parse(
88107
JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.BackupStorage));
89-
90108
SourceContainerName = JobConfigurationManager.TryGetArgument(jobArgsDictionary, JobArgumentNames.SourceContainerName) ?? DefaultSourceContainerName;
91109
BackupsContainerName = JobConfigurationManager.TryGetArgument(jobArgsDictionary, JobArgumentNames.BackupContainerName) ?? DefaultBackupContainerName;
110+
ReadMeContainerName = JobConfigurationManager.TryGetArgument(jobArgsDictionary, JobArgumentNames.ReadMeContainerName) ?? DefaultReadMeContainerName;
92111

93112
SourceContainer = Source.CreateCloudBlobClient().GetContainerReference(SourceContainerName);
94113
BackupsContainer = Backups.CreateCloudBlobClient().GetContainerReference(BackupsContainerName);
114+
ReadMeContainer = Source.CreateCloudBlobClient().GetContainerReference(ReadMeContainerName);
95115

96116
MaxTryCount = DefaultMaxRetryCount;
97117
}
@@ -154,13 +174,14 @@ public override async Task<bool> Run()
154174
await UpdatePackageEditDbWithError(exception, edit.Key);
155175
}
156176
}
157-
158177
return true;
159178
}
160179

161180
private async Task ApplyEdit(PackageEdit edit)
162181
{
163182
string originalPath = null;
183+
string originalReadMeMDPath = null;
184+
string originalReadMeHTMLPath = null;
164185

165186
try
166187
{
@@ -172,6 +193,8 @@ private async Task ApplyEdit(PackageEdit edit)
172193
}
173194

174195
originalPath = Path.Combine(directory, "original.nupkg");
196+
Trace.TraceInformation($"Downloading nupkg to {originalPath}");
197+
175198
var sourceBlob = SourceContainer.GetBlockBlobReference(
176199
StorageHelpers.GetPackageBlobName(edit.Id, edit.Version));
177200
Trace.TraceInformation($"Name is {sourceBlob.Name}, storage uri is {sourceBlob.StorageUri}");
@@ -223,8 +246,12 @@ private async Task ApplyEdit(PackageEdit edit)
223246
hash = Convert.ToBase64String(
224247
hashAlgorithm.ComputeHash(originalStream));
225248
}
249+
250+
// ReadMe update
251+
var readMeMDBlob = await UpdateReadMeAsync(edit, directory, originalReadMeMDPath, MarkdownExtension);
252+
253+
var readMeHTMLBlob = await UpdateReadMeAsync(edit, directory, originalReadMeHTMLPath, HtmlExtension);
226254

227-
// Update the database
228255
try
229256
{
230257
Trace.TraceInformation($"Updating package record for {edit.Id} {edit.Version}");
@@ -234,20 +261,52 @@ private async Task ApplyEdit(PackageEdit edit)
234261
catch (Exception exception)
235262
{
236263
// Error occurred while updaing database, roll back the blob to the snapshot
237-
// Can't do "await" in a catch block, but this should be pretty quick since it just starts the copy
238264
Trace.TraceError($"Failed to update database! {exception}");
239265
Trace.TraceWarning(
240266
$"Rolling back updated blob for {edit.Id} {edit.Version}. Copying snapshot {sourceSnapshot.Uri.AbsoluteUri} to {sourceBlob.Uri.AbsoluteUri}");
241-
sourceBlob.StartCopy(sourceSnapshot);
267+
await sourceBlob.StartCopyAsync(sourceSnapshot);
268+
while (sourceBlob.CopyState.Status != CopyStatus.Success)
269+
{
270+
await Task.Delay(1000);
271+
}
242272
Trace.TraceWarning(
243273
$"Rolled back updated blob for {edit.Id} {edit.Version}. Copying snapshot {sourceSnapshot.Uri.AbsoluteUri} to {sourceBlob.Uri.AbsoluteUri}");
244274

275+
await RollBackReadMeAsync(edit, directory, originalReadMeMDPath, readMeMDBlob.activeSnapshot, readMeMDBlob.activeBlob);
276+
await RollBackReadMeAsync(edit, directory, originalReadMeHTMLPath, readMeHTMLBlob.activeSnapshot, readMeHTMLBlob.activeBlob);
277+
245278
throw;
246279
}
247280

281+
if (edit.ReadMeState == ReadMeChanged)
282+
{
283+
// Delete pending ReadMes
284+
Trace.TraceInformation($"Deleting pending ReadMe for {edit.Id} {edit.Version} from {readMeMDBlob.pendingBlob.Uri.AbsoluteUri}");
285+
await readMeMDBlob.pendingBlob.DeleteIfExistsAsync();
286+
Trace.TraceInformation($"Deleted pending ReadMe for {edit.Id} {edit.Version} from {readMeMDBlob.pendingBlob.Uri.AbsoluteUri}");
287+
288+
Trace.TraceInformation($"Deleting pending ReadMe for {edit.Id} {edit.Version} from {readMeHTMLBlob.pendingBlob.Uri.AbsoluteUri}");
289+
await readMeHTMLBlob.pendingBlob.DeleteIfExistsAsync();
290+
Trace.TraceInformation($"Deleted pending ReadMe for {edit.Id} {edit.Version} from {readMeHTMLBlob.pendingBlob.Uri.AbsoluteUri}");
291+
}
292+
248293
Trace.TraceInformation("Deleting snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri);
249294
await sourceSnapshot.DeleteAsync();
250295
Trace.TraceInformation("Deleted snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri);
296+
297+
if (readMeMDBlob.activeSnapshot != null)
298+
{
299+
Trace.TraceInformation("Deleting snapshot ReadMe {2} for {0} {1}.", edit.Id, edit.Version, readMeMDBlob.activeSnapshot.Uri.AbsoluteUri);
300+
await readMeMDBlob.activeSnapshot.DeleteAsync();
301+
Trace.TraceInformation("Deleted snapshot ReadMe{2} for {0} {1}.", edit.Id, edit.Version, readMeMDBlob.activeSnapshot.Uri.AbsoluteUri);
302+
}
303+
304+
if (readMeHTMLBlob.activeSnapshot != null)
305+
{
306+
Trace.TraceInformation("Deleting snapshot ReadMe {2} for {0} {1}.", edit.Id, edit.Version, readMeHTMLBlob.activeSnapshot.Uri.AbsoluteUri);
307+
await readMeHTMLBlob.activeSnapshot.DeleteAsync();
308+
Trace.TraceInformation("Deleted snapshot ReadMe {2} for {0} {1}.", edit.Id, edit.Version, readMeHTMLBlob.activeSnapshot.Uri.AbsoluteUri);
309+
}
251310
}
252311
finally
253312
{
@@ -277,6 +336,8 @@ private async Task UpdateDatabaseWithEdit(PackageEdit edit, string hash, long si
277336
edit.IconUrl,
278337
edit.LicenseUrl,
279338
edit.ProjectUrl,
339+
edit.RepositoryUrl,
340+
edit.ReadMeState,
280341
edit.ReleaseNotes,
281342
edit.RequiresLicenseAcceptance,
282343
edit.Summary,
@@ -290,6 +351,20 @@ private async Task UpdateDatabaseWithEdit(PackageEdit edit, string hash, long si
290351
HashAlgorithm = HashAlgorithmName
291352
});
292353

354+
// Update parameters with new HasReadMe value
355+
var HasReadMeSQL = string.Empty;
356+
if (edit.ReadMeState == ReadMeChanged)
357+
{
358+
parameters.Add("HasReadMe", 1);
359+
HasReadMeSQL = ", HasReadMe = @HasReadMe";
360+
361+
}
362+
else if (edit.ReadMeState == ReadMeDeleted)
363+
{
364+
parameters.Add("HasReadMe", 0);
365+
HasReadMeSQL = ", HasReadMe = @HasReadMe";
366+
}
367+
293368
// Prep SQL for merging in authors
294369
var loadAuthorsSql = new StringBuilder();
295370
var authors = edit.Authors.Split(',');
@@ -299,7 +374,7 @@ private async Task UpdateDatabaseWithEdit(PackageEdit edit, string hash, long si
299374
parameters.Add("Author" + i, authors[i]);
300375
}
301376

302-
await connection.QueryAsync<int>(@"
377+
await connection.QueryAsync<int>($@"
303378
BEGIN TRANSACTION
304379
-- Form a comma-separated list of authors
305380
DECLARE @existingAuthors nvarchar(MAX)
@@ -327,7 +402,8 @@ INSERT INTO [PackageHistories]
327402
HashAlgorithm,
328403
PackageFileSize,
329404
LastUpdated,
330-
Published
405+
Published,
406+
RepositoryUrl
331407
FROM [Packages]
332408
WHERE [Key] = @PackageKey
333409
@@ -349,7 +425,9 @@ UPDATE [Packages]
349425
Hash = @Hash,
350426
HashAlgorithm = @HashAlgorithm,
351427
PackageFileSize = @PackageFileSize,
352-
FlattenedAuthors = @Authors
428+
FlattenedAuthors = @Authors,
429+
RepositoryUrl = @RepositoryUrl
430+
{HasReadMeSQL}
353431
WHERE [Key] = @PackageKey
354432
355433
-- Update Authors
@@ -389,7 +467,123 @@ UPDATE PackageEdits
389467
{
390468
Trace.TraceError($"Error updating the package edit database with error! {ex}");
391469
}
470+
}
471+
472+
private async Task<ReadMeBlobs> UpdateReadMeAsync(PackageEdit edit,
473+
string directory, string originalReadMePath, string readMeExtension)
474+
{
475+
ReadMeBlobs currentBlob = new ReadMeBlobs();
392476

477+
// ReadMeState of null means unmodified
478+
if (edit.ReadMeState == null)
479+
{
480+
return currentBlob;
481+
}
482+
483+
originalReadMePath = Path.Combine(directory, "readme" + readMeExtension);
484+
Trace.TraceInformation($"Attempting to save ReadMe at {originalReadMePath}");
485+
486+
currentBlob.activeBlob = ReadMeContainer.GetBlockBlobReference(
487+
StorageHelpers.GetActiveReadMeBlobNamePath(edit.Id, edit.Version, readMeExtension));
488+
Trace.TraceInformation($"Found active ReadMe {currentBlob.activeBlob.Name} at storage URI {currentBlob.activeBlob.StorageUri}");
489+
490+
// Update ReadMe in blob storage
491+
if (edit.ReadMeState == ReadMeChanged)
492+
{
493+
// Do the blob update
494+
try
495+
{
496+
currentBlob.pendingBlob = ReadMeContainer.GetBlockBlobReference(
497+
StorageHelpers.GetPendingReadMeBlobNamePath(edit.Id, edit.Version, readMeExtension));
498+
Trace.TraceInformation($"Found pending ReadMe {currentBlob.pendingBlob.Name} at storage URI {currentBlob.pendingBlob.StorageUri}");
499+
500+
if (edit.HasReadMe)
501+
{
502+
// Snapshot the original blob if it exists (so if it's an edit, not an upload)
503+
Trace.TraceInformation($"Snapshotting original blob for {edit.Id} {edit.Version} ({currentBlob.activeBlob.Uri.AbsoluteUri}).");
504+
currentBlob.activeSnapshot = await currentBlob.activeBlob.CreateSnapshotAsync();
505+
Trace.TraceInformation($"Snapshotted original blob for {edit.Id} {edit.Version} ({currentBlob.activeBlob.Uri.AbsoluteUri}).");
506+
}
507+
508+
// Download pending ReadMe
509+
Trace.TraceInformation($"Downloading new ReadMe for {edit.Id} {edit.Version}");
510+
await currentBlob.pendingBlob.DownloadToFileAsync(originalReadMePath, FileMode.Create);
511+
Trace.TraceInformation($"Downloaded new ReadMe for {edit.Id} {edit.Version}");
512+
513+
// Upload pending ReadMe to active
514+
Trace.TraceInformation($"Uploading new ReadMe for {edit.Id} {edit.Version} to {currentBlob.activeBlob.Uri.AbsoluteUri}");
515+
await currentBlob.activeBlob.UploadFromFileAsync(originalReadMePath);
516+
Trace.TraceInformation($"Uploaded new ReadMe for {edit.Id} {edit.Version} to {currentBlob.activeBlob.Uri.AbsoluteUri}");
517+
}
518+
finally
519+
{
520+
if (!string.IsNullOrEmpty(originalReadMePath) && File.Exists(originalReadMePath))
521+
{
522+
File.Delete(originalReadMePath);
523+
}
524+
}
525+
}
526+
// Delete ReadMe in blob storage
527+
else if (edit.ReadMeState == ReadMeDeleted)
528+
{
529+
// Download active ReadMe
530+
Trace.TraceInformation($"Downloading old ReadMe for {edit.Id} {edit.Version}");
531+
await currentBlob.activeBlob.DownloadToFileAsync(originalReadMePath, FileMode.Create);
532+
Trace.TraceInformation($"Downloaded old ReadMe for {edit.Id} {edit.Version}");
533+
534+
// Delete active ReadMe
535+
Trace.TraceInformation($"Deleting ReadMe of {edit.Id} {edit.Version} from {currentBlob.activeBlob.Uri.AbsoluteUri}");
536+
await currentBlob.activeBlob.DeleteIfExistsAsync();
537+
Trace.TraceInformation($"Deleted ReadMe of {edit.Id} {edit.Version} from {currentBlob.activeBlob.Uri.AbsoluteUri}");
538+
}
539+
return currentBlob;
540+
}
541+
542+
private async Task RollBackReadMeAsync(PackageEdit edit, string directory, string originalReadMePath,
543+
CloudBlockBlob activeReadMeSnapshot, CloudBlockBlob activeReadMeBlob)
544+
{
545+
if (edit.ReadMeState != null)
546+
{
547+
if (edit.ReadMeState == ReadMeChanged)
548+
{
549+
if (edit.HasReadMe)
550+
{
551+
Trace.TraceWarning(
552+
$"Rolling back ReadMe blob for {edit.Id} {edit.Version}. Copying snapshot {activeReadMeSnapshot.Uri.AbsoluteUri} to {activeReadMeBlob.Uri.AbsoluteUri}");
553+
activeReadMeBlob.StartCopy(activeReadMeSnapshot);
554+
while (activeReadMeBlob.CopyState.Status != CopyStatus.Success)
555+
{
556+
await Task.Delay(1000);
557+
}
558+
Trace.TraceWarning(
559+
$"Rolled back ReadMe blob for {edit.Id} {edit.Version}. Copying snapshot {activeReadMeSnapshot.Uri.AbsoluteUri} to {activeReadMeBlob.Uri.AbsoluteUri}");
560+
}
561+
else
562+
{
563+
// Delete ReadMes from active
564+
Trace.TraceInformation($"Deleting ReadMe of {edit.Id} {edit.Version} from {activeReadMeBlob.Uri.AbsoluteUri}");
565+
await activeReadMeBlob.DeleteIfExistsAsync();
566+
Trace.TraceInformation($"Deleted ReadMe of {edit.Id} {edit.Version} from {activeReadMeBlob.Uri.AbsoluteUri}");
567+
}
568+
}
569+
else if (edit.ReadMeState == ReadMeDeleted)
570+
{
571+
try
572+
{
573+
// Upload original ReadMe back to active
574+
Trace.TraceInformation($"Uploading old ReadMe for {edit.Id} {edit.Version} to {activeReadMeBlob.Uri.AbsoluteUri}");
575+
await activeReadMeBlob.UploadFromFileAsync(originalReadMePath);
576+
Trace.TraceInformation($"Uploaded old ReadMe for {edit.Id} {edit.Version} to {activeReadMeBlob.Uri.AbsoluteUri}");
577+
}
578+
finally
579+
{
580+
if (!string.IsNullOrEmpty(originalReadMePath) && File.Exists(originalReadMePath))
581+
{
582+
File.Delete(originalReadMePath);
583+
}
584+
}
585+
}
586+
}
393587
}
394588
}
395589
}

0 commit comments

Comments
 (0)