public async Task <FolderOperationResult> CopyFolderAsync( string path, string destinationPath, DuplicateEntryHandling dupeEntryHandling = DuplicateEntryHandling.Skip, CancellationToken cancelToken = default) { Guard.NotEmpty(path, nameof(path)); Guard.NotEmpty(destinationPath, nameof(destinationPath)); path = FolderService.NormalizePath(path); ValidateFolderPath(path, "CopyFolder", nameof(path)); destinationPath = FolderService.NormalizePath(destinationPath); if (destinationPath.EnsureEndsWith("/").StartsWith(path.EnsureEndsWith("/"))) { throw new ArgumentException(T("Admin.Media.Exception.DescendantFolder", destinationPath, path), nameof(destinationPath)); } var node = _folderService.GetNodeByPath(path); if (node == null) { throw _exceptionFactory.FolderNotFound(path); } if (node.Value.IsAlbum) { throw new NotSupportedException(T("Admin.Media.Exception.CopyRootAlbum", node.Value.Name)); } using (var scope = new DbContextScope(_db, autoDetectChanges: false, deferCommit: true)) { destinationPath += "/" + node.Value.Name; var dupeFiles = new List <DuplicateFileInfo>(); // >>>> Do the heavy stuff var folder = await InternalCopyFolder(scope, node, destinationPath, dupeEntryHandling, dupeFiles, cancelToken); var result = new FolderOperationResult { Operation = "copy", DuplicateEntryHandling = dupeEntryHandling, Folder = folder, DuplicateFiles = dupeFiles }; return(result); } }
private async Task <(MediaFile Copy, bool IsDupe)> InternalCopyFile( MediaFile file, MediaPathData destPathData, bool copyData, DuplicateEntryHandling dupeEntryHandling, Func <Task <MediaFile> > dupeFileSelector, Func <MediaPathData, Task> uniqueFileNameChecker) { // Find dupe and handle var isDupe = false; var dupe = await dupeFileSelector(); if (dupe != null) { switch (dupeEntryHandling) { case DuplicateEntryHandling.Skip: await uniqueFileNameChecker(destPathData); return(dupe, true); case DuplicateEntryHandling.ThrowError: var fullPath = destPathData.FullPath; await uniqueFileNameChecker(destPathData); throw _exceptionFactory.DuplicateFile(fullPath, ConvertMediaFile(dupe), destPathData.FullPath); case DuplicateEntryHandling.Rename: await uniqueFileNameChecker(destPathData); dupe = null; break; case DuplicateEntryHandling.Overwrite: if (file.FolderId == destPathData.Folder.Id) { throw new IOException(T("Admin.Media.Exception.Overwrite")); } break; } } isDupe = dupe != null; var copy = dupe ?? new MediaFile(); // Simple clone MapMediaFile(file, copy); // Set folder id copy.FolderId = destPathData.Folder.Id; // A copied file cannot stay in deleted state copy.Deleted = false; // Set name stuff if (!copy.Name.EqualsNoCase(destPathData.FileName)) { copy.Name = destPathData.FileName; copy.Extension = destPathData.Extension; copy.MimeType = destPathData.MimeType; } // Save to DB if (isDupe) { _db.TryUpdate(copy); } else { _db.MediaFiles.Add(copy); } // Copy data: blob, alt, title etc. if (copyData) { await InternalCopyFileData(file, copy); } return(copy, isDupe); }
private MediaFolderInfo InternalCopyFolder(TreeNode <MediaFolderNode> sourceNode, string destPath, DuplicateEntryHandling dupeEntryHandling, IList <DuplicateFileInfo> dupeFiles) { // Get dest node var destNode = _folderService.GetNodeByPath(destPath); // Dupe handling if (destNode != null && dupeEntryHandling == DuplicateEntryHandling.ThrowError) { throw _exceptionFactory.DuplicateFolder(sourceNode.Value.Path, destNode.Value); } var doDupeCheck = destNode != null; // Create dest folder if (destNode == null) { destNode = CreateFolder(destPath); } var ctx = _fileRepo.Context; // INFO: we gonna change file name during the files loop later. var destPathData = new MediaPathData(destNode, "placeholder.txt"); // Get all source files in one go var files = _searcher.SearchFiles( new MediaSearchQuery { FolderId = sourceNode.Value.Id }, MediaLoadFlags.AsNoTracking | MediaLoadFlags.WithTags).Load(); IDictionary <string, MediaFile> destFiles = null; HashSet <string> destNames = null; if (doDupeCheck) { // Get all files in destination folder for faster dupe selection destFiles = _searcher.SearchFiles(new MediaSearchQuery { FolderId = destNode.Value.Id }, MediaLoadFlags.None).ToDictionarySafe(x => x.Name); // Make a HashSet from all file names in the destination folder for faster unique file name lookups destNames = new HashSet <string>(destFiles.Keys, StringComparer.CurrentCultureIgnoreCase); } // Holds source and copy together, 'cause we perform a two-pass copy (file first, then data) var tuples = new List <(MediaFile, MediaFile)>(500); // Copy files batched foreach (var batch in files.Slice(500)) { foreach (var file in batch) { destPathData.FileName = file.Name; // >>> Do copy var copy = InternalCopyFile( file, destPathData, false /* copyData */, dupeEntryHandling, () => destFiles?.Get(file.Name), UniqueFileNameChecker, out var isDupe); if (copy != null) { if (isDupe) { dupeFiles.Add(new DuplicateFileInfo { SourceFile = ConvertMediaFile(file, sourceNode.Value), DestinationFile = ConvertMediaFile(copy, destNode.Value), UniquePath = destPathData.FullPath }); } if (!isDupe || dupeEntryHandling != DuplicateEntryHandling.Skip) { // When dupe: add to processing queue only if file was NOT skipped tuples.Add((file, copy)); } } } // Save batch to DB (1st pass) ctx.SaveChanges(); // Now copy file data foreach (var op in tuples) { InternalCopyFileData(op.Item1, op.Item2); } // Save batch to DB (2nd pass) ctx.SaveChanges(); ctx.DetachEntities <MediaFolder>(); ctx.DetachEntities <MediaFile>(); tuples.Clear(); } // Copy folders foreach (var node in sourceNode.Children) { destPath = destNode.Value.Path + "/" + node.Value.Name; InternalCopyFolder(node, destPath, dupeEntryHandling, dupeFiles); } return(new MediaFolderInfo(destNode)); void UniqueFileNameChecker(MediaPathData pathData) { if (destNames != null && _helper.CheckUniqueFileName(pathData.FileTitle, pathData.Extension, destNames, out var uniqueName)) { pathData.FileName = uniqueName; } } }
private MediaFile InternalCopyFile( MediaFile file, MediaPathData destPathData, bool copyData, DuplicateEntryHandling dupeEntryHandling, Func <MediaFile> dupeFileSelector, Action <MediaPathData> uniqueFileNameChecker, out bool isDupe) { // Find dupe and handle isDupe = false; var dupe = dupeFileSelector(); if (dupe != null) { switch (dupeEntryHandling) { case DuplicateEntryHandling.Skip: isDupe = true; uniqueFileNameChecker(destPathData); return(dupe); case DuplicateEntryHandling.ThrowError: var fullPath = destPathData.FullPath; uniqueFileNameChecker(destPathData); throw _exceptionFactory.DuplicateFile(fullPath, ConvertMediaFile(dupe), destPathData.FullPath); case DuplicateEntryHandling.Rename: uniqueFileNameChecker(destPathData); dupe = null; break; case DuplicateEntryHandling.Overwrite: if (file.FolderId == destPathData.Folder.Id) { throw new IOException(T("Admin.Media.Exception.Overwrite")); } break; } } isDupe = dupe != null; var copy = dupe ?? new MediaFile(); // Simple clone MapMediaFile(file, copy); // Set folder id copy.FolderId = destPathData.Folder.Id; // A copied file cannot stay in deleted state copy.Deleted = false; // Set name stuff if (!copy.Name.IsCaseInsensitiveEqual(destPathData.FileName)) { copy.Name = destPathData.FileName; copy.Extension = destPathData.Extension; copy.MimeType = destPathData.MimeType; } // Save to DB if (isDupe) { _fileRepo.Update(copy); } else { _fileRepo.Insert(copy); } // Copy data: blob, alt, title etc. if (copyData) { InternalCopyFileData(file, copy); } return(copy); }