public static async Task <CopyDirectoryStatus> CopyDirAsync(FileSystem srcFileSystem, string srcPath, FileSystem destFileSystem, string destPath, CopyDirectoryParams?param = null, object?state = null, CopyDirectoryStatus?statusObject = null, CancellationToken cancel = default) { CopyDirectoryStatus status = statusObject ?? new CopyDirectoryStatus(); status.Clear(); status.State = state; status.StartTick = Tick64.Now; if (param == null) { param = new CopyDirectoryParams(); } srcPath = await srcFileSystem.NormalizePathAsync(srcPath, cancel : cancel); destPath = await destFileSystem.NormalizePathAsync(destPath, cancel : cancel); if (srcFileSystem == destFileSystem) { if (srcFileSystem.PathParser.PathStringComparer.Equals(srcPath, destPath)) { throw new FileException(destPath, "Both source and destination is the same directory."); } } using (ProgressReporterBase dirReporter = param.EntireProgressReporterFactory.CreateNewReporter($"CopyDir '{srcFileSystem.PathParser.GetFileName(srcPath)}'", state)) { DirectoryWalker walker = new DirectoryWalker(srcFileSystem); bool walkRet = await walker.WalkDirectoryAsync(srcPath, async (dirInfo, entries, c) => { c.ThrowIfCancellationRequested(); foreach (FileSystemEntity entity in entries) { c.ThrowIfCancellationRequested(); try { if (await param.ProgressCallbackProc(status, entity) == false) { throw new OperationCanceledException($"Copying of the file '{entity.FullPath}' is cancaled by the user."); } string entryName = entity.Name; if (entity.IsCurrentDirectory) { entryName = ""; } string srcFullPath = srcFileSystem.PathParser.Combine(srcPath, dirInfo.RelativePath, entryName); string destFullPath = destFileSystem.PathParser.Combine(destPath, srcFileSystem.PathParser.ConvertDirectorySeparatorToOtherSystem(dirInfo.RelativePath, destFileSystem.PathParser), entryName); if (entity.IsDirectory == false) { // Copy a file lock (status.LockObj) status.NumFilesTotal++; FileMetadataGetFlags metadataGetFlags = FileMetadataCopier.CalcOptimizedMetadataGetFlags(param.FileMetadataCopier.Mode | FileMetadataCopyMode.Attributes); FileMetadata srcFileMetadata = await srcFileSystem.GetFileMetadataAsync(srcFullPath, metadataGetFlags, cancel); FileFlags copyFileAdditionalFlags = FileFlags.None; lock (status.LockObj) status.SizeTotal += srcFileMetadata.Size; if (param.CopyDirFlags.Bit(CopyDirectoryFlags.CopyFileCompressionFlag)) { if (srcFileMetadata.Attributes is FileAttributes attr) { if (attr.Bit(FileAttributes.Compressed)) { copyFileAdditionalFlags |= FileFlags.OnCreateSetCompressionFlag; } else { copyFileAdditionalFlags |= FileFlags.OnCreateRemoveCompressionFlag; } } } if (param.CopyDirFlags.Bit(CopyDirectoryFlags.CopyFileSparseFlag)) { if (srcFileMetadata.Attributes is FileAttributes attr) { if (attr.Bit(FileAttributes.SparseFile)) { copyFileAdditionalFlags |= FileFlags.SparseFile; } } } var copyFileParam = param.GenerateCopyFileParams(copyFileAdditionalFlags); RefBool ignoredReadError = new RefBool(false); await CopyFileAsync(srcFileSystem, srcFullPath, destFileSystem, destFullPath, copyFileParam, state, cancel, ignoredReadError); lock (status.LockObj) { status.NumFilesOk++; status.SizeOk += srcFileMetadata.Size; if (ignoredReadError) { status.IgnoreReadErrorFileNameList.Add(srcFullPath); } } } else { // Make a directory lock (status.LockObj) { status.NumDirectoriesTotal++; } FileMetadataGetFlags metadataGetFlags = FileMetadataCopier.CalcOptimizedMetadataGetFlags(param.DirectoryMetadataCopier.Mode | FileMetadataCopyMode.Attributes); FileMetadata srcDirMetadata = await srcFileSystem.GetDirectoryMetadataAsync(srcFullPath, metadataGetFlags, cancel); FileFlags copyDirFlags = FileFlags.None; if (param.CopyDirFlags.Bit(CopyDirectoryFlags.BackupMode)) { copyDirFlags |= FileFlags.BackupMode; } if (param.CopyDirFlags.Bit(CopyDirectoryFlags.CopyDirectoryCompressionFlag)) { if (srcDirMetadata.Attributes is FileAttributes attr) { if (attr.Bit(FileAttributes.Compressed)) { copyDirFlags |= FileFlags.OnCreateSetCompressionFlag; } else { copyDirFlags |= FileFlags.OnCreateRemoveCompressionFlag; } } } await destFileSystem.CreateDirectoryAsync(destFullPath, copyDirFlags, cancel); FileMetadata dstDirMetadata = param.DirectoryMetadataCopier.Copy(srcDirMetadata); if (param.CopyDirFlags.Bit(CopyDirectoryFlags.SetAclProtectionFlagOnRootDir)) { if (dirInfo.IsRoot) { if (dstDirMetadata.Security != null) { if (dstDirMetadata.Security.Acl != null) { dstDirMetadata.Security.Acl.Win32AclSddl = "!" + dstDirMetadata.Security.Acl.Win32AclSddl; } if (dstDirMetadata.Security.Audit != null) { dstDirMetadata.Security.Audit.Win32AuditSddl = "!" + dstDirMetadata.Security.Audit.Win32AuditSddl; } } } } await destFileSystem.SetDirectoryMetadataAsync(destFullPath, dstDirMetadata, cancel); lock (status.LockObj) status.NumDirectoriesOk++; } } catch (Exception ex) { if (await param.ExceptionCallbackProc(status, entity, ex) == false) { throw ex; } } } return(true); }, async (dirInfo, entries, c) => { c.ThrowIfCancellationRequested(); foreach (FileSystemEntity entity in entries) { c.ThrowIfCancellationRequested(); string entryName = entity.Name; if (entity.IsCurrentDirectory) { entryName = ""; } string srcFullPath = srcFileSystem.PathParser.Combine(srcPath, dirInfo.RelativePath, entryName); string destFullPath = destFileSystem.PathParser.Combine(destPath, srcFileSystem.PathParser.ConvertDirectorySeparatorToOtherSystem(dirInfo.RelativePath, destFileSystem.PathParser), entryName); if (entity.IsDirectory) { // Update the directory LastWriteTime metadata after placing all inside files into the directory if (param.DirectoryMetadataCopier.Mode.BitAny(FileMetadataCopyMode.TimeAll)) { FileMetadataGetFlags metadataGetFlags = FileMetadataCopier.CalcOptimizedMetadataGetFlags(param.DirectoryMetadataCopier.Mode & (FileMetadataCopyMode.TimeAll)); FileMetadata srcDirMetadata = await srcFileSystem.GetDirectoryMetadataAsync(srcFullPath, metadataGetFlags, cancel); FileMetadata dstDirMetadata = param.DirectoryMetadataCopier.Copy(srcDirMetadata); await destFileSystem.SetDirectoryMetadataAsync(destFullPath, dstDirMetadata, cancel); } } } return(true); }, async (dirInfo, exception, c) => { c.ThrowIfCancellationRequested(); if (await param.ExceptionCallbackProc(status, dirInfo.Entity, exception) == false) { throw exception; } return(true); }, param.CopyDirFlags.Bit(CopyDirectoryFlags.Recursive), cancel ); } status.EndTick = Tick64.Now; return(status); }