I am trying to write a program to keep multiple folders in sync. To do this, I need to copy and delete files and subfolders.
To me, it doesn't make a difference if an object is a file or a folder, I want to create all necessary parent folders and copy the object, overwriting if necessary. I'm currently using a jagged array of FileSystemInfo to hold my files/folders.
This has the advantage of avoiding a duplication of code to sync files and folders separately.
However, I can't figure out how to Copy a FileSystemInfo. I'm looking for a way to be able to copy/delete/read creation or modified time that will work on both files and folders.
FileSystemInfo don't have Copy or Delete methods but is the base class for DirectoryInfo and FileInfo.
So when you loop over your FileSystemInfo objects you have to cast to the proper concrete class and use the specific copy/delete methods.
foreach( var fsi in fileSystemInfoObjects )
{
if( fsi is DirectoryInfo )
{
var directory = (DirectoryInfo)fsi;
//do something
}
else if (fsi is FileInfo )
{
var file = (FileInfo)fsi;
//do something
}
}
I used sam's answer to help me solve my problem. What I did was put the copying logic in my custom class so that I don't need to duplicate the logic whenever I use it in my code.
public class myFSInfo
{
public FileSystemInfo Dir;
public string RelativePath;
public string BaseDirectory;
public myFSInfo(FileSystemInfo dir, string basedir)
{
Dir = dir;
BaseDirectory = basedir;
RelativePath = Dir.FullName.Substring(basedir.Length + (basedir.Last() == '\\' ? 1 : 2));
}
private myFSInfo() { }
/// <summary>
/// Copies a FileInfo or DirectoryInfo object to the specified path, creating folders and overwriting if necessary.
/// </summary>
/// <param name="path"></param>
public void CopyTo(string path)
{
if (Dir is FileInfo)
{
var f = (FileInfo)Dir;
Directory.CreateDirectory(path.Substring(0,path.LastIndexOf("\\")));
f.CopyTo(path,true);
}
else if (Dir is DirectoryInfo) Directory.CreateDirectory(path);
}
}
Related
Is it possible to have Resources.Load(name, type) search for a fitting asset not just in the base Resources folder / a specified subfolder, but instead the full subfolder structure under Resources?
Example folder structure of
Resources
- Subfolder
- image.png
I would like something like Resources.Load("image", typeof(Texture2D)) to return the image without the user having to specify "Subfolder/image".
I know it's ugly, but it's supposed to be a "drop it in your bashed together project without worrying about your folder structure"-type utility script and I won't know the subfolder.
The accepted answer only works in the editor context, and doesn't work after building the game. The resources folder is packed when building your game, and will no longer be a file hierarchy you can loop through with Directory.GetDirectories. The only way to get this to work is to save all file paths while still in the editor context, and use this file hierarchy to load the assets at runtime. In my project I used the following code to add a button to the component that uses the assets in the resources folder, named CharacterGen. When this button is clicked, all png files in all subfolders in the Resources folder are saved to the public attribute named filePaths that CharacterGen has.
[CustomEditor(typeof(CharacterGen))]
public class RefreshCharacterList : Editor
{
CharacterGen charGen;
public void OnEnable()
{
charGen = (CharacterGen)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Load resource paths"))
{
List<String> paths = new List<string>();
LoadPathsRecursive("", ref paths);
charGen.filePaths = paths;
EditorUtility.SetDirty(charGen); //original post didn't have this line, but this is needed to make sure your changes are saved.
}
}
void LoadPathsRecursive(string path, ref List<string> paths)
{
var fullPath = Application.dataPath + "/Resources/" + path;
Debug.Log("fullPath: " + fullPath);
DirectoryInfo dirInfo = new DirectoryInfo(fullPath);
foreach(var file in dirInfo.GetFiles())
{
if (file.Name.Contains(".PNG") && !file.Name.Contains(".meta"))
{
paths.Add(path + "/" + file.Name.Replace(".PNG", ""));
}
}
foreach (var dir in dirInfo.GetDirectories())
{
LoadPathsRecursive(path + "/" + dir.Name, ref paths);
}
}
}
In CharacterGen I later evoke Resources.Load, using the paths that were saved when clicking the button.
foreach (var filePath in filePaths)
{
var loadedSprite = Resources.Load<Sprite>(filePath);
//Do something with loadedSprite
}
Edit: I failed to mention an important detail in my original post. the filePaths is a Monobehaviour field, and I marked it with [SerializeField] (alternatively it could be marked as public) so that unity actually serializes the values of this field and includes it in the build.
There is no way to change the Resouces.Load() static method functionality, it's Unity Internal. However, you can write your own custom class that does your desired functionality. The code needs to find all the directories inside the Resources folder and search for the file. Let's call the class ResourcesExtension
public class ResourcesExtension
{
public static string ResourcesPath = Application.dataPath+"/Resources";
public static UnityEngine.Object Load(string resourceName, System.Type systemTypeInstance)
{
string[] directories = Directory.GetDirectories(ResourcesPath,"*",SearchOption.AllDirectories);
foreach (var item in directories)
{
string itemPath = item.Substring(ResourcesPath.Length+1);
UnityEngine.Object result = Resources.Load(itemPath+"\\"+resourceName,systemTypeInstance);
if(result!=null)
return result;
}
return null;
}
}
Then all you need to do is calling the static method.
ResourcesExtension.Load("image", typeof(Texture2D))
if you want to find all Object of a type and return as a List.
it Emad code Edited a bit.
private List<T> FindAllObject<T>()
{
List<T> tmp = new List<T>();
string ResourcesPath = Application.dataPath + "/Resources";
string[] directories = Directory.GetDirectories(ResourcesPath, "*", SearchOption.AllDirectories);
foreach (string item in directories)
{
string itemPath = item.Substring(ResourcesPath.Length + 1);
T[] reasult = Resources.LoadAll(itemPath, typeof(T)).Cast<T>().ToArray();
foreach (T x in reasult)
{
if (!tmp.Contains(x))
{
tmp.Add(x);
}
}
}
return tmp;
}
To use it.
List<MyClass> myClasses = FindAllObject<MyClass>();
Imagine I wish to create (or overwrite) the following file :- C:\Temp\Bar\Foo\Test.txt
Using the File.Create(..) method, this can do it.
BUT, if I don't have either one of the following folders (from that example path, above)
Temp
Bar
Foo
then I get an DirectoryNotFoundException thrown.
So .. given a path, how can we recursively create all the folders necessary to create the file .. for that path? If Temp or Bar folders exists, but Foo doesn't... then that is created also.
For simplicity, lets assume there's no Security concerns -- all permissions are fine, etc.
To summarize what has been commented in other answers:
//path = #"C:\Temp\Bar\Foo\Test.txt";
Directory.CreateDirectory(Path.GetDirectoryName(path));
Directory.CreateDirectory will create the directories recursively and if the directory already exist it will return without an error.
If there happened to be a file Foo at C:\Temp\Bar\Foo an exception will be thrown.
DirectoryInfo di = Directory.CreateDirectory(path);
Console.WriteLine("The directory was created successfully at {0}.",
Directory.GetCreationTime(path));
See this MSDN page.
Use Directory.CreateDirectory before you create the file. It creates the folder recursively for you.
. given a path, how can we recursively create all the folders necessary to create the file .. for that path
Creates all directories and subdirectories as specified by path.
Directory.CreateDirectory(path);
then you may create a file.
You will need to check both parts of the path (directory and filename) and create each if it does not exist.
Use File.Exists and Directory.Exists to find out whether they exist. Directory.CreateDirectory will create the whole path for you, so you only ever need to call that once if the directory does not exist, then simply create the file.
You should use Directory.CreateDirectory.
http://msdn.microsoft.com/en-us/library/54a0at6s.aspx
Assuming that your assembly/exe has FileIO permission is itself, well is not right. Your application may not run with admin rights. Its important to consider Code Access Security and requesting permissions
Sample code:
FileIOPermission f2 = new FileIOPermission(FileIOPermissionAccess.Read, "C:\\test_r");
f2.AddPathList(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, "C:\\example\\out.txt");
try
{
f2.Demand();
}
catch (SecurityException s)
{
Console.WriteLine(s.Message);
}
Understanding .NET Code Access Security
Is “Code Access Security” of any real world use?
You want Directory.CreateDirectory()
Here is a class I use (converted to C#) that if you pass it a source directory and a destination it will copy all of the files and sub-folders of that directory to your destination:
using System.IO;
public class copyTemplateFiles
{
public static bool Copy(string Source, string destination)
{
try {
string[] Files = null;
if (destination[destination.Length - 1] != Path.DirectorySeparatorChar) {
destination += Path.DirectorySeparatorChar;
}
if (!Directory.Exists(destination)) {
Directory.CreateDirectory(destination);
}
Files = Directory.GetFileSystemEntries(Source);
foreach (string Element in Files) {
// Sub directories
if (Directory.Exists(Element)) {
copyDirectory(Element, destination + Path.GetFileName(Element));
} else {
// Files in directory
File.Copy(Element, destination + Path.GetFileName(Element), true);
}
}
} catch (Exception ex) {
return false;
}
return true;
}
private static void copyDirectory(string Source, string destination)
{
string[] Files = null;
if (destination[destination.Length - 1] != Path.DirectorySeparatorChar) {
destination += Path.DirectorySeparatorChar;
}
if (!Directory.Exists(destination)) {
Directory.CreateDirectory(destination);
}
Files = Directory.GetFileSystemEntries(Source);
foreach (string Element in Files) {
// Sub directories
if (Directory.Exists(Element)) {
copyDirectory(Element, destination + Path.GetFileName(Element));
} else {
// Files in directory
File.Copy(Element, destination + Path.GetFileName(Element), true);
}
}
}
}
Following code will create directories (if not exists) & then copy files.
// using System.IO;
// for ex. if you want to copy files from D:\A\ to D:\B\
foreach (var f in Directory.GetFiles(#"D:\A\", "*.*", SearchOption.AllDirectories))
{
var fi = new FileInfo(f);
var di = new DirectoryInfo(fi.DirectoryName);
// you can filter files here
if (fi.Name.Contains("FILTER")
{
if (!Directory.Exists(di.FullName.Replace("A", "B"))
{
Directory.CreateDirectory(di.FullName.Replace("A", "B"));
File.Copy(fi.FullName, fi.FullName.Replace("A", "B"));
}
}
}
Here's the problem, I have a bunch of directories like
S:\HELLO\HI
S:\HELLO2\HI\HElloAgain
On the file system it shows these directories as
S:\hello\Hi
S:\hello2\Hi\helloAgain
Is there any function in C# that will give me what the file system name of a directory is with the proper casing?
string FileSystemCasing = new System.IO.DirectoryInfo("H:\...").FullName;
EDIT:
As iceman pointed out, the FullName returns the correct casing only if the DirectoryInfo (or in general the FileSystemInfo) comes from a call to the GetDirectories (or GetFileSystemInfos) method.
Now I'm posting a tested and performance-optimized solution. It work well both on directory and file paths, and has some fault tolerance on input string.
It's optimized for "conversion" of single paths (not the entire file system), and faster than getting the entire file system tree.
Of course, if you have to renormalize the entire file system tree, you may prefer iceman's solution, but i tested on 10000 iterations on paths with medium level of deepness, and it takes just few seconds ;)
private string GetFileSystemCasing(string path)
{
if (Path.IsPathRooted(path))
{
path = path.TrimEnd(Path.DirectorySeparatorChar); // if you type c:\foo\ instead of c:\foo
try
{
string name = Path.GetFileName(path);
if (name == "") return path.ToUpper() + Path.DirectorySeparatorChar; // root reached
string parent = Path.GetDirectoryName(path); // retrieving parent of element to be corrected
parent = GetFileSystemCasing(parent); //to get correct casing on the entire string, and not only on the last element
DirectoryInfo diParent = new DirectoryInfo(parent);
FileSystemInfo[] fsiChildren = diParent.GetFileSystemInfos(name);
FileSystemInfo fsiChild = fsiChildren.First();
return fsiChild.FullName; // coming from GetFileSystemImfos() this has the correct case
}
catch (Exception ex) { Trace.TraceError(ex.Message); throw new ArgumentException("Invalid path"); }
return "";
}
else throw new ArgumentException("Absolute path needed, not relative");
}
Here's a basic and relatively fast solution, keep reading below for some commentary:
private static string GetCase(string path)
{
DirectoryInfo dir = new DirectoryInfo(path);
if (dir.Exists)
{
string[] folders = dir.FullName.Split(Path.DirectorySeparatorChar);
dir = dir.Root;
foreach (var f in folders.Skip(1))
{
dir = dir.GetDirectories(f).First();
}
return dir.FullName;
}
else
{
return path;
}
}
The basic idea is that getting subdirectories from a DirectoryInfo object will get you the correct case, so we just need to split the directory name and walk from the root to the target directory, getting the proper case at each step.
My initial answer relied on getting the casing for every folder on the drive, and it worked but was slow. I came up with a slight improvement that stored the results, but it was still too slow for everyday usage. You can see the edit history for this comment if you need to do this for every thing on the drive, and even then there are probably ways to speed up that code. It was "here's how you might do it" and not "here's a great way to do it."
Bertu, in his answer, came up with the idea of splitting the path into its components and getting the casing piece by piece, which results in a huge speed increase since you're no longer checking everything as in my original answer. Bertu also generalized his solution to do files as well as directories. In my tests, the code posted above (which uses Bertu's "split the path and do it by pieces" idea but approaches it iteratively instead of recursively) runs in about half the time of Bertu's code. I'm not sure if that's because his method also handles files, because his use of recursion introduces extra overhead, or because he ends up calling Path.GetFileName(path) and Path.GetDirectoryName(path) in each iteration. Depending on your exact needs, some combination of his answer and mine will likely solve your problem as well as is possible in C#.
On that note, I should mention that there are some limitations to .Net filename handling, and since doing this in .Net requires making a lot of DirectoryInfo objects, you might want to consider unmanaged code if this is your bottleneck.
My version (similar to BertuPG's and Kevin's using GetDirectories() and GetFileSystemInfos() but allowing for non-existent paths and files and working as an extension method):
/// <summary>
/// Gets the full path with all the capitalization properly done as it exists in the file system.
/// Non-existent paths returned as passed in.
/// </summary>
/// <param name="path">
/// The path to make full and make properly cased.
/// </param>
/// <returns>
/// The complete and capitalization-accurate path based on the
/// given <paramref name="path"/>.
/// </returns>
/// <remarks>
/// Walks the path using <see cref="DirectoryInfo"/>.<see cref="DirectoryInfo.GetDirectories()"/>
/// and <see cref="DirectoryInfo.GetFileSystemInfos()"/> so don't expect awesome performance.
/// </remarks>
public static string GetFileSystemFullPath(this string path)
{
if (path == null)
{
return path;
}
string[] pathParts = Path.GetFullPath(path).Split(Path.DirectorySeparatorChar);
if (pathParts.Any())
{
var dir = new DirectoryInfo(pathParts[0]).Root;
for (int i = 1; i < pathParts.Length; ++i)
{
var next = dir.GetDirectories(pathParts[i]).FirstOrDefault();
if (next == null)
{
if (i == pathParts.Length - 1)
{
var fileInfo = dir.GetFileSystemInfos(pathParts[i]).FirstOrDefault();
if (fileInfo != null)
{
return fileInfo.FullName;
}
}
var ret = dir.FullName;
do
{
ret = Path.Combine(ret, pathParts[i++]);
} while (i < pathParts.Length);
return ret;
}
dir = next;
}
return dir.FullName;
}
// cannot do anything with it.
return path;
}
static void Main( string[] args )
{
string[] paths = new string[] { "S:\hello\Hi", "S:\hello2\Hi\helloAgain" };
foreach( string aPath in paths )
{
string normalizedPath = NormalizePath( aPath );
Console.WriteLine( "Previous: '{0}', Normalized: '{1}'", aPath, normalizedPath );
}
Console.Write( "\n\n\nPress any key..." );
Console.Read();
}
public static string NormalizePath( string path )
{
StringBuilder sb = new StringBuilder( path );
string[] paths = path.Split('\\');
foreach( string folderName in paths )
{
string normalizedFolderName = ToProperCase( folderName );
sb.Replace( folderName, normalizedFolderName );
}
return sb.ToString();
}
/// <summary>
/// Converts a string to first character upper and rest lower (Camel Case).
/// </summary>
/// <param name="stringValue"></param>
/// <returns></returns>
public static string ToProperCase( string stringValue )
{
if( string.IsNullOrEmpty( stringValue ) )
return stringValue;
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase( stringValue.ToLower() );
}
I am just learning C# (have been fiddling with it for about 2 days now) and I've decided that, for leaning purposes, I will rebuild an old app I made in VB6 for syncing files (generally across a network).
When I wrote the code in VB 6, it worked approximately like this:
Create a Scripting.FileSystemObject
Create directory objects for the source and destination
Create file listing objects for the source and destination
Iterate through the source object, and check to see if it exists in the destination
if not, create it
if so, check to see if the source version is newer/larger, and if so, overwrite the other
So far, this is what I have:
private bool syncFiles(string sourcePath, string destPath) {
DirectoryInfo source = new DirectoryInfo(sourcePath);
DirectoryInfo dest = new DirectoryInfo(destPath);
if (!source.Exists) {
LogLine("Source Folder Not Found!");
return false;
}
if (!dest.Exists) {
LogLine("Destination Folder Not Found!");
return false;
}
FileInfo[] sourceFiles = source.GetFiles();
FileInfo[] destFiles = dest.GetFiles();
foreach (FileInfo file in sourceFiles) {
// check exists on file
}
if (optRecursive.Checked) {
foreach (DirectoryInfo subDir in source.GetDirectories()) {
// create-if-not-exists destination subdirectory
syncFiles(sourcePath + subDir.Name, destPath + subDir.Name);
}
}
return true;
}
I have read examples that seem to advocate using the FileInfo or DirectoryInfo objects to do checks with the "Exists" property, but I am specifically looking for a way to search an existing collection/list of files, and not live checks to the file system for each file, since I will be doing so across the network and constantly going back to a multi-thousand-file directory is slow slow slow.
Thanks in Advance.
The GetFiles() method will only get you files that does exist. It doesn't make up random files that doesn't exist. So all you have to do is to check if it exists in the other list.
Something in the lines of this could work:
var sourceFiles = source.GetFiles();
var destFiles = dest.GetFiles();
foreach (var file in sourceFiles)
{
if(!destFiles.Any(x => x.Name == file.Name))
{
// Do whatever
}
}
Note: You have of course no guarantee that something hasn't changed after you have done the calls to GetFiles(). For example, a file could have been deleted or renamed if you try to copy it later.
Could perhaps be done nicer somehow by using the Except method or something similar. For example something like this:
var sourceFiles = source.GetFiles();
var destFiles = dest.GetFiles();
var sourceFilesMissingInDestination = sourceFiles.Except(destFiles, new FileNameComparer());
foreach (var file in sourceFilesMissingInDestination)
{
// Do whatever
}
Where the FileNameComparer is implemented like so:
public class FileNameComparer : IEqualityComparer<FileInfo>
{
public bool Equals(FileInfo x, FileInfo y)
{
return Equals(x.Name, y.Name);
}
public int GetHashCode(FileInfo obj)
{
return obj.Name.GetHashCode();
}
}
Untested though :p
One little detail, instead of
sourcePath + subDir.Name
I would use
System.IO.Path.Combine(sourcePath, subDir.Name)
Path does reliable, OS independent operations on file- and foldernames.
Also I notice optRecursive.Checked popping out of nowhere. As a matter of good design, make that a parameter:
bool syncFiles(string sourcePath, string destPath, bool checkRecursive)
And since you mention it may be used for large numbers of files, keep an eye out for .NET 4, it has an IEnumerable replacement for GetFiles() that will let you process this in a streaming fashion.
I need a simple function which will take a FileInfo and a destination_directory_name as input, get the file path from the fileinfo and replicate it in the destination_directory_name passed as the second parameter.
for ex. filepath is "d:\recordings\location1\client1\job1\file1.ext
the function should create the directories in the destination_directory_name if they dont exist and copy the file after creating the directories.
System.IO.Directory.CreateDirectory can be used to create the final directory, it will also automatically create all folders in the path if they do not exist.
//Will create all three directories (if they do not already exist).
System.IO.Directory.CreateDirectory("C:\\First\\Second\\Third")
I'm using the following method for that purpose:
public static void CreateDirectory(DirectoryInfo directory)
{
if (!directory.Parent.Exists)
CreateDirectory(directory.Parent);
directory.Create();
}
Use it in this way:
// path is your file path
string directory = Path.GetDirectoryName(path);
CreateDirectory(new DirectoryInfo(directory));
Based on #NTDLS's answer here's a method that will replicate source to destination. It handles case where source is a file or a folder. Function name kind of stinks... lemme know if you think of a better one.
/// <summary>
/// Copies the source to the dest. Creating any neccessary folders in the destination path as neccessary.
///
/// For example:
/// Directory Example:
/// pSource = C:\somedir\conf and pDest=C:\somedir\backups\USER_TIMESTAMP\somedir\conf
/// all files\folders under source will be replicated to destination and any paths in between will be created.
/// </summary>
/// <param name="pSource">path to file or directory that you want to copy from</param>
/// <param name="pDest">path to file or directory that you want to copy to</param>
/// <param name="pOverwriteDest">if true, files/directories in pDest will be overwritten.</param>
public static void FileCopyWithReplicate(string pSource, string pDest, bool pOverwriteDest)
{
string destDirectory = Path.GetDirectoryName(pDest);
System.IO.Directory.CreateDirectory(destDirectory);
if (Directory.Exists(pSource))
{
DirectoryCopy(pSource, pDest,pOverwriteDest);
}
else
{
File.Copy(pSource, pDest, pOverwriteDest);
}
}
// From MSDN Aricle "How to: Copy Directories"
// Link: http://msdn.microsoft.com/en-us/library/bb762914.aspx
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, true);
}
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
}
Similar to the question, I am copying a folder structure from one destination and duplicating it to another. Sorry for posting to an old thread, but it may be useful for someone that ends up here.
Let's assume that we have an application that is standalone, and we need to copy the folder structure from an Input folder to an Output folder. The Input folder and Output folder is in the root directory of our application.
We can make a DirectoryInfo for the Input folder (structure we want to copy) like this:
DirectoryInfo dirInput = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory + "\\Input\\");
Our output folder location can be stored in a string.
string dirOutput = AppDomain.CurrentDomain.BaseDirectory + "\\Output\\";
This recursive method should handle the rest for us.
public static void ProcessDirectories(DirectoryInfo dirInput, string dirOutput)
{
string dirOutputfix = String.Empty;
foreach (DirectoryInfo di in dirInput.GetDirectories())
{
dirOutputfix = dirOutput + "\\" + di.Name);
if (!Directory.Exists(dirOutputfix))
{
try
{
Directory.CreateDirectory(dirOutputfix);
}
catch(Exception e)
{
throw (e);
}
}
ProcessDirectories(di, dirOutputfix);
}
}
This method can be easily modified to handle files also, but I designed it with only folders in mind for my project.
Now we just call the method with our dirInput and dirOutput.
ProcessDirectories(dirInput, dirOutput);