Apparently I can't move files on different volumes using Directory.Move.
I have read that I have to copy each file individually to the destination, then delete the source directory.
Do I have any other option?
Regardless of whether or not Directory.Move (or any other function) performed the move between volumes, it would essentially be doing a copy and delete anyway underneath. So if you want a speed increase, that's not going to happen. I think the best solution would be to write your own reusable move function, which would get the volume label (C:,D:) from the to and from paths, and then either perform a move, or copy+delete when necessary.
To my knowledge there is no other way however deleting a directory has a catch: Read Only Files might cause a UnauthorizedAccessException when deleting a directory and all of its contents.
This recurses a directory and unsets all the read only flags. Call before Directory.Delete:
public void removeReadOnlyDeep(string directory)
{
string[] files = Directory.GetFiles(directory);
foreach (string file in files)
{
FileAttributes attributes = File.GetAttributes(file);
if ((attributes & FileAttributes.ReadOnly) != 0)
{
File.SetAttributes(file, ~FileAttributes.ReadOnly);
}
}
string[] dirs = Directory.GetDirectories(directory);
foreach (string dir in dirs)
{
removeReadOnlyDeep(dir);
}
}
An easier option would be, to add a reference to the Microsoft.VisualBasic namespace and use the MoveDirectory method, which can move across volumes.
Microsoft.VisualBasic.FileIO.FileSystem.MoveDirectory(sourceDirName, destDirName);
Try to use this:
public static void RobustMove(string sourceDirectory, string destDirectory)
{
//move if directories are on the same volume
if (Path.GetPathRoot(source) == Path.GetPathRoot(destination))
{
Directory.Move(source, destination);
}
else
{
CopyDirectoryRecursive(source, destination);
Directory.Delete(source, true);
}
}
You will find CopyDirectoryRecursive function here:
This should be working until you use spanned volume or symbol links to another physical disk.
To be even more robust you can improve this function to use Move until System.IO .Exception is thrown and then to switch to copying and deleting.
Related
I'm using below code to make a copy a folder available on a network. This folder has, subfolders and files with total of 455 files and 13 folders and of size 409 MB.
My method is recursively calling itself to create a copy of sub folders and files in it. Overall, this method is taking more than 10 minutes to finish the task and I'm looking to speed up the process.
So far, I've went through different posts but did not find any better solution. Is there a better way to achieve my task or any improvements to my code for a faster execution?
void CopyDirectoryAndFiles(string sourceDirectory, string destinationDirectory, bool recursive)
{
// Get information about the source directory
var dir = new DirectoryInfo(sourceDirectory);
// Check if the source directory exists
if (!dir.Exists)
throw new DirectoryNotFoundException($"Source directory not found:dir.FullName}");
// Cache directories before we start copying
DirectoryInfo[] dirs = dir.GetDirectories();
// Create the destination directory
Directory.CreateDirectory(destinationDirectory);
// Get the files in the source directory and copy to the destination directory
foreach (FileInfo file in dir.GetFiles())
{
string targetFilePath = Path.Combine(destinationDirectory, file.Name);
file.CopyTo(targetFilePath);
}
// If recursive and copying subdirectories, recursively call this method
if (recursive)
{
foreach (DirectoryInfo subDir in dirs)
{
string newDestinationDirectory= Path.Combine(destinationDirectory,subDir.Name);
CopyDirectoryAndFiles(subDir.FullName, newDestinationDirectory, true);
}
}
}
Thanks for your help.
I don't think there's a way to increase performance in a drastic way, but I can suggest a few things to try:
replace foreach w/ Parallel.ForEach to copy data in several streams;
you can use an external tool (e.g. xcopy), which is optimized for the task, and call this tool from your C# code. xcopy can copy folders recursively if you specify the /e flag.
Why is it that File.Move(sourceFileName, destFileName) works fine when the source file and destination files are in different partitions, but Directory.Move(sourceDirName, destDirName) don't? It throws
System.IO.IOException: "Source and destination path must have
identical roots. Move will not work across volumes."
I even tried to create a DirectoryInfo instance and use the MoveTo(destDirName) method but without success.
Am I missing something? Do I really have to implement a "move" functionality myself? (the directory I want to move is very large btw).
You should Use Copy Function followed by a remove. As Move only works in the same drive.
Directory.Move has a condition that states that :
IO Exception will be thrown if an attempt was made to move a directory to a different volume.
Another option is, to add a reference to the Microsoft.VisualBasic namespace and use the MoveDirectory method, which can move across volumes.
Microsoft.VisualBasic.FileIO.FileSystem.MoveDirectory(sourceDirName, destDirName);
Although this is not a Vb.Net question but I found no one mentioned this method so I think might help... Only you need to convert it to C# if needed.
Code:
My.Computer.FileSystem.MoveDirectory(SrcDir,DestDir)
This works on different volume seamlessly/ per my experience.
Based on the posts "Copy a directory to a different drive" and "Non-recursive way to get all files in a directory and its subdirectories in Java", I wrote this non-recursive method and it works fine:
public static void Move(string source, string target)
{
if (!Directory.Exists(source))
{
throw new System.IO.DirectoryNotFoundException("Source directory couldn't be found.");
}
if (Directory.Exists(target))
{
throw new System.IO.IOException("Target directory already exists.");
}
DirectoryInfo sourceInfo = Directory.CreateDirectory(source);
DirectoryInfo targetInfo = Directory.CreateDirectory(target);
if (sourceInfo.FullName == targetInfo.FullName)
{
throw new System.IO.IOException("Source and target directories are the same.");
}
Stack<DirectoryInfo> sourceDirectories = new Stack<DirectoryInfo>();
sourceDirectories.Push(sourceInfo);
Stack<DirectoryInfo> targetDirectories = new Stack<DirectoryInfo>();
targetDirectories.Push(targetInfo);
while (sourceDirectories.Count > 0)
{
DirectoryInfo sourceDirectory = sourceDirectories.Pop();
DirectoryInfo targetDirectory = targetDirectories.Pop();
foreach (FileInfo file in sourceDirectory.GetFiles())
{
file.CopyTo(Path.Combine(targetDirectory.FullName, file.Name), overwrite: true);
}
foreach(DirectoryInfo subDirectory in sourceDirectory.GetDirectories())
{
sourceDirectories.Push(subDirectory);
targetDirectories.Push(targetDirectory.CreateSubdirectory(subDirectory.Name));
}
}
sourceInfo.Delete(true);
}
You can also p/invoke SHFileOperation which is the same function Windows Explorer uses to move directories around. It will either perform a true move or recursive-copy-then-delete, as appropriate.
It can also show the same progress UI as explorer, just by setting a flag.
I know this post is a little old... but there is a way around this! Don't try and move the directory, but zip it up and move it as a File.Move(src,dest); and you can then extract it and there you have it!
I had same problem in VB.NET and instead of "Directory.Move" I used MoveFolder with "FileSystemObject".
You can preserve creation dates with this method.
Scripting.FileSystemObject oFSO = new Scripting.FileSystemObject();
oFSO.MoveFolder(sourceDirName, destDirName)
i have this problem to and i like to solve it in this way
string startPath = #".\start";
string zipPath = #".\result.zip";
string extractPath = #".\extract";
ZipFile.CreateFromDirectory(startPath, zipPath);
ZipFile.ExtractToDirectory(zipPath, extractPath);
I know that if you want to delete a directory you have to delete all of it's files first.
However if you want to delete a directory which contains empty sub-directories, do you have to delete those sub-directories first? or can you just go ahead and delete the main directory?
Directory.Delete set the recurse flag to true, should do the job, no need to empty them first.
Directory.Delete(path, true);
I have just noticed that your tag refers to IsolatedStorage, in which case you will need to enumerate all the files and folders and delete as you go.
How to: Delete Files and Directories in Isolated Storage
You can try to delete recursively:
var path = Path.GetFullPath(#"C:\Temp\DeleteMe");
Directory.Delete(path,true); // true for recursive
This should delete everything including files if you have the proper permissions.
Why check if it is empty or not when you are going to delete it anyway.
You can use the Directory.Delete(yourpath,true) method only if you are sure that there isn't any readonly file in the directory. else it will throw an exception. Instead you can use your own recursive method like this which will first mark the file as normal before deleting it.
public static void DeleteDirectory(string target_dir)
{
string[] files = Directory.GetFiles(target_dir);
string[] dirs = Directory.GetDirectories(target_dir);
foreach (string file in files)
{
File.SetAttributes(file, FileAttributes.Normal);
File.Delete(file);
}
foreach (string dir in dirs)
{
DeleteDirectory(dir);
}
Directory.Delete(target_dir, false);
}
I want to make an exact copy of some files, directories and subdirectories that are on my USB drive I:/ and want them to be in C:/backup (for example)
My USB drive has the following structure:
(just to know, this is an example, my drive has more files, directories and subdirectories)
courses/data_structures/db.sql
games/pc/pc-game.exe
exams/exam01.doc
Well, I am not sure how to start with this but my first idea is to get all the files doing this:
string[] files = Directory.GetFiles("I:");
The next step could be to make a loop and use File.Copy specifying the destination path:
string destinationPath = #"C:/backup";
foreach (string file in files)
{
File.Copy(file, destinationPath + "\\" + Path.GetFileName(file), true);
}
At this point everything works good but not as I wanted cause this doesn't replicate the folder structure. Also some errors happen like the following...
The first one happens because my PC configuration shows hidden files for every folder and my USB has an AUTORUN.INF hidden file that is not hidden anymore and the loop tries to copy it and in the process generates this exception:
Access to the path 'AUTORUN.INF' is denied.
The second one happens when some paths are too long and this generates the following exception:
The specified path, file name, or both are too long. The fully
qualified file name must be less than 260 characters, and the
directory name must be less than 248 characters.
So, I am not sure how to achieve this and validate each posible case of error. I would like to know if there is another way to do this and how (maybe some library) or something more simple like an implemented method with the following structure:
File.CopyDrive(driveLetter, destinationFolder)
(VB.NET answers will be accepted too).
Thanks in advance.
public static void Copy(string src, string dest)
{
// copy all files
foreach (string file in Directory.GetFiles(src))
{
try
{
File.Copy(file, Path.Combine(dest, Path.GetFileName(file)));
}
catch (PathTooLongException)
{
}
// catch any other exception that you want.
// List of possible exceptions here: http://msdn.microsoft.com/en-us/library/c6cfw35a.aspx
}
// go recursive on directories
foreach (string dir in Directory.GetDirectories(src))
{
// First create directory...
// Instead of new DirectoryInfo(dir).Name, you can use any other way to get the dir name,
// but not Path.GetDirectoryName, since it returns full dir name.
string destSubDir = Path.Combine(dest, new DirectoryInfo(dir).Name);
Directory.CreateDirectory(destSubDir);
// and then go recursive
Copy(dir, destSubDir);
}
}
And then you can call it:
Copy(#"I:\", #"C:\Backup");
Didn't have time to test it, but i hope you get the idea...
edit: in the code above, there are no checks like Directory.Exists and such, you might add those if the directory structure of some kind exists at destination path. And if you're trying to create some kind of simple sync app, then it gets a bit harder, as you need to delete or take other action on files/folders that don't exist anymore.
This generally starts with a recursive descent parser. Here is a good example: http://msdn.microsoft.com/en-us/library/bb762914.aspx
You might want to look into the overloaded CopyDirectory Class
CopyDirectory(String, String, UIOption, UICancelOption)
It will recurse through all of the subdirectories.
If you want a standalone application, I have written an application that copies from one selected directory to another, overwriting newer files and adding subdirectories as needed.
Just email me.
Right now i'm attempting to take a screenshot and save it to the local machine, then later copy it to a network drive:
Q:\1234567890123456789012345\123456789012\12345\Screenshots\sc.jpg
^ for an idea about how many characters were talking. I have it setup to create a screenshots folder after that "12345" When the program gets to the point this error occurs:
How can I avoid this?
Also:
DirectoryInfo scdestinfo = new DirectoryInfo(scdest);
DirectoryInfo scinfo = new DirectoryInfo(sccpath);
CopyAll(scinfo, scdestinfo);
That's my code for copying the folders/files.
public void CopyAll(DirectoryInfo source, DirectoryInfo target)
{
copyall = false;
try
{
//check if the target directory exists
if (Directory.Exists(target.FullName) == false)
{
Directory.CreateDirectory(target.FullName);
}//end if
//copy all the files into the new directory
foreach (FileInfo fi in source.GetFiles())
{
fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
}//end foreach
//copy all the sub directories using recursion
foreach (DirectoryInfo diSourceDir in source.GetDirectories())
{
DirectoryInfo nextTargetDir = target.CreateSubdirectory(diSourceDir.Name);
CopyAll(diSourceDir, nextTargetDir);
}//end foreach
//success here
copyall = true;
}//end try
catch (IOException ie)
{
MessageBox.Show(ie.Message);
//handle it here
copyall = false;
}//end catch
}//end CopyAll
And the copyall function.
This is a Windows limitation, though there are Unicode variants of many API functions that allow for a maximum path length of approximately 32,767 characters, though that is again limited by the specific file system you are dealing with. Typically a given path component is limited to 255 characters (check GetVolumeInformation for the specific limitation for a given volume).
To use the Unicode variants (e.g. CreateFileW) that allow for longer paths, I believe you will have to deal with the Windows API directly rather than using the .Net library functions.
There is no straightforward way to avoid this, unfortunately.
Solution1: most natural one: generate a path in a way that it doesn't go over that limit.(shorter directory and file names) , or just reorganize directories layout, if this is possible.
Solution2: Don't like this one (wired, imo), but can use WindowsAPI, that doesn't fail in that case, like from the .NET 2.0 Workaround for PathTooLongException (it's for 2.0, but valid for nowdays)
Solution3. Try, when you're going to read/write data to a file, move OS cursor to that directory, so you will not need to specify complete (260+) to access it, but just file name. You should try this to see if it works for you. (even if this works I would prefer the first solution, but it worths to try)
Hope this helps.