using FluentMigrator; using Nop.Core.Domain.Media; using Nop.Data; using Nop.Data.Migrations; using Nop.Services.Media; using Nop.Services.Media.RoxyFileman; namespace Nop.Web.Framework.Migrations.UpgradeTo460; [NopMigration("2022-10-18 00:00:00", "Move uploaded images to disk", MigrationProcessType.Update)] public class UploadedImagesMigration : Migration { #region Fields protected readonly IPictureService _pictureService; protected readonly IRepository _pictureRepository; protected readonly IRoxyFilemanFileProvider _roxyFilemanFileProvider; #endregion public UploadedImagesMigration(IPictureService pictureService, IRepository pictureRepository, IRoxyFilemanFileProvider roxyFilemanFileProvider) { _pictureService = pictureService; _pictureRepository = pictureRepository; _roxyFilemanFileProvider = roxyFilemanFileProvider; } public override void Up() { if (!_pictureService.IsStoreInDbAsync().Result) return; const int pageSize = 400; var pageIndex = 0; var uploadRoot = $"~{NopRoxyFilemanDefaults.DefaultRootDirectory}/"; try { while (true) { var pictures = _pictureService.GetPicturesAsync(uploadRoot, pageIndex, pageSize).Result; pageIndex++; //all pictures flushed? if (!pictures.Any()) break; foreach (var picture in pictures) { if (string.IsNullOrEmpty(picture?.VirtualPath) || string.IsNullOrEmpty(picture.SeoFilename)) return; var seoFileName = picture.SeoFilename; var lastPart = _pictureService.GetFileExtensionFromMimeTypeAsync(picture.MimeType).Result; var thumbFileName = $"{seoFileName}.{lastPart}"; var thumbDirectoryName = picture.VirtualPath[uploadRoot.Length..]; var thumbFilePath = Path.Combine(thumbDirectoryName, thumbFileName); _roxyFilemanFileProvider.CreateDirectory("/", thumbDirectoryName); if (picture.IsNew) { // delete old file if exist _roxyFilemanFileProvider.DeleteFile(thumbFilePath); } if (_roxyFilemanFileProvider.GetFileInfo(thumbFilePath).Exists) { _pictureRepository.DeleteAsync(picture, false).Wait(); continue; } //the named mutex helps to avoid creating the same files in different threads, //and does not decrease performance significantly, because the code is blocked only for the specific file. //you should be very careful, mutexes cannot be used in with the await operation //we can't use semaphore here, because it produces PlatformNotSupportedException exception on UNIX based systems using var mutex = new Mutex(false, thumbFileName); mutex.WaitOne(); try { //check, if the file was created, while we were waiting for the release of the mutex. if (!_roxyFilemanFileProvider.GetFileInfo(thumbFilePath).Exists) { var pictureBinary = _pictureService.LoadPictureBinaryAsync(picture).Result; if (pictureBinary == null || pictureBinary.Length == 0) continue; using var stream = new MemoryStream(pictureBinary); _roxyFilemanFileProvider.SaveFileAsync(thumbDirectoryName, thumbFileName, picture.MimeType, stream).Wait(); } } finally { mutex.ReleaseMutex(); } _pictureRepository.DeleteAsync(picture, false).Wait(); } } } catch { // ignored } } public override void Down() { //add the downgrade logic if necessary } }