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);
 }
Esempio n. 8
0
        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));
 }