Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

RoxyFilemanFileProvider.cs

?using System.IO.Compression;
using System.Text;
using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json;
using Nop.Core.Domain.Media;
using Nop.Core.Infrastructure;
using SkiaSharp;

 namespace Nop.Services.Media.RoxyFileman; 

 /// 
 /// Looks up and manages uploaded files using the on-disk file system
 /// 
 public partial class RoxyFilemanFileProvider : PhysicalFileProvider, IRoxyFilemanFileProvider
 {
     #region Fields

     protected INopFileProvider _nopFileProvider;
     protected readonly MediaSettings _mediaSettings;

     #endregion

     #region Ctor

     public RoxyFilemanFileProvider(INopFileProvider nopFileProvider) : base(nopFileProvider.Combine(nopFileProvider.WebRootPath, NopRoxyFilemanDefaults.DefaultRootDirectory))
     {
         _nopFileProvider = nopFileProvider;
     }

     public RoxyFilemanFileProvider(INopFileProvider defaultFileProvider, MediaSettings mediaSettings) : this(defaultFileProvider)
     {
         _mediaSettings = mediaSettings;
     }

     #endregion

     #region Utilities

     /// 
     /// Adjust image measures to target size
     /// 
     /// Source image
     /// Target width
     /// Target height
     /// Adjusted width and height
     protected virtual (int width, int height) ValidateImageMeasures(SKBitmap image, int maxWidth = 0, int maxHeight = 0)
     {
         ArgumentNullException.ThrowIfNull(image);

         float width = Math.Min(image.Width, maxWidth);
         float height = Math.Min(image.Height, maxHeight);

         var targetSize = Math.Max(width, height);

         if (image.Height > image.Width)
         {
             // portrait
             width = image.Width * (targetSize / image.Height);
             height = targetSize;
         }
         else
         {
             // landscape or square
             width = targetSize;
             height = image.Height * (targetSize / image.Width);
         }

         return ((int)width, (int)height);
     }

     /// 
     /// Get a file type by the specified path string
     /// 
     /// The path string from which to get the file type
     /// File type
     protected virtual string GetFileType(string subpath)
     {
         var fileExtension = Path.GetExtension(subpath)?.ToLowerInvariant();

         return fileExtension switch
         {
             ".jpg" or ".jpeg" or ".png" or ".gif" or ".webp" or ".svg" => "image",
             ".swf" or ".flv" => "flash",
             ".mp4" or ".webm" or ".ogg" or ".mov" or ".m4a" or ".mp3" or ".wav" => "media",
             _ => "file"
         };

         /* These media extensions are not supported by HTML5 or tinyMCE out of the box
          * but may possibly be supported if You find players for them.
          * if (fileExtension == ".3gp" || fileExtension == ".flv" 
          *     || fileExtension == ".rmvb" || fileExtension == ".wmv" || fileExtension == ".divx"
          *     || fileExtension == ".divx" || fileExtension == ".mpg" || fileExtension == ".rmvb"
          *     || fileExtension == ".vob" // video
          *     || fileExtension == ".aif" || fileExtension == ".aiff" || fileExtension == ".amr"
          *     || fileExtension == ".asf" || fileExtension == ".asx" || fileExtension == ".wma"
          *     || fileExtension == ".mid" || fileExtension == ".mp2") // audio
          *     fileType = "media"; */
     }

     /// 
     /// Get the absolute path for the specified path string in the root directory for this instance
     /// 
     /// The file or directory for which to obtain absolute path information
     /// The fully qualified location of path, such as "C:\MyFile.txt"
     protected virtual string GetFullPath(string path)
     {
         if (string.IsNullOrEmpty(path))
             throw new RoxyFilemanException("NoFilesFound");

         path = path.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

         if (Path.IsPathRooted(path))
             throw new RoxyFilemanException("NoFilesFound");

         var fullPath = Path.GetFullPath(Path.Combine(Root, path));

         if (!IsUnderneathRoot(fullPath))
             throw new RoxyFilemanException("NoFilesFound");

         return fullPath;
     }

     /// 
     /// Get image format by mime type
     /// 
     /// Mime type
     /// SKEncodedImageFormat
     protected virtual SKEncodedImageFormat GetImageFormatByMimeType(string mimeType)
     {
         var format = SKEncodedImageFormat.Jpeg;
         if (string.IsNullOrEmpty(mimeType))
             return format;

         var parts = mimeType.ToLowerInvariant().Split('/');
         var lastPart = parts[^1];

         switch (lastPart)
         {
             case "webp":
                 format = SKEncodedImageFormat.Webp;
                 break;
             case "png":
             case "gif":
             case "bmp":
             case "x-icon":
                 format = SKEncodedImageFormat.Png;
                 break;
             default:
                 break;
         }

         return format;
     }

     /// 
     /// Get the unique name of the file (add -copy-(N) to the file name if there is already a file with that name in the directory)
     /// 
     /// Path to the file directory
     /// Original file name
     /// Unique name of the file
     protected virtual string GetUniqueFileName(string directoryPath, string fileName)
     {
         var uniqueFileName = fileName;

         var i = 0;
         while (GetFileInfo(Path.Combine(directoryPath, uniqueFileName)) is IFileInfo fileInfo && fileInfo.Exists)
         {
             uniqueFileName = $"{Path.GetFileNameWithoutExtension(fileName)}-Copy-{++i}{Path.GetExtension(fileName)}";
         }

         return uniqueFileName;
     }

     /// 
     /// Check the specified path is in the root directory of this instance
     /// 
     /// The absolute path
     /// True if passed path is in the root; otherwise false
     protected virtual bool IsUnderneathRoot(string fullPath)
     {
         return fullPath
             .StartsWith(Root, StringComparison.OrdinalIgnoreCase);
     }

     /// 
     /// Scale image to fit the destination sizes
     /// 
     /// Image data
     /// SkiaSharp image format
     /// Target width
     /// Target height
     /// The byte array of resized image
     protected virtual byte[] ResizeImage(byte[] data, SKEncodedImageFormat format, int maxWidth, int maxHeight)
     {
         using var sourceStream = new SKMemoryStream(data);
         using var inputData = SKData.Create(sourceStream);
         using var image = SKBitmap.Decode(inputData);

         var (width, height) = ValidateImageMeasures(image, maxWidth, maxHeight);

         var toBitmap = new SKBitmap(width, height, image.ColorType, image.AlphaType);

         if (!image.ScalePixels(toBitmap, SKFilterQuality.None))
             throw new Exception("Image scaling");

         var newImage = SKImage.FromBitmap(toBitmap);
         var imageData = newImage.Encode(format, _mediaSettings.DefaultImageQuality);

         newImage.Dispose();
         return imageData.ToArray();
     }

     #endregion

     #region Methods

     /// 
     /// Moves a file or a directory and its contents to a new location
     /// 
     /// The path of the file or directory to move
     /// 
     /// The path to the new location for sourceDirName. If sourceDirName is a file, then destDirName
     /// must also be a file name
     /// 
     public virtual void DirectoryMove(string sourceDirName, string destDirName)
     {
         if (destDirName.StartsWith(sourceDirName, StringComparison.InvariantCulture))
             throw new RoxyFilemanException("E_CannotMoveDirToChild");

         var sourceDirInfo = new DirectoryInfo(GetFullPath(sourceDirName));
         if (!sourceDirInfo.Exists)
             throw new RoxyFilemanException("E_MoveDirInvalisPath");

         if (string.Equals(sourceDirInfo.FullName, Root, StringComparison.InvariantCultureIgnoreCase))
             throw new RoxyFilemanException("E_MoveDir");

         var newFullPath = Path.Combine(GetFullPath(destDirName), sourceDirInfo.Name);
         var destinationDirInfo = new DirectoryInfo(newFullPath);
         if (destinationDirInfo.Exists)
             throw new RoxyFilemanException("E_DirAlreadyExists");

         try
         {
             sourceDirInfo.MoveTo(destinationDirInfo.FullName);
         }
         catch
         {
             throw new RoxyFilemanException("E_MoveDir");
         }
     }

     /// 
     /// Locate a file at the given path by directly mapping path segments to physical directories.
     /// 
     /// A path under the root directory
     /// The file information. Caller must check Microsoft.Extensions.FileProviders.IFileInfo.Exists property.
     public new IFileInfo GetFileInfo(string subpath)
     {
         if (string.IsNullOrEmpty(subpath))
             return new NotFoundFileInfo(subpath);

         subpath = subpath.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

         // Absolute paths not permitted.
         if (Path.IsPathRooted(subpath))
             return new NotFoundFileInfo(subpath);

         return base.GetFileInfo(subpath);
     }

     /// 
     /// Create configuration file for RoxyFileman
     /// 
     /// The base path for the store
     /// Two-letter language code
     /// A task that represents the asynchronous operation
     public virtual async Task GetOrCreateConfigurationAsync(string pathBase, string lang)
     {
         //check whether the path base has changed, otherwise there is no need to overwrite the configuration file
         if (Singleton.Instance?.RETURN_URL_PREFIX?.Equals(pathBase) ?? false)
         {
             return Singleton.Instance;
         }

         var filePath = _nopFileProvider.GetAbsolutePath(NopRoxyFilemanDefaults.ConfigurationFile);

         //create file if not exists
         _nopFileProvider.CreateFile(filePath);

         //try to read existing configuration
         var existingText = await _nopFileProvider.ReadAllTextAsync(filePath, Encoding.UTF8);
         var existingConfiguration = JsonConvert.DeserializeObject(existingText);

         //create configuration
         var configuration = new RoxyFilemanConfig
         {
             FILES_ROOT = existingConfiguration?.FILES_ROOT ?? NopRoxyFilemanDefaults.DefaultRootDirectory,
             SESSION_PATH_KEY = existingConfiguration?.SESSION_PATH_KEY ?? string.Empty,
             THUMBS_VIEW_WIDTH = existingConfiguration?.THUMBS_VIEW_WIDTH ?? 140,
             THUMBS_VIEW_HEIGHT = existingConfiguration?.THUMBS_VIEW_HEIGHT ?? 120,
             PREVIEW_THUMB_WIDTH = existingConfiguration?.PREVIEW_THUMB_WIDTH ?? 300,
             PREVIEW_THUMB_HEIGHT = existingConfiguration?.PREVIEW_THUMB_HEIGHT ?? 200,
             MAX_IMAGE_WIDTH = existingConfiguration?.MAX_IMAGE_WIDTH ?? _mediaSettings.MaximumImageSize,
             MAX_IMAGE_HEIGHT = existingConfiguration?.MAX_IMAGE_HEIGHT ?? _mediaSettings.MaximumImageSize,
             DEFAULTVIEW = existingConfiguration?.DEFAULTVIEW ?? "list",
             FORBIDDEN_UPLOADS = existingConfiguration?.FORBIDDEN_UPLOADS ?? string.Join(" ", NopRoxyFilemanDefaults.ForbiddenUploadExtensions),
             ALLOWED_UPLOADS = existingConfiguration?.ALLOWED_UPLOADS ?? string.Empty,
             FILEPERMISSIONS = existingConfiguration?.FILEPERMISSIONS ?? "0644",
             DIRPERMISSIONS = existingConfiguration?.DIRPERMISSIONS ?? "0755",
             LANG = existingConfiguration?.LANG ?? lang,
             DATEFORMAT = existingConfiguration?.DATEFORMAT ?? "dd/MM/yyyy HH:mm",
             OPEN_LAST_DIR = existingConfiguration?.OPEN_LAST_DIR ?? "yes",

             //no need user to configure
             INTEGRATION = "custom",
             RETURN_URL_PREFIX = $"{pathBase}/images/uploaded/",
             DIRLIST = $"{pathBase}/Admin/RoxyFileman/DirectoriesList",
             CREATEDIR = $"{pathBase}/Admin/RoxyFileman/CreateDirectory",
             DELETEDIR = $"{pathBase}/Admin/RoxyFileman/DeleteDirectory",
             MOVEDIR = $"{pathBase}/Admin/RoxyFileman/MoveDirectory",
             COPYDIR = $"{pathBase}/Admin/RoxyFileman/CopyDirectory",
             RENAMEDIR = $"{pathBase}/Admin/RoxyFileman/RenameDirectory",
             FILESLIST = $"{pathBase}/Admin/RoxyFileman/FilesList",
             UPLOAD = $"{pathBase}/Admin/RoxyFileman/UploadFiles",
             DOWNLOAD = $"{pathBase}/Admin/RoxyFileman/DownloadFile",
             DOWNLOADDIR = $"{pathBase}/Admin/RoxyFileman/DownloadDirectory",
             DELETEFILE = $"{pathBase}/Admin/RoxyFileman/DeleteFile",
             MOVEFILE = $"{pathBase}/Admin/RoxyFileman/MoveFile",
             COPYFILE = $"{pathBase}/Admin/RoxyFileman/CopyFile",
             RENAMEFILE = $"{pathBase}/Admin/RoxyFileman/RenameFile",
             GENERATETHUMB = $"{pathBase}/Admin/RoxyFileman/CreateImageThumbnail"
         };

         //save the file
         var text = JsonConvert.SerializeObject(configuration, Formatting.Indented);
         await File.WriteAllTextAsync(filePath, text, Encoding.UTF8);

         Singleton.Instance = configuration;

         return configuration;
     }

     /// 
     /// Get all available directories as a directory tree
     /// 
     /// Type of the file
     /// A value indicating whether to return a directory tree recursively
     /// Path to start directory
     public virtual IEnumerable GetDirectories(string type, bool isRecursive = true, string rootDirectoryPath = "")
     {
         foreach (var item in GetDirectoryContents(rootDirectoryPath))
         {
             if (item.IsDirectory)
             {
                 var dirInfo = new DirectoryInfo(item.PhysicalPath);

                 yield return new RoxyDirectoryInfo(
                     getRelativePath(item.Name),
                     dirInfo.GetFiles().Count(x => isMatchType(x.Name)),
                     dirInfo.GetDirectories().Length);
             }

             if (!isRecursive)
                 break;

             foreach (var subDir in GetDirectories(type, isRecursive, getRelativePath(item.Name)))
                 yield return subDir;
         }

         string getRelativePath(string name) => Path.Combine(rootDirectoryPath, name);
         bool isMatchType(string name) => string.IsNullOrEmpty(type) || GetFileType(name) == type;
     }

     /// 
     /// Get files in the passed directory
     /// 
     /// Path to the files directory
     /// Type of the files
     /// 
     /// The list of 
     /// 
     public virtual IEnumerable GetFiles(string directoryPath = "", string type = "")
     {
         var files = GetDirectoryContents(directoryPath);

         return files
             .Where(f => !f.IsDirectory && isMatchType(f.Name))
             .Select(f =>
             {
                 var width = 0;
                 var height = 0;

                 if (GetFileType(f.Name) == "image")
                 {
                     using var skData = SKData.Create(f.PhysicalPath);
                        
                     if (skData != null)
                     {
                         var image = SKBitmap.DecodeBounds(skData);

                         width = image.Width;
                         height = image.Height;
                     }
                 }

                 return new RoxyFileInfo(getRelativePath(f.Name), f.LastModified, f.Length, width, height);
             });

         bool isMatchType(string name) => string.IsNullOrEmpty(type) || GetFileType(name) == type;
         string getRelativePath(string name) => Path.Combine(directoryPath, name);
     }

     /// 
     /// Moves a specified file to a new location, providing the option to specify a new file name
     /// 
     /// The name of the file to move. Can include a relative or absolute path
     /// The new path and name for the file
     public virtual void FileMove(string sourcePath, string destinationPath)
     {
         var sourceFile = GetFileInfo(sourcePath);

         if (!sourceFile.Exists)
             throw new RoxyFilemanException("E_MoveFileInvalisPath");

         var destinationFile = GetFileInfo(destinationPath);
         if (destinationFile.Exists)
             throw new RoxyFilemanException("E_MoveFileAlreadyExists");

         try
         {
             new FileInfo(sourceFile.PhysicalPath)
                 .MoveTo(GetFullPath(destinationPath));
         }
         catch
         {
             throw new RoxyFilemanException("E_MoveFile");
         }
     }

     /// 
     /// Copy the directory with the embedded files and directories
     /// 
     /// Path to the source directory
     /// Path to the destination directory
     public virtual void CopyDirectory(string sourcePath, string destinationPath)
     {
         var sourceDirInfo = new DirectoryInfo(GetFullPath(sourcePath));
         if (!sourceDirInfo.Exists)
             throw new RoxyFilemanException("E_CopyDirInvalidPath");

         var newPath = Path.Combine(GetFullPath(destinationPath), sourceDirInfo.Name);
         var destinationDirInfo = new DirectoryInfo(newPath);

         if (destinationDirInfo.Exists)
             throw new RoxyFilemanException("E_DirAlreadyExists");

         try
         {
             destinationDirInfo.Create();

             foreach (var file in sourceDirInfo.GetFiles())
             {
                 var newFile = GetFileInfo(Path.Combine(destinationPath, file.Name));
                 if (!newFile.Exists)
                     file.CopyTo(Path.Combine(destinationDirInfo.FullName, file.Name));
             }

             foreach (var directory in sourceDirInfo.GetDirectories())
             {
                 var destinationSubPath = Path.Combine(destinationPath, sourceDirInfo.Name, directory.Name);
                 var sourceSubPath = Path.Combine(sourcePath, directory.Name);
                 CopyDirectory(sourceSubPath, destinationSubPath);
             }
         }
         catch
         {
             throw new RoxyFilemanException("E_CopyFile");
         }
     }

     /// 
     /// Rename the directory
     /// 
     /// Path to the source directory
     /// New name of the directory
     /// A task that represents the asynchronous operation
     public virtual void RenameDirectory(string sourcePath, string newName)
     {
         try
         {
             var destinationPath = Path.Combine(Path.GetDirectoryName(sourcePath), newName);
             DirectoryMove(sourcePath, destinationPath);
         }
         catch (Exception ex)
         {
             throw new RoxyFilemanException("E_RenameDir", ex);
         }
     }

     /// 
     /// Rename the file
     /// 
     /// Path to the source file
     /// New name of the file
     /// A task that represents the asynchronous operation
     public virtual void RenameFile(string sourcePath, string newName)
     {
         try
         {
             var destinationPath = Path.Combine(Path.GetDirectoryName(sourcePath), newName);
             FileMove(sourcePath, destinationPath);
         }
         catch (Exception ex)
         {
             throw new RoxyFilemanException("E_RenameFile", ex);
         }
     }

     /// 
     /// Delete the file
     /// 
     /// Path to the file
     /// A task that represents the asynchronous operation
     public virtual void DeleteFile(string path)
     {
         var fileToDelete = GetFileInfo(path);

         if (!fileToDelete.Exists)
             throw new RoxyFilemanException("E_DeleteFileInvalidPath");

         try
         {
             File.Delete(GetFullPath(path));
         }
         catch
         {
             throw new RoxyFilemanException("E_DeleteFile");
         }
     }

     /// 
     /// Copy the file
     /// 
     /// Path to the source file
     /// Path to the destination file
     /// A task that represents the asynchronous operation
     public virtual void CopyFile(string sourcePath, string destinationPath)
     {
         var sourceFile = GetFileInfo(sourcePath);

         if (!sourceFile.Exists)
             throw new RoxyFilemanException("E_CopyFileInvalidPath");

         var newFilePath = Path.Combine(destinationPath, sourceFile.Name);
         var destinationFile = GetFileInfo(newFilePath);

         if (destinationFile.Exists)
             newFilePath = Path.Combine(destinationPath, GetUniqueFileName(destinationPath, sourceFile.Name));

         try
         {
             File.Copy(sourceFile.PhysicalPath, GetFullPath(newFilePath));
         }
         catch
         {
             throw new RoxyFilemanException("E_CopyFile");
         }
     }

     /// 
     /// Create the new directory
     /// 
     /// Path to the parent directory
     /// Name of the new directory
     /// A task that represents the asynchronous operation
     public virtual void CreateDirectory(string parentDirectoryPath, string name)
     {
         //validate path and get absolute form
         var fullPath = GetFullPath(Path.Combine(parentDirectoryPath, name));

         var newDirectory = new DirectoryInfo(fullPath);
         if (!newDirectory.Exists)
             newDirectory.Create();
     }

     /// 
     /// Delete the directory
     /// 
     /// Path to the directory
     public virtual void DeleteDirectory(string path)
     {
         var sourceDirInfo = new DirectoryInfo(GetFullPath(path));
         if (!sourceDirInfo.Exists)
             throw new RoxyFilemanException("E_DeleteDirInvalidPath");

         if (string.Equals(sourceDirInfo.FullName, Root, StringComparison.InvariantCultureIgnoreCase))
             throw new RoxyFilemanException("E_CannotDeleteRoot");

         if (sourceDirInfo.GetFiles().Length > 0 || sourceDirInfo.GetDirectories().Length > 0)
             throw new RoxyFilemanException("E_DeleteNonEmpty");

         try
         {
             sourceDirInfo.Delete();
         }
         catch
         {
             throw new RoxyFilemanException("E_CannotDeleteDir");
         }
     }

     /// 
     /// Save file in the root directory for this instance
     /// 
     /// Directory path in the root
     /// The file name and extension
     /// Mime type
     /// The stream to read
     /// A task that represents the asynchronous operation
     public virtual async Task SaveFileAsync(string directoryPath, string fileName, string contentType, Stream fileStream)
     {
         var uniqueFileName = GetUniqueFileName(directoryPath, Path.GetFileName(fileName));
         var destinationFile = Path.Combine(directoryPath, uniqueFileName);

         await using var stream = new FileStream(GetFullPath(destinationFile), FileMode.Create);

         if (GetFileType(Path.GetExtension(uniqueFileName)) == "image")
         {
             using var memoryStream = new MemoryStream();
             await fileStream.CopyToAsync(memoryStream);

             var roxyConfig = Singleton.Instance;

             var imageData = ResizeImage(memoryStream.ToArray(),
                 GetImageFormatByMimeType(contentType),
                 roxyConfig?.MAX_IMAGE_WIDTH ?? _mediaSettings.MaximumImageSize,
                 roxyConfig?.MAX_IMAGE_HEIGHT ?? _mediaSettings.MaximumImageSize);

             await stream.WriteAsync(imageData);
         }
         else
         {
             await fileStream.CopyToAsync(stream);
         }

         await stream.FlushAsync();
     }

     /// 
     /// Get the thumbnail of the image
     /// 
     /// File path
     /// Mime type
     /// Byte array of the specified image
     public virtual byte[] CreateImageThumbnail(string sourcePath, string contentType)
     {
         var imageInfo = GetFileInfo(sourcePath);

         if (!imageInfo.Exists)
             throw new RoxyFilemanException("Image not found");

         var roxyConfig = Singleton.Instance;

         using var imageStream = imageInfo.CreateReadStream();
         using var ms = new MemoryStream();

         imageStream.CopyTo(ms);

         return ResizeImage(
             ms.ToArray(),
             GetImageFormatByMimeType(contentType),
             roxyConfig.THUMBS_VIEW_WIDTH,
             roxyConfig.THUMBS_VIEW_HEIGHT);
     }

     /// 
     /// Create a zip archive of the specified directory.
     /// 
     /// The directory path with files to compress
     /// The byte array
     public virtual byte[] CreateZipArchiveFromDirectory(string directoryPath)
     {
         var sourceDirInfo = new DirectoryInfo(GetFullPath(directoryPath));
         if (!sourceDirInfo.Exists)
             throw new RoxyFilemanException("E_CreateArchive");

         using var memoryStream = new MemoryStream();
         using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
         {
             foreach (var file in sourceDirInfo.EnumerateFiles("*", SearchOption.AllDirectories))
             {
                 var fileRelativePath = file.FullName.Replace(sourceDirInfo.FullName, string.Empty)
                     .TrimStart('\\');

                 using var fileStream = file.OpenRead();
                 using var fileStreamInZip = archive.CreateEntry(fileRelativePath).Open();
                 fileStream.CopyTo(fileStreamInZip);
             }
         }

         //ToArray() should be outside of the archive using
         return memoryStream.ToArray();
     }

     #endregion
 }