简体   繁体   中英

Copy files from directory recursively smallest first

I want to copy all files in a directory to a destination folder using Visual C# .NET version 3.5, which can be done pretty easily (taken from this answer):

private static void Copy(string sourceDir, string targetDir)
{
    Directory.CreateDirectory(targetDir);

    foreach (var file in Directory.GetFiles(sourceDir))
        File.Copy(file, Path.Combine(targetDir, Path.GetFileName(file)));

    foreach (var directory in Directory.GetDirectories(sourceDir))
        Copy(directory, Path.Combine(targetDir, Path.GetFileName(directory)));
}

Now, there's one little problem here: I want it to sort all files smallest first, so if the source path is a removable drive, which gets plugged out after some time, it would still have most possible data copied. With upper algorithm, if it takes a directory containing big files first and continues with a directory containing many smaller ones afterwards, there is the chance that the user will plug his drive out while the software is still copying the big file, and nothing will stay on the drive except the incomplete big file.

My idea was to do multiple loops: First, every filepath would be put in a dictionary including its size, then this dictionary would get sorted, and then every file would be copied from source to destination (including folder creation).

I'm afraid this is not a very neat solution, since looping two times about the same just doesn't seem right to me. Also, I'm not sure if my dictionary can store that much information, if the source folder has got too many different files and subfolders.

Are there any better options to choose from?

You could use a simpler method based on the fact that you can get all the files in a directory subtree just asking for them without using recursion.

The missing piece of the problem is the file size. This information could be obtained using the DirectoryInfo class and the FileInfo class while the ordering is just a Linq instruction applied to the sequence of files as in the following example.

private static void Copy(string sourceDir, string targetDir)
{
    DirectoryInfo di = new DirectoryInfo(sourceDir);
    foreach (FileInfo fi in di.GetFiles("*.*", SearchOption.AllDirectories).OrderBy(d => d.Length))
    {
        string leftOver = fi.DirectoryName.Replace(sourceDir, "");
        string destFolder = Path.Combine(targetDir, leftOver);

        // Because the files are listed in order by size
        // we could copy a file deep down in the subtree before the 
        // ones at the top of the sourceDir

        // Luckily CreateDirectory doesn't throw if the directory exists
        // and automatically creates all the intermediate folders required
        Directory.CreateDirectory(destFolder);

        // Just write the intended copy parameters as a proof of concept
        Console.WriteLine($"{fi.Name} with size = {fi.Length} -> Copy from {fi.DirectoryName} to {Path.Combine(destFolder, fi.Name)}");
    }
}

In this example I have changed the File.Copy method with a Console.WriteLine just to have a proof of concept without copying anything, but the substitution is trivial.

Notice also that it is better to use EnumerateFiles instead of GetFiles as explained in the MSDN documentation

I hope this helps!

First; get all files from the source directory, with recursion being optional. Then proceed to copy all files ordered by size to the target directory.

void CopyFiles(string sourceDir, string targetDir, bool recursive = false)
{
    foreach (var file in GetFiles(sourceDir, recursive).OrderBy(f => f.Length))
    {
        var subDir = file.DirectoryName
            .Replace(sourceDir, String.Empty)
            .TrimStart(Path.DirectorySeparatorChar);
        var fullTargetDir = Path.Combine(targetDir, subDir);

        if (!Directory.Exists(fullTargetDir))
            Directory.CreateDirectory(fullTargetDir);

        file.CopyTo(Path.Combine(fullTargetDir, file.Name));
    }
}

IEnumerable<FileInfo> GetFiles(string directory, bool recursive)
{
    var files = new List<FileInfo>();

    if (recursive)
    {
        foreach (var subDirectory in Directory.GetDirectories(directory))
            files.AddRange(GetFiles(subDirectory, true));
    }

    foreach (var file in Directory.GetFiles(directory))
        files.Add(new FileInfo(file));

    return files;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM