Hi I am new to C# and I am reviewing a code I didn't write. The code copies a compressed file from a network location to a local location before it is extracted and then parsed for data. The copy file function is:
public static void CopySourceFileToDestinationFile(string sourcePath, string destinationPath)
{
try
{
//copy log from source to local and uncompress it
File.Copy(sourcePath, destinationPath, true);
File.SetAttributes(destinationPath, FileAttributes.Normal);
}
catch (Exception ex)
{
var errmsg = $"ERROR in function 'CopySourceFileToDestinationFile()': {ex.Message.ToString()}";
Logger.Error(errmsg);
throw new InvalidOperationException(errmsg);
}
}
I see the File.SetAttributes function and looked up it and all it said on https://learn.microsoft.com/en-us/dotnet/api/system.io.file.setattributes?view=net-5.0 was telling me what the function is but not why you'd need it.
So I humbly ask, why do I ever need to use this function? Had I written this code from scratch I wouldn't have known the existence of this function, nor it's purpose
If you were reviewing that code, what I'd be more curious about is why the original developer put a lying comment above the copy action (it doesn't decompress) and why they catch an exception only to log and throw its message (why not the exception type and its stack trace)?
But about the attributes: a file, when written, will get an Archive flag, indicating to backup software that the file should be backed up. Clearing this bit by setting the file's attributes to Normal (i.e. no attributes) will ... probably not be very relevant, unless this file is copied into a directory that will actually be backed up.
It's not very relevant as any decent backup program won't just look at the Archive bit to decide whether to include a file in its backup or not. Clearing this attribute, as well as the rest of the code, reeks like code smell, cargo cult, voodoo programming and whatnot.
Related
I need to write a big file in my project.
What I learned:
I should NOT write the big file directly to the destination path,
because this may leave a incomplete file in case the app crash while writing it.
Instead, I should write to a temporary file and move (rename) it. (called atomic file operation)
My code snippet:
[NotNull]
public static async Task WriteAllTextAsync([NotNull] string path, [NotNull] string content)
{
string temporaryFilePath = null;
try {
temporaryFilePath = Path.GetTempFileName();
using (var stream = new StreamWriter(temporaryFilePath, true)) {
await stream.WriteAsync(content).ConfigureAwait(false);
}
File.Delete(path);
File.Move(temporaryFilePath, path);
}
finally {
if (temporaryFilePath != null) File.Delete(temporaryFilePath);
}
}
My Question:
The file will be missing if the app crashes between File.Delete and File.Move. Can I avoid this?
Is there any other best practice for writing big files?
Is there any suggestion on my code?
The file will be missing if the app crashes between File.Delete and File.Move. Can I avoid this?
Not that I'm aware of, but you can detect it - and if you use a more predictable filename, you can recover from that. It helps if you tweak the process somewhat to use three file names: the target, a "new" file and an "old" file. The process becomes:
Write to "new" file (e.g. foo.txt.new)
Rename the target file to the "old" file (e.g. foo.txt.old)
Rename the "new" file to the target file
Delete the "old" file
You then have three files, each of which may be present or absent. That can help you to detect the situation when you come to read the new file:
No files: Nothing's written data yet
Just target: All is well
Target and new: App crashed while writing new file
Target and old: App failed to delete old file
New and old: App failed after the first rename, but before the second
All three, or just old, or just new: Something very odd is going on! User may have interfered
Note: I was unaware of File.Replace before, but I suspect it's effectively just a simpler and possibly more efficient way of doing the code you're already doing. (That's great - use it!) The recovery process would still be the same though.
You can use File.Replace instead of deleting and moving files. In case of hard fault (electricity cut or something like this) you will always lost data, you have to count with that.
I try to use the following code in my project without any success and it's driving me mad.
System.IO.Compression.ZipFile.ExtractToDirectory(filePath, appPath);
The parameters are:
filePath = "/storage/emulated/0/Flashback_Backup/memory_backup.zip"
appPath = "/storage/emulated/0/Flashback"
According to the documentation here IOException should be thrown if:
The directory specified by destinationDirectoryName already exists.
-or- The name of an entry in the archive is Empty, contains only white space, or contains at least one invalid character.
-or- Extracting an archive entry would create a file that is outside the directory specified by destinationDirectoryName. (For example,
this might happen if the entry name contains parent directory
accessors.)
-or- An archive entry to extract has the same name as an entry that has already been extracted from the same archive.
As far as I know none of it applies. The zip file is a totally valid one, which I compressed with the Directory.CreateDirectory method, and only contains a few uniquely named JSON files. I tried with and without existing "Flashback" folder too, but nothing seems to work.
If anyone have any ideas or solutions please tell me because I'm seriously lost at this. I can provide more info if needed.
Try to wrap extraction intro try-catch, it may give you a better understanding of what is going on.
try {
System.IO.Compression.ZipFile.ExtractToDirectory(filePath, appPath);
} catch (Exception ex) {
Console.Log(ex);
}
If there is an error, it will be one of your listed above.
I am working on a project that I need to do the following:
I need to rename an image file. (Open an image from folder, and give a name & save it in to same folder)
try
{
string oldFileName = #"path\to\person1.jpg";
string desFileName = #"path\to\person2.jpg";
File.Copy(oldFileName, desFileName, true);
if (File.Exists(oldFileName))
{
File.Delete(#oldFileName);
}
}
catch (Exception ex)
{
}
I did rename the file using above way.
This process copy the old file with new name, but couldn't remove old file
Exception message :
The process cannot access the file 'path\to\person1.jpg' because it is
being used by another process.
How to resolve this? Please suggest any way to detect copying process has complete or not.
Your copy process is definatly complete on if statement becouse your code is sync.
I bet you got this error becouse file is used by another proccess (not your programm). Maby you have paint open or something else.
You should find it out with process monitor or something else. Check this question.
File.Copy allows simple file copying. When a duplicate file name is encountered, File.Copy has a third parameter to determine if the original file is overwritten or not.
Is there a built-in .Net function that allows a third option to rename the copied file and hence keep both files?. For example, the file copy would automatically rename "readme.txt" to "readme - Copy.txt" if another readme.txt already existed in the destination folder - similar to Windows Explorer functionality?
I realize it can be written but didn't want reinvent the wheel if it exists.
Thanks in advance.
Nope, this functionality doesn't exist out of the box (thankfully, as it would introduce a responsibility to the framework which it ought not to have*,) so if you want this, then you will need to implement a bespoke solution.
*Which implementation should it take? Appending "- Copy", appending "(n)"? It becomes problematic rather sherpish.
There's nothing you can do all-in-one go with File.Copy, however you could test if the destination exists and then Move instead.
Move takes two parameters, and you can essentially rename at the same time.
if File.Exists(destinationPath) {
File.Move(source, destinationPathRenamed);
} else {
try {
File.Copy(source, destinationPath);
} catch (IOException ex) {
// destinationPath file already exists
File.Move(source, destinationPathRenamed);
}
}
See Move documentation
EDIT:
Updated code above. #xanatos makes a good point about atomic operation. I made no assumptions about whether there are other processes accessing the file(s).
Note that I haven't added other error handling for the source file being deleted before the operation begins either.
var destinationPath = c:\temp\archive.txt;
if(File.Exists(destinationPath))
destinationPath = string.Format("c:\temp\archive.{0}.txt", DateTime.Now.ToString("yyyyMMddHHmmffff"));
File.Move(filePath, destinationPath );
I am trying to delete/open/edit some files in my C# .Net application.Sometimes i get exception stating the file/directory is being accessed by another process.Is there a way to check if a file/directory is being accessed by process and try to release the file from that process?
No. The only way to do this is to try to access the file, and handle the IOException.
Realistically this is the only safe way anyway. Suppose there was a IsFileInUse() method, and you called it, and it returned "nope, nobody's using that file," and you went ahead and accessed the file. The problem is that in the meantime some other process might have locked or deleted the file. So you'd need to put exception handling around your attempt to access the file anyway. The "test by acquiring" model is the only one that is 100% reliable.
If a file is in use by another process, .NET doesn't provide a way of determining which other process that might be. I believe this would require some pretty low-level unmanaged code though I could be wrong. It is a very low-level operation, if it is possible at all, to "release the file from that process" because that would violate the other process' expectations -- e.g. it thinks it is allowed to write to the file but you have deleted the file and garbaged the handle. I believe you would need to terminate the other process if it's not willing to give up its lock voluntarily.
First, I suppose there are 2 things that may help you:
consider using FileAccess and FileShare flags when opening files
if data from the file is needed only withing the scope of the function use the construction
using(FileStream stream = File.Open(...)) { <file operations> }
this will ensure that file is closed immediately after exiting 'using' block, and not when FileStream object is collected by GC.
Second, there is an unsafe way to get processes that use the file. It is based on debugging features provided by windows. The main idea is to get all system handles and iterate through them to find which are the files handle and additional information. This is done using functions that I'm not sure are documented. If you are interested use google to find more information, but I do not think it is not a good way.
public bool IsInUse(string path)
{
bool IsFree = true;
try
{
//Just opening the file as open/create
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
{
//we can check by using
fs.CanRead // or
fs.CanWrite
}
}
catch (IOException ex)
{
IsFree = false;
}
return IsFree;
}
string path = "D:\\test.doc";
bool IsFileFree = IsInUse(path);