My question is about handling temporary files in a small .NET program. This program/utility just does the following with two URLs:
Download each URL to a string (WebClient.DownloadString).
Save each string to a temporary file.
Call WinMerge (Process.Start) to diff the two files (in read-only mode for both files).
I currently have the Simplest Thing That Could Possibly Work:
save URL1 to windows/temp/leftFileToDiff.txt
save URL2 to windows/temp/rightFileToDiff.txt.
This works great - as WinMerge only needs to run in Read Only mode the files can be overwritten by running my program multiple times and nothing bad happens.
However, I would now like to change the temporary file names to something meaningful (related to the URL) so that I can see which is which in the WinMerge view. I also want to clean these files up when they are no longer needed. What are my options for this?
My next simplest idea is to have a specified folder where these are stored and just to zap this every time my program exits. Is there a better/more elegant/standard way?
Thanks.
Create a Guid-based folder under the user's temp area and use that?
string path = Path.Combine(Path.GetTempPath(),
Guid.NewGuid().ToString("n"));
Directory.CreateDirectory(path);
try
{
// work inside path
}
finally
{
try { Directory.Delete(path, true); }
catch (Exception ex) {Trace.WriteLine(ex);}
}
Related
This is similar with this question but with one more requirement:
Since the deletion of files can fail for whatever reasons. So I want the operation to be "transacted" which mean the whole operation either success in total, or fail and do not change anything at all.
In general this will be very very hard and I can't see any possibility to recover when the physical hard drive suddenly broken. So a weakened clause would be: if it success, we finished. Otherwise if fail, restore everything to the original state when possible.
Some kind of errors I could think of would be:
Access violation. You simply don't allowed to delete some files or folders. This is the case that I wanted to handle the most.
File/folder was used by somebody else and so it is "locked". In Linux this is not a problem but in Windows it is. This is also to be handled.
If it is a network folder there could be network issues. The recover can be hard or impossible. I would not expect this kind of error to be properly handled.
Hardware failure. I don't think any recover can happen here.
Scenario
You have a software that can export its internal data. The result is in a folder and with sub-folder names timestamped.
Now if the user specified a folder that is not empty (probably a previous output folder), the software will create new sub-folders on top of it, which is a mass. So you want to ensure the folder is empty before performing the export.
You can easily detect the folder emptiness and alert the user if not. But if the user say "go ahead and do it" you should do something then. Now, what if you were deleted some of the files and failed on others?
Going ahead in this case is just creating worse mass. At the same time the user would not expect a damaged folder without getting anything working. So it is better to either give them a fully working output or does not change the previous output at all.
As per comments, I'll give you the pseudocode for the process you can follow writing the code:
Clear contents of cache folder if any files exist (they shouldn't)
Copy contents of destination folder to cache folder
Try
While files exist, iterate
Delete file
End While
Catch
While files exist in cache, iterate
If file does not exist in destination folder
Move file from cache to destination
else
Delete file from cache
end If
End While
End Try
By following the guidelines given in the comments, I came up with this solution.
The following code will attempt to move everything to a temporary folder inside the given folder. If success, it returns True. If failed, the catch block will then try to move everything back and return a False. In either case, the finally block will remove the temporary folder recursively.
public static bool EmptyFolderTransactionaly(string folder)
{
var directoryInfo = new DirectoryInfo(folder);
var tmpDir = Directory.CreateDirectory(Path.Combine(folder, Path.GetFileName(Path.GetTempFileName())));
try
{
foreach (var e in directoryInfo.EnumerateFiles())
{
e.MoveTo(Path.Combine(tmpDir.FullName, e.Name));
}
foreach (var e in directoryInfo.EnumerateDirectories().Where(e => e.Name!=tmpDir.Name))
{
e.MoveTo(Path.Combine(tmpDir.FullName, e.Name));
}
return true;
}
catch
{
foreach (var e in tmpDir.EnumerateDirectories())
{
e.MoveTo(Path.Combine(directoryInfo.FullName, e.Name));
}
foreach (var e in tmpDir.EnumerateFiles())
{
e.MoveTo(Path.Combine(directoryInfo.FullName, e.Name));
}
return false;
}
finally
{
tmpDir.Delete(true);
}
}
Let me know if you see any risks in the code.
I have a function that checks every file in a directory and writes a list to the console. The problem is, I don't want it to include files that are currently being copied to the directory, I only want it to show the files that are complete. How do I do that? Here is my code:
foreach (string file in Directory.EnumerateFiles("C:\folder"))
{
Console.WriteLine(file);
}
There's really no way to tell "being copied" vs "locked for writing by something". Relevant: How to check for file lock? and Can I simply 'read' a file that is in use?
If you want to simply display a list of files that are not open for writing, you can do that by attempting to open them:
foreach (string file in Directory.EnumerateFiles("C:\folder"))
{
try {
using (var file = file.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) {
Console.WriteLine(file);
}
} catch {
// file is in use
continue;
}
}
However -- lots of caveats.
Immediately after displaying the filename (end of the using block) the file could be opened by something else
The process writing the file may have used FileShare.Read which means the call will succeed, despite it being written to.
I'm not sure what exactly you're up to here, but it sounds like two processes sharing a queue directory: one writing, one reading/processing. The biggest challenge is that writing a file takes time, and so your "reading" process ends up picking it up and trying to read it before the whole file is there, which will fail in some way depending on the sharing mode, how your apps are written, etc.
A common pattern to deal with this situation is to use an atomic file operation like Move:
Do the (slow) write/copy operation to a temporary directory that's on the same file system (very important) as the queue directory
Once complete, do a Move from the temporary directory to the queue directory.
Since move is atomic, the file will either not be there, or it will be 100% there -- there is no opportunity for the "reading" process to ever see the file while it's partially there.
Note that if you do the move across file systems, it will act the same as a copy.
There's no "current files being copied" list stored anywhere in Windows/.NET/whatever. Probably the most you could do is attempt to open each file for append and see if you get an exception. Depending on the size and location of your directory, and on the security setup, that may not be an option.
There isn't a clean way to do this, but this... works...
foreach (var file in new DirectoryInfo(#"C:\Folder").GetFiles())
{
try
{
file.OpenRead();
}
catch
{
continue;
}
Console.WriteLine(file.Name);
}
I created a zip file/folder with DotNetZip. I'm trying to move that file from the original directory/folder to another, e.g. My Documents. So far I have done the following, but it gives me an error saying that it could not find part of the path.
private static void Move()
{
try
{
Directory.Move(#"Debug\Settings.zip", IO.Paths.Enviroment.MyDocuments);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
UPDATE:
So I've played with it a bit and laughed not because I fixed it but because it's weird. I used both File.Move() and Directory.Move() and changed both.Move(#"Debug\Settings.zip",...); to both.Move(#"Settings.zip",...); and then get get an an error saying Cannot create a file when that file already exists.
While it may seem strange to use Directory.Move to move a file, (I'd use File.Move instead), Jean-Philippe Leclerc points out that it will work.
The problem is with the path Debug\Settings.zip:
All relative paths are relative to the working directory. By default the working directory is the folder in which the assembly (your program) is executed, and while debugging that is the bin\Debug subfolder of your project. So your path Debug\Settings.zip is expanded to a path like:
C:\..\MyProject\bin\Debug\Debug\Settings.zip
This is probably not what you meant. You meant just "Settings.zip".
The fact that it's a ZIP is irrelevant.
Use System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) to get your MyDocuments path.
could not find part of the path - The error seems like the Relative Path to your file Settings.Zip is not a valid path!
You need to use File.Move, Directory.Move will move the entire content of the Directory to different folder.
File.Move : Only moves the file to a specified location
private static void Move()
{
try
{
File.Move(#"Debug\Settings.zip", System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Fixed! The issues were first the "Debug\Settings.zip" should have been "Settings.zip" or #"Settings.zip" and finally destination should not just be System.IO.File.Move(#"Settings.zip", System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop)); but System.IO.File.Move(#"Settings.zip", System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop) + #"\Settings.zip"); Basically, add the file name and the extension of the file at the end of the destination string.
I am trying to write out a text file to: C:\Test folder\output\, but without putting C:\ in.
i.e.
This is what I have at the moment, which currently works, but has the C:\ in the beginning.
StreamWriter sw = new StreamWriter(#"C:\Test folder\output\test.txt");
I really want to write the file to the output folder, but with out having to have C:\ in the front.
I have tried the following, but my program just hangs (doesn't write the file out):
(#"\\Test folder\output\test.txt");
(#".\Test folder\output\test.txt");
("//Test folder//output//test.txt");
("./Test folder//output//test.txt");
Is there anyway I could do this?
Thanks.
Thanks for helping guys.
A colleague of mine chipped in and helped as well, but #Kami helped a lot too.
It is now working when I have:
string path = string.Concat(Environment.CurrentDirectory, #"\Output\test.txt");
As he said: "The CurrentDirectory is where the program is run from.
I understand that you would want to write data to a specified folder. The first method is to specify the folder in code or through configuration.
If you need to write to specific drive or current drive you can do the following
string driveLetter = Path.GetPathRoot(Environment.CurrentDirectory);
string path = diveLetter + #"Test folder\output\test.txt";
StreamWriter sw = new StreamWriter(path);
If the directory needs to be relative to the current application directory, then user AppDomain.CurrentDomain.BaseDirectory to get the current directory and use ../ combination to navigate to the required folder.
You can use System.IO.Path.GetDirectoryName to get the directory of your running application and then you can add to this the rest of the path..
I don't get clearly what you want from this question , hope this get it..
A common technique is to make the directory relative to your exe's runtime directory, e.g., a sub-directory, like this:
string exeRuntimeDirectory =
System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
string subDirectory =
System.IO.Path.Combine(exeRuntimeDirectory, "Output");
if (!System.IO.Directory.Exists(subDirectory))
{
// Output directory does not exist, so create it.
System.IO.Directory.CreateDirectory(subDirectory);
}
This means wherever the exe is installed to, it will create an "Output" sub-directory, which it can then write files to.
It also has the advantage of keeping the exe and its output files together in one location, and not scattered all over the place.
i have written some pdf files to a temp directory and these get displayed as a thumbnail that the user can view. when i close my form i clean up all of the files in the temp directory.
If however the user has one of the thumbnails open and then closes my application - it deletes the files and then throws an exception because the pdf is open in another process and cant be cleaned up.
I guess this is a shocking programming decision by me, but i am still a novice! How should i account for this in my code?
Thanks
You can detect if the file is in use by using code similar to below, then use that to warn the user that a file can't be deleted.
Unfortunately you can't delete a file that is in use.
public static bool IsFileInUse(string pathToFile)
{
if (!System.IO.File.Exists(pathToFile))
{
// File doesn't exist, so we know it's not in use.
return false;
}
bool inUse = false;
System.IO.FileStream fs;
try
{
fs = System.IO.File.Open(pathToFile, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Read, System.IO.FileShare.None);
fs.Close();
}
catch (System.IO.IOException ex)
{
string exMess = ex.Message;
inUse = true;
}
return inUse;
}
You should catch that exception (in catch block you can inform user to close that file or it will not be deleted), and if the temp directory is yours you can try to delete it when application starts (or when it ends again), if its windows temp directory, then it does not matter that much
Tools like File Unlocker can release a file. However I think this could make programs depending on the file crash...
Maybe you can look up how they unlock files or manage to execute the unlocker via Process.Start to unlock your file and delete it.
However if it's you blocking the file you should try and fix this in your programm. Maybe you should dispose all loaded files (filestreams etc) before trying to clean it up.