C# how to lock the file - c#

We are using an application that reads the file and inserts into database. For some reason, we set the creation time to a particular time. and when we create the file the application picks up the file before we even change the creation time. Is there anyway that we can lock the file until we change its creation time.
The error i am getting is at File.SetCreationTime as the file is being used by other process or the file is archived.
//Copy signal file
s = filePath + "s";
newS = targetFullPath2 + "s";
File.Copy(s, newS, true);
//
//new creation ts
dtFileCreation = File.GetCreationTime(s);
//retain creation time
File.SetCreationTime(newS, dtFileCreation);
Please advice.

The common solution to this is to create the file (and set its timestamp) in a different directory or under a different name first, and then move or rename it when it’s ready so the other process can pick it up. The move/rename is an atomic operation on NTFS (unless of course you move files between separate partitions).
For example:
s = filePath + "s";
newS = targetFullPath2 + "s";
File.Copy(s, newS + "_", true); // add the _ so the other process
// doesn’t “see” the file yet
dtFileCreation = File.GetCreationTime(s);
File.SetCreationTime(newS + "_", dtFileCreation);
// We’re done with the file, now rename it to its intended final name
File.Move(newS + "_", newS);

That error indicates someone (it could be you or another program) is locking the file; therefore, I do not think locking the file will solve your problem. What kind of file is it? Is your code reading the file and forgetting to close the stream after it is done?

Related

Files disappear after they fail to be moved

