private Task OnCompletedInternal(MediaMoverContext context, bool succeeded, CancellationToken cancelToken) { if (context.AffectedFiles.Any()) { var hasReceived = context.Target == this; if ((!hasReceived && succeeded) || (hasReceived && !succeeded)) { // FS > DB sucessful OR DB > FS failed/aborted: delete all physical files. // Run a background task for the deletion of files (fire & forget) return(_asyncRunner.Run(async(scope, ct, state) => { // Run this fire & forget code in a new scope, because we want // this provider to be disposed as soon as possible. var fileSystem = scope.Resolve <IMediaFileSystem>(); var files = state as string[]; foreach (var file in files) { await fileSystem.TryDeleteFileAsync(file); } }, context.AffectedFiles.ToArray(), cancellationToken: cancelToken)); } } return(Task.CompletedTask); }
public async Task ReceiveAsync(MediaMoverContext context, MediaFile mediaFile, Stream stream) { Guard.NotNull(context, nameof(context)); Guard.NotNull(mediaFile, nameof(mediaFile)); // store data into file if (stream != null && stream.Length > 0) { var filePath = GetPath(mediaFile); if (!await _fileSystem.FileExistsAsync(filePath)) { // TBD: (mc) We only save the file if it doesn't exist yet. // This should save time and bandwidth in the case where the target // is a cloud based file system (like Azure BLOB). // In such a scenario it'd be advisable to copy the files manually // with other - maybe more performant - tools before performing the provider switch. // Create directory if it does not exist yet await _fileSystem.TryCreateDirectoryAsync(Path.GetDirectoryName(filePath)); using (stream) { await _fileSystem.SaveStreamAsync(filePath, stream); } context.AffectedFiles.Add(filePath); } } }
Task IMediaSender.OnCompletedAsync(MediaMoverContext context, bool succeeded, CancellationToken cancelToken) { if (succeeded && context.AffectedFiles.Any() && _db.DataProvider.CanShrink) { // SHrink database after sending/removing at least one blob. return(_db.DataProvider.ShrinkDatabaseAsync(cancelToken)); } return(Task.CompletedTask); }
public Task ReceiveAsync(MediaMoverContext context, MediaFile mediaFile, Stream stream) { Guard.NotNull(context, nameof(context)); Guard.NotNull(mediaFile, nameof(mediaFile)); // Store data for later bulk commit if (stream != null && stream.Length > 0) { // Requires AutoDetectChanges set to true or remove explicit entity detaching return(SaveAsync(mediaFile, MediaStorageItem.FromStream(stream))); } return(Task.CompletedTask); }
public async Task MoveToAsync(IMediaReceiver target, MediaMoverContext context, MediaFile mediaFile) { Guard.NotNull(target, nameof(target)); Guard.NotNull(context, nameof(context)); Guard.NotNull(mediaFile, nameof(mediaFile)); var filePath = GetPath(mediaFile); try { // Let target store data (into database for example) await target.ReceiveAsync(context, mediaFile, await OpenReadAsync(mediaFile)); // Remember file path: we must be able to rollback IO operations on transaction failure context.AffectedFiles.Add(filePath); } catch (Exception ex) { Logger.Debug(ex); } }
public async Task MoveToAsync(IMediaReceiver target, MediaMoverContext context, MediaFile mediaFile) { Guard.NotNull(target, nameof(target)); Guard.NotNull(context, nameof(context)); Guard.NotNull(mediaFile, nameof(mediaFile)); if (mediaFile.MediaStorageId != null) { // Let target store data (into a file for example) await target.ReceiveAsync(context, mediaFile, await OpenReadAsync(mediaFile)); // Remove blob from DB try { mediaFile.MediaStorageId = null; mediaFile.MediaStorage = null; // Remove with stub entity _db.MediaStorage.Remove(new MediaStorage { Id = mediaFile.MediaStorageId.Value }); } catch { } } }
Task IMediaReceiver.OnCompletedAsync(MediaMoverContext context, bool succeeded, CancellationToken cancelToken) { // nothing to do return(Task.CompletedTask); }
public virtual async Task <bool> MoveAsync( Provider <IMediaStorageProvider> sourceProvider, Provider <IMediaStorageProvider> targetProvider, CancellationToken cancelToken = default) { Guard.NotNull(sourceProvider, nameof(sourceProvider)); Guard.NotNull(targetProvider, nameof(targetProvider)); // Source must support sending if (sourceProvider.Value is not IMediaSender sender) { throw new ArgumentException(T("Admin.Media.StorageMovingNotSupported", sourceProvider.Metadata.SystemName)); } // Target must support receiving if (targetProvider.Value is not IMediaReceiver receiver) { throw new ArgumentException(T("Admin.Media.StorageMovingNotSupported", targetProvider.Metadata.SystemName)); } // Source and target provider must not be equal if (sender == receiver) { throw new ArgumentException(T("Admin.Media.CannotMoveToSameProvider")); } var success = false; var utcNow = DateTime.UtcNow; var context = new MediaMoverContext(sender, receiver); // We are about to process data in chunks but want to commit ALL at once after ALL chunks have been processed successfully. // AutoDetectChanges true required for newly inserted binary data. using (var scope = new DbContextScope(ctx: _db, autoDetectChanges: true, retainConnection: true)) { using (var transaction = await _db.Database.BeginTransactionAsync(cancelToken)) { try { var pager = new FastPager <MediaFile>(_db.MediaFiles, PAGE_SIZE); while ((await pager.ReadNextPageAsync <MediaFile>()).Out(out var files)) { if (cancelToken.IsCancellationRequested) { break; } foreach (var file in files) { // Move item from source to target await sender.MoveToAsync(receiver, context, file); file.UpdatedOnUtc = utcNow; ++context.MovedItems; } if (!cancelToken.IsCancellationRequested) { await _db.SaveChangesAsync(cancelToken); // Detach all entities from previous page to save memory scope.DbContext.DetachEntities(files, deep: true); } } if (!cancelToken.IsCancellationRequested) { await transaction.CommitAsync(cancelToken); success = true; } else { success = false; await transaction.RollbackAsync(CancellationToken.None); } } catch (Exception exception) { success = false; await transaction.RollbackAsync(cancelToken); _notifier.Error(exception); Logger.Error(exception); } } } if (success) { await _settingService.ApplySettingAsync("Media.Storage.Provider", targetProvider.Metadata.SystemName); await _db.SaveChangesAsync(cancelToken); } // Inform both provider about ending await sender.OnCompletedAsync(context, success, cancelToken); await receiver.OnCompletedAsync(context, success, cancelToken); return(success); }
Task IMediaReceiver.OnCompletedAsync(MediaMoverContext context, bool succeeded, CancellationToken cancelToken) { return(OnCompletedInternal(context, succeeded, cancelToken)); }