umbraco / Umbraco.StorageProviders

MIT License
29 stars 21 forks source link

Unable to add custom metadata to blob #49

Closed skartknet closed 2 years ago

skartknet commented 2 years ago

I'm creating a federated search on an Umbraco site (the site is actually V7) but the issue still remains on this V10 package. The search uses Azure Cognitive Search, and I'm using an Indexer to extract PDF content from all the files from the Media folder.

The problem is that I need to include some metadata on the blobs indicating the source of the file. Also, there is another issue in regards to deleting the files. As described here, the files are not removed from the index when they are deleted so I would like to include custom metadata to indicate that the file has been deleted.

I haven't found in the package a way of including custom metadata. It would be great to have a SavingFile event on the AzureBlobFileSytem where we could include this metadata.

ronaldbarendse commented 2 years ago

Hi Mario, I've looked into this and the IAzureBlobFileSystem contains a GetBlobClient() method you can use to directly manipulate the blob. Regrettably the MediaFileManager.FileSystem property that you'd use to get access to the media file system always returns a 'shadow' file system (which is internal to the CMS and AFAIK adds transaction scope support), so you can't cast from that and there's no way to get the inner file system. You can use the IAzureBlobFileSystemProvider to get it instead though, but that assumes you've configured media to use the Azure Blob Storage file system using the default setup.

I've created the following example to show how you can set the blob metadata when deleting or restoring media to/from the recycle bin:

using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.StorageProviders.AzureBlob.IO;

public class MediaMetadataComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddNotificationAsyncHandler<MediaMovedNotification, MediaMetadataNotificationHandler>();
        builder.AddNotificationAsyncHandler<MediaMovedToRecycleBinNotification, MediaMetadataNotificationHandler>();
    }

    private class MediaMetadataNotificationHandler : INotificationAsyncHandler<MediaMovedNotification>, INotificationAsyncHandler<MediaMovedToRecycleBinNotification>
    {
        private readonly IAzureBlobFileSystemProvider _azureBlobFileSystemProvider;
        private readonly MediaUrlGeneratorCollection _mediaUrlGenerators;

        public MediaMetadataNotificationHandler(IAzureBlobFileSystemProvider azureBlobFileSystemProvider, MediaUrlGeneratorCollection mediaUrlGenerators)
        {
            _azureBlobFileSystemProvider = azureBlobFileSystemProvider;
            _mediaUrlGenerators = mediaUrlGenerators;
        }

        public async Task HandleAsync(MediaMovedNotification notification, CancellationToken cancellationToken)
            => await ProcessAsync(notification.MoveInfoCollection, cancellationToken).ConfigureAwait(false);

        public async Task HandleAsync(MediaMovedToRecycleBinNotification notification, CancellationToken cancellationToken)
            => await ProcessAsync(notification.MoveInfoCollection, cancellationToken).ConfigureAwait(false);

        public async Task ProcessAsync(IEnumerable<MoveEventInfo<IMedia>> moveInfoCollection, CancellationToken cancellationToken)
        {
            var azureBlobFileSystem = _azureBlobFileSystemProvider.GetFileSystem(AzureBlobFileSystemOptions.MediaFileSystemName);
            if (azureBlobFileSystem is null)
            {
                return;
            }

            foreach (var media in moveInfoCollection.Select(x => x.Entity))
            {
                if (!media.TryGetMediaPath(Constants.Conventions.Media.File, _mediaUrlGenerators, out var mediaPath) || mediaPath is null)
                {
                    continue;
                }

                var mediaBlob = azureBlobFileSystem.GetBlobClient(mediaPath);

                var metadata = new Dictionary<string, string>()
                {
                    { "IsDeleted", media.Trashed.ToString() }
                };

                await mediaBlob.SetMetadataAsync(metadata).ConfigureAwait(false);
            }
        }
    }
}

Adding a SavingFile event/notification to the AzureBlobFileSystem doesn't give you access to the IMedia or IContent to get the relevant context, so that doesn't seem to be the solution either. Please share any other idea's you might have to make this easier, but I'll close this issue as there's already a way to do it.