public async Task <Package> ReflowAsync(string id, string version) { var package = _packageService.FindPackageByIdAndVersion(id, version); if (package == null) { return(null); } // Must suspend the retry execution strategy in order to use transactions. using (EntitiesConfiguration.SuspendRetriableExecutionStrategy()) { using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { // 1) Download package binary to memory using (var packageStream = await _packageFileService.DownloadPackageFileAsync(package)) { using (var packageArchive = new PackageArchiveReader(packageStream, leaveStreamOpen: false)) { // 2) Determine package metadata from binary var packageStreamMetadata = new PackageStreamMetadata { HashAlgorithm = Constants.Sha512HashAlgorithmId, Hash = CryptographyService.GenerateHash(packageStream.AsSeekableStream()), Size = packageStream.Length, }; var packageMetadata = PackageMetadata.FromNuspecReader(packageArchive.GetNuspecReader()); // 3) Clear referenced objects that will be reflowed ClearSupportedFrameworks(package); ClearAuthors(package); ClearDependencies(package); // 4) Reflow the package var listed = package.Listed; package = _packageService.EnrichPackageFromNuGetPackage( package, packageArchive, packageMetadata, packageStreamMetadata, package.User); package.LastEdited = DateTime.UtcNow; package.Listed = listed; // 5) Save and profit await _entitiesContext.SaveChangesAsync(); } } // Commit transaction transaction.Commit(); } } return(package); }
public async Task HardDeletePackagesAsync(IEnumerable <Package> packages, User deletedBy, string reason, string signature, bool deleteEmptyPackageRegistration) { List <PackageRegistration> packageRegistrations; // Must suspend the retry execution strategy in order to use transactions. using (EntitiesConfiguration.SuspendRetriableExecutionStrategy()) { using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { // Increase command timeout _entitiesContext.SetCommandTimeout(seconds: 300); // Keep package registrations packageRegistrations = packages.GroupBy(p => p.PackageRegistration).Select(g => g.First().PackageRegistration).ToList(); // Backup the package binaries and remove from main storage // We're doing this early in the process as we need the metadata to still exist in the DB. await BackupPackageBinaries(packages); // Remove the package and related entities from the database foreach (var package in packages) { await ExecuteSqlCommandAsync(_entitiesContext.GetDatabase(), "DELETE pa FROM PackageAuthors pa JOIN Packages p ON p.[Key] = pa.PackageKey WHERE p.[Key] = @key", new SqlParameter("@key", package.Key)); await ExecuteSqlCommandAsync(_entitiesContext.GetDatabase(), "DELETE pd FROM PackageDependencies pd JOIN Packages p ON p.[Key] = pd.PackageKey WHERE p.[Key] = @key", new SqlParameter("@key", package.Key)); await ExecuteSqlCommandAsync(_entitiesContext.GetDatabase(), "DELETE pf FROM PackageFrameworks pf JOIN Packages p ON p.[Key] = pf.Package_Key WHERE p.[Key] = @key", new SqlParameter("@key", package.Key)); await _auditingService.SaveAuditRecordAsync(CreateAuditRecord(package, package.PackageRegistration, AuditedPackageAction.Delete, reason)); package.PackageRegistration.Packages.Remove(package); _packageRepository.DeleteOnCommit(package); } // Commit changes to package repository await _packageRepository.CommitChangesAsync(); // Remove package registrations that have no more packages? if (deleteEmptyPackageRegistration) { await RemovePackageRegistrationsWithoutPackages(packageRegistrations); } // Commit transaction transaction.Commit(); } } // handle in separate transaction because of concurrency check with retry await UpdateIsLatestAsync(packageRegistrations); // Force refresh the index UpdateSearchIndex(); }
public async Task SoftDeletePackagesAsync(IEnumerable <Package> packages, User deletedBy, string reason, string signature) { List <PackageRegistration> packageRegistrations; // Must suspend the retry execution strategy in order to use transactions. using (EntitiesConfiguration.SuspendRetriableExecutionStrategy()) { using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { // Increase command timeout _entitiesContext.SetCommandTimeout(seconds: 300); // Keep package registrations packageRegistrations = packages .GroupBy(p => p.PackageRegistration) .Select(g => g.First().PackageRegistration) .ToList(); // Backup the package binaries and remove from main storage // We're doing this early in the process as we need the metadata to still exist in the DB. await BackupPackageBinaries(packages); // Store the soft delete in the database var packageDelete = new PackageDelete { DeletedOn = DateTime.UtcNow, DeletedBy = deletedBy, Reason = reason, Signature = signature }; foreach (var package in packages) { package.Listed = false; package.Deleted = true; packageDelete.Packages.Add(package); await _auditingService.SaveAuditRecordAsync(CreateAuditRecord(package, package.PackageRegistration, AuditedPackageAction.SoftDelete, reason)); } _packageDeletesRepository.InsertOnCommit(packageDelete); // Commit changes await _packageRepository.CommitChangesAsync(); await _packageDeletesRepository.CommitChangesAsync(); transaction.Commit(); } } // handle in separate transaction because of concurrency check with retry await UpdateIsLatestAsync(packageRegistrations); // Force refresh the index UpdateSearchIndex(); }
public async Task UpdateIsLatestAsync(PackageRegistration packageRegistration) { // Must suspend the retry execution strategy in order to use transactions. using (EntitiesConfiguration.SuspendRetriableExecutionStrategy()) { if (await TryUpdateIsLatestAsync(_entitiesContext, packageRegistration)) { return; } // Retry the update in case a concurrency conflict was detected on the first attempt. int retryCount = 1; do { await Task.Delay(_randomGenerator.Value.Next(0, 1000)); _trace.Information(String.Format("Retrying {0} for package '{1}' ({2}/{3})", nameof(UpdateIsLatestAsync), packageRegistration.Id, retryCount, UpdateIsLatestMaxRetries)); // Since EF contexts are short-lived and do not really support refresh, we will use a // different context than the request on retry to avoid putting the request context in // a bad state. More than likely the retry will detect that the concurrent update has // already made the right updates and no changes will be necessary. using (var detachedRetryContext = CreateNewEntitiesContext()) { var detachedPackageRegistration = detachedRetryContext.PackageRegistrations.SingleOrDefault( pr => pr.Id == packageRegistration.Id); if (await TryUpdateIsLatestAsync(detachedRetryContext, detachedPackageRegistration)) { return; } } retryCount++; }while (retryCount <= UpdateIsLatestMaxRetries); } }