We have a process where people scan documents with photocopiers and drop them in a certain directory on our file server. We then have a hourly service within an .NET Core app, that scans the directory, grabs the file and moves them according to their file name to a certain directory. Here comes the problems.
The code looks something like that:
private string MoveFile(string file, string commNumber)
{
var fileName = Path.GetFileName(file);
var baseFileName = Path.GetFileNameWithoutExtension(fileName).Split("-v")[0];
// 1. Check if the file already exists at destination
var existingFileList = luxWebSamContext.Documents.Where(x => EF.Functions.Like(x.DocumentName, "%" + Path.GetFileNameWithoutExtension(baseFileName) + "%")).ToList();
// If the file exists, check for the current version of file
if (existingFileList.Count > 0)
{
var nextVersion = existingFileList.Max(x => x.UploadVersion) + 1;
var extension = Path.GetExtension(fileName);
fileName = baseFileName + "-v" + nextVersion.ToString() + extension;
}
var from = #file;
var to = Path.Combine(#destinationPath, commNumber,fileName);
try
{
log.Info($"------ Moving File! ------ {fileName}");
Directory.CreateDirectory(Path.Combine(#destinationPath, commNumber));
File.Move(from, to, true);
return to;
}
catch (Exception ex)
{
log.Error($"----- Couldn't MOVE FILE: {file} ----- commission number: {commNumber}", ex);
The interesting part is in the try-block, where the file move takes place. Sometmes we have the problem that the program throws the following exception
2021-11-23 17:15:37,960 [60] ERROR App ----- Couldn't MOVE FILE:
\PATH\PATH\PATH\Filename_423489120.pdf ----- commission number:
05847894
System.IO.IOException: The process cannot access the file because it is being used by another process.
at System.IO.FileSystem.MoveFile(String sourceFullPath, String destFullPath, Boolean overwrite)
at System.IO.File.Move(String sourceFileName, String destFileName, Boolean overwrite)
So far so good. I would expect that after the file cannot be moved, it remains in the directory from it was supposed to be moved. But that's not the case. We had this issue yesterday afternoon and after I looked for the file, it was gone from the directory.
Is this the normal behaviour of the File.Move() method?
First to your question:
Is this the normal behaviour of the File.Move() method?
No, thats not the expected behaviour. The documentation says:
Moving the file across disk volumes is equivalent to copying the file
and deleting it from the source if the copying was successful.
If you try to move a file across disk volumes and that file is in use,
the file is copied to the destination, but it is not deleted from the
source.
Your Exception says, that another process is using the file in the same moment. So you should check, whether other parts of your application may performs a Delete, or someone (if this scenario is valid) is deleting files manually from the file system.
Typically, File.Move() only removes the source file, once the destination file is successfully transferred in place. So the answer to your question is no, it cannot be purely the File.Move(). The interesting part is, why is this file locked? Probaby because some file stream is still open and blocking access to the file. Also, do you have multiple instances of the copy process services running? This may cause several services trying to access the file simultaneously, causing the exception you posted.
There must be a different cause making the files disappear because the File.Move() will certainly not remove the file when the copy process did not succeed.
For debugging purposes, you may try and open the file with a lock on it. This will fail when a different process locks the file providing you a little bit more information.

How to read and write csv file when it is opened with Excel by the user in parallel with C#?

This is the first question I asked on stackoverflow, very exciting. So sorry about my grammar and other type mistakes, I would appreciate if you correct them. I want to write a program that reads a csv file if it exists in the specific folder first, stores it in List variable, add some new lines and writes it to the same file. This process will be repeated continuously in a while block.
While the file is being read and written by the program, if it is opened with Notepad, it doesn't give an error and the program could access the file in parallel. But if it is opened with Office Excel, program gives error that says "file access is denied because it is used by another process..". I want to ask you that:
1) Is it possible to give priority to the program, so program can still access the file but user cannot? Or is it possible both program and user can access the file?
2) If the solution is opening the file with Notepad, is there a way to set default program for this file as Notepad? Or how can I change default program for csv files as Notepad from C#?
Reading and writing parts of the code is as bellows:
List<string> csvLines = new List<string>();
string address = folderpath + #"\PLC_LOGLARI";
if (!Directory.Exists(address))
Directory.CreateDirectory(address);
string fileAddress = address + #"\" + fileName + "_" + machineNo + "_1.csv";
if (Directory.EnumerateFiles(address).Any(f => f.Contains(fileName + "_" + machineNo)))
{
string[] addressArray = Directory.GetFiles(address, fileName + "_" + machineNo + "*.*");
FileInfo fileInformation = new FileInfo(addressArray[addressArray.Length - 1]);
long fileSize = fileInformation.Length;
if (fileSize > 5000000)
{
int fileNo = int.Parse(addressArray[addressArray.Length - 1].Substring(addressArray[addressArray.Length - 1].LastIndexOf('_') + 1, 1)) + 1;
fileAddress = addressArray[addressArray.Length - 1].Substring(0, addressArray[addressArray.Length - 1].LastIndexOf('_') + 1) + fileNo.ToString() + ".csv";
}
else
{
fileAddress = addressArray[addressArray.Length - 1];
csvLines = File.ReadAllLines(address).ToList();
}
}
else
{
string[] headings = makeHeadings();
csvLines.Add(headings[0]);
csvLines.Add(headings[1]);
}
string newLine = "";
// some operations and calculations for newLine
// some operations and calculations for newLine
// some operations and calculations for newLine
newLine = newLine.Substring(1, newLine.Length-1);
csvLines.Add(newLine);
File.WriteAllLines(fileAddress, csvLines.ToArray());
Thanks for your help.
For question 2: In order to change the default program, if you are using Windows, you could click start and find/type 'Default Programs'. Depending on the version of windows, basically find 'Associate a file type of protocol with a program' or 'choose a default application by file type' and adjust the csv file to use a new default program.
I would be very careful if both program and notepad are able accessing the file in the same time. The reason why it was blocked in the first place was to prevent overridding the content of the file, thus probably test it first if you open it in the program and notepad. Then the program make some update - and you also make a different update with the notepad. Would both changes be recorded? As I suspect the 'last save' win in which you may lost some of your changes.
For question 1: I was guessing that whoever open the file first got the lock of that file. As such, if Excel was opening the file - then the program would have that error. Vice versa, if the program had opened the file - then the Excel may have problem opening that file. I'm not sure how to give priority to the program.

Deleted file still exists?

I am trying to save a backup of some data. If the backup exists then I want to delete it, give the most recent file the name of the backup file, and then save the new file as the most recent file.
The problem I am running into is that when I delete an existing file I still cannot save another file under the same name. The system still thinks that the file exists, and indeed the file isn't deleted at the point where the program throws the exception even though the two lines are right after each other. The only thing I can think of is that that the "delete" operation hasn't had time to complete by the time the move operation wants to execute. But I do not know how to fix it.
The code looks something like this.
File.Delete(filePath.Substring(filePath.Length - 4) + ".bak");
File.Move(filePath, filePath.Substring(0, filePath.Length - 4) + ".bak");
I thought of setting a timer in order to execute the move function later, but it seems to me that this would probably be a very sloppy and dangerous way of handling the problem. In addition, the amount of time that one needs to wait might have to vary from system to system. So I am thinking that there must be a better way.
The problem has probably to do with the fact that you use two different .Substring methods: String#Substring(int) and String#Substring(int,int). Indeed:
File.Delete(filePath.Substring(filePath.Length - 4) + ".bak");
// ^ with one int
File.Move(filePath, filePath.Substring(0, filePath.Length - 4) + ".bak");
// ^ with two ints
And unfortunately, the two are not semantically equivalent. The one with one int is the start index. So given that filePath is equal to test.txt, the file you aim to remove is .txt.bak and next you aim to move a file to test.bak. Or running this with Mono's C# emulator:
csharp> String filePath = "test.txt";
csharp> filePath.Substring(filePath.Length - 4) + ".bak"
".txt.bak"
csharp> filePath.Substring(0, filePath.Length - 4) + ".bak"
"test.bak"
Please update it with:
File.Delete(filePath.Substring(0,filePath.Length - 4) + ".bak");
// ^ added zero
File.Move(filePath, filePath.Substring(0, filePath.Length - 4) + ".bak");
Another more elegant and less error prone way is evidently to use a variable in between such that you are sure, you are talking about the same file:
String backupfile = filePath.Substring(0, filePath.Length - 4) + ".bak";
File.Delete(backupfile);
File.Move(filePath,backupfile);
Finally it's not recommended to do path processing yourself: use dedicated methods to join and manipulate file paths.
I tried your code with a little change and it worked fine. I only changed how you defined the backup file name, because the file extension can be more or less than 3 chars:
string backupPath = Path.GetFileNameWithoutExtension(filePath) + ".bak";
File.Delete(backupPath);
File.Move(filePath, backupPath);
But if it does not work for you, try to rename the backup to lets say "*.bak2" and then delete that one.
Hope that helps...

Increment Counter

In my application there is a option for where user creates a simple txt file containing some data. I would like to name the file in sequential order like ST1, ST2.... This sequence will be remain same for all users. if user1 creates a file system should name the file ST100 and then if user2 creates a file then system should name the file ST101.
I cant use the application scope as it is ready only and cant be changed at run time where as the user scope will only impact individual user not across the whole application.
I was wondering is there any other solution to achieve this apart from using database table and tacking sequence.
Thanks
You could use a loop and File.Exists:
var dir = #"C:\SampleFolder";
int number = 100; // you want to start at 100
string fileName = String.Format("ST{0}.txt", number.ToString("D3"));
while(File.Exists(Path.Combine(dir, fileName)))
fileName = String.Format("ST{0}.txt", (++number).ToString("D3"));
Finally you will have a new file-name and you get the correct path:
string path = Path.Combine(dir, fileName);
if your application is always on, you can use a global variable for the entire system with the numeric value of the next entry.
if not, you can use a text file as a database with the current number of the file; The problem with this solution is the synchronization, if two users doing the same operation at the same time can give you problems, so it is best to use a database.
I hope it helps you

C# - Program won't overwrite existing text file

My system creates a text file on the first button click of the program and appends various things to it on other button clicks. Ideally everything would be saved all at the end but as it is a monitoring system that uses a timer, some of the data does need to be written to file at specific times or it will be lost.
On the click of the first button (btnStart), the program locates/creates the main folder (given a name using a constant string) and locates/creates a sub folder (using text entered into a combination textboxes prior to the button click) this sets the directory for the text file as shown below in string (fileName):
private void createDirectory()
{ //create output file in this folder using owner name and current date
//main folder path (contains all files output from system)
string rootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Horse Monitoring Records";
//sub folder path (each patient has individual subfolder)
string subDirectory = rootDirectory + "\\" + txtPatName.Text + "/" + txtOwnerName.Text;
//file name (patient has file created for each operation)
fileName = subDirectory + "\\" + txtOwnerName.Text + "/(" + DateTime.Now.Date.ToString("dd-MM-yyyy") + ").txt";
if (!Directory.Exists(rootDirectory)) //if main folder does not exist...
{
Directory.CreateDirectory(rootDirectory); //create it in My Documents
}
if (!Directory.Exists(subDirectory)) //if patient sub folder does not exist...
{
Directory.CreateDirectory(subDirectory); //create it in Patient-Owner format
}
}
The next function called writes the first bit of text to the file, as shown:
private void saveFileDetails()
{
//Once case details have been entered, create new file using these details and add data input structure
StreamWriter consoleFile = new StreamWriter(fileName, true);
consoleFile.WriteLine("------------------------------------------- Case Details -------------------------------------------");
consoleFile.WriteLine("\n\n");
//patient and vet details
...
}
Originally when I set this function up, I did not include the 'true' overload to the StreamWriter in the first line of the function. This led to a run-time error that told me the text file could not be opened as it was already opened by another process.
Since I added the 'true' overload, the system has worked fine, but I have just encountered a problem in that once a text file has been created, it cannot be overwritten as a text file normally would by a StreamWriter using the same path.
I do need to be able to overwrite files as they become obselete after a period of time and do need to be replaced, does anyone have any idea where I can go from here to fix this issue?
Thanks,
Mark
StreamWriter's overload that takes a boolean is detailed here. true makes it append the content. If you always want to override the file, you can use File.OpenWrite; I believe you can also pass this to the constructor like the following:
new StreamWriter(File.OpenWrite(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
See more about File.OpenWrite and FIleMode. File.OpenWrite can do the job by itself if you want to use that instead. But I believe since File.OpenWrite returns FIleStream, that can be passed to the StreamWriter constructor.

Categories