I am trying to implement some basic config-file read/write/edit-funtctionality in a class.
The config-file is stored in the file [...]\config.txt, which is stored in the 'path'-variable.
Syntax of my configfile is as follows:
param0 value_of_it
param1 value_of_it
something_else you_get_it
Everything left of the first space is the name by which the parameter is to be found, the rest of the line is considered the value of that param.
So much for context.
Now, when I am trying to delete that file using file.delete I get:
"System.IO.IOException: Cannot access file, because it is being used by another process".
Here is the code in question:
internal void alter(string param, bool replace = false, string value = null) {
//here
string buf;
System.IO.StreamReader reader = new System.IO.StreamReader(path);
string bufPath = path.Substring(0, path.Length-4)+"_buf.txt";
System.IO.StreamWriter writer = new StreamWriter(bufPath);
while((buf = reader.ReadLine()) != null) {
if(buf.Substring(0, param.Length) != param)
writer.WriteLine(buf);
else if(replace)
writer.WriteLine(param+" "+value);
}
writer.Dispose();
reader.Dispose();
writer.Close();
reader.Close();
//there
File.Delete(path);
File.Move(bufPath, path);
}
The function copies the original file line by line into the buffer, except for the param specified in the call. That line either gets ignored, thus deleted, or replaced if the call says so.
Then the original file is deleted and the _buf version is "moved" there (renamed).
These steps are executed correctly (the _buf-file gets created and correctly filled) until 'File.Delete(path)' which is where the exception is thrown.
I already tried commenting out the entire part between //here and //there and only deleting the file, which results in the same exception, so the problem has to be of more basic nature.
I tried finding a process having a problematic handle using Sysinternals Process Explorer as suggested here on ServerFault, but couldn't find anything. I only found three handles for that file in my own process.
I also ran the app after killing the windows explorer, because I've read the sometimes it is the offender; That didn't help either.
I also checked the entire rest of my program and confirmed the following:
This is the first occasion this file is used, so forgotten handles from the past are impossible.
It is a single-threaded application (so far), so deadlocks and race-conditions are ruled out as well.
So here is my question: How can I find what is locking my file?
Alternatively: Is there a simpler way to delete or edit a line in a file?
It will be more convenient to store configuration in xml format, because you have options to store more complex value if you need to do so later. And also you can rely on XDocument for loading and saving the file as simple as XDocument.Load(path) and doc.Save(path). For example, your current configuration file will look like following in xml format :
<?xml version="1.0" encoding="utf-8"?>
<params>
<param name="param0">value_of_it</param>
<param name="param1">value_of_it</param>
<param name="something_else">you_get_it</param>
</params>
And your function to alter existing param's value or add new parameter to configuration file will be like so :
internal void alterXml(string param, bool replace = false, string value = null)
{
//load xml configuration file to XDocument object
var doc = XDocument.Load(path);
//search for <param> having attribute "name" = param
var existingParam = doc.Descendants("param").FirstOrDefault(o => o.Attribute("name").Value == param);
//if such a param element doesn't exist, add new element
if (existingParam == null)
{
var newParam = new XElement("param");
newParam.SetAttributeValue("name", param);
newParam.Value = "" + value;
doc.Root.Add(newParam);
}
//else update element's value
else if (replace) existingParam.Value = "" + value;
//save modified object back to xml file
doc.Save(path);
}
If there are three handles to that file in your process, then you are opening that file multiple times, or calling the function multiple times with the handle being left open, or you have ghost versions of your processing hanging around. You should double check that you are not opening this file elsewhere in your code. When I run your code in a test, it runs fine (if I don't cause the bug related to the Substring function below).
Because the file at "path" is opened in a read-mode, it will be able to be opened by other readers without problems. However, as soon as other code tries a write operation (including file metadata, such as File.Delete), you will see this error.
Your code is not exception safe, so an exception thrown by this, or a similar function while you are reading from the stream in that loop will cause the handle to stay open, causing a later call to a function that opens the file to fail with exception you are now experiencing at File.Delete. To avoid exception safety issues, use try/finally or using (I'll provide an example below).
One such exception that is likely to occur is where you call Substring within the loop, because there is a chance the length of the line is less than the length of a full parameter. There's also another issue here in that if you pass the parameter without a trailing space, then it's possible that you will match another parameter that contains the first as a prefix (i.e. "hello" would match "hello_world").
Here's a slightly fixed version of the code that is exception safe and fixes the Substring issue.
internal void alter( string param, bool replace = false, string value = null )
{
//here
string buf;
string bufPath = path.Substring( 0, path.Length - Path.GetExtension( path ).Length ) + "_buf.txt";
using ( System.IO.StreamReader reader = new System.IO.StreamReader( path ) )
{
string paramWithSpace = param + " ";
using ( System.IO.StreamWriter writer = new StreamWriter( bufPath ) )
{
while ( ( buf = reader.ReadLine() ) != null )
{
if ( !buf.StartsWith( paramWithSpace ) )
writer.WriteLine( buf );
else if ( replace )
writer.WriteLine( paramWithSpace + value );
}
}
}
//there
File.Delete( path );
File.Move( bufPath, path );
}
However, you may wish to consider loading your configuration entirely into memory, altering it in memory multiple times and then writing back to it once in a batch. This will give you greater performance reading/writing configuration (and usually you have to write multiple configuration changes at once) and it will also simplify your code. For a simple implementation, use the generic Dictionary class.
Related
Hello I'm beginner with C# and I want to delete the last character of my file to inject JSON objects to this file manually (I know that's not the best way to do that), so I can get the right format I tried with multiple ways like open the file, manipulating the string (deleting the last character) and when I try to replace the text in that same file I have errors like IOException: The process cannot access the file 'file path' because it is being used by another process or System.UnauthorizedAccessException : 'Access to the path 'C:\Users\ASUS\Desktop\Root' is denied.
I'll show you the code :
StoreLogs Log = new StoreLogs()
{
Id = ID,
DateTime = dateT,
TaskName = task,
SrcAddress = srcPath,
DstAddress = path,
FileSize = DirSize(new DirectoryInfo(srcPath)),
DelayTransfer = ts.Milliseconds,
};
// Record JSON data in the variable
string strResultJson = JsonConvert.SerializeObject(Log);
// Show the JSON Data
// Console.WriteLine(strResultJson);
// Write JSON Data in another file
string MyJSON = null;
string strPath = #"C:\Users\ASUS\Desktop\Backup\logs\log.json";
if (File.Exists(strPath))
{
//FileInfo table = new FileInfo(strPath);
//string strTable = table.OpenText().ReadToEnd();
//string erase = strTable.Remove(strTable.LastIndexOf(']'));
//Console.WriteLine(erase);
//StreamReader r1 = new StreamReader(strPath);
//string strTable = r1.OpenText().ReadToEnd();
//string erase = strTable.Remove(strTable.LastIndexOf(']'));
//r1.Close();
using (StreamReader sr = File.OpenText(strPath))
{
string table = sr.ReadToEnd();
string erase = table.Remove(table.LastIndexOf(']'));
sr.Close();
File.WriteAllText(strPath, erase);
}
//MyJSON = "," + strResultJson;
//File.AppendAllText(strPath, MyJSON + "]");
//Console.WriteLine("The file exists.");
}
else if (!File.Exists(strPath))
{
MyJSON = "[" + strResultJson + "]";
File.WriteAllText(strPath, MyJSON);
Console.WriteLine("The file doesn't exists.");
}
else
{
Console.WriteLine("Error");
}
// End
Console.WriteLine("JSON Object generated !");
Console.ReadLine();
And that's the result I want :
[{"Id":"8484","DateTime":"26 novembre 2019 02:33:35 ","TaskName":"dezuhduzhd","SrcAddress":"C:\\Users\\ASUS\\Desktop\\Root","DstAddress":"C:\\Users\\ASUS\\Desktop\\Backup","FileSize":7997832.0,"DelayTransfer":0.0},{"Id":"8484","DateTime":"26 novembre 2019 02:33:35 ","TaskName":"dezuhduzhd","SrcAddress":"C:\\Users\\ASUS\\Desktop\\Root","DstAddress":"C:\\Users\\ASUS\\Desktop\\Backup","FileSize":7997832.0,"DelayTransfer":0.0},{"Id":"8484","DateTime":"26 novembre 2019 02:33:35 ","TaskName":"dezuhduzhd","SrcAddress":"C:\\Users\\ASUS\\Desktop\\Root","DstAddress":"C:\\Users\\ASUS\\Desktop\\Backup","FileSize":7997832.0,"DelayTransfer":0.0}]
Edit :
Thank you all for your advices
Solution:
FileStream fs = new FileStream(strPath, FileMode.Open, FileAccess.ReadWrite);
fs.SetLength(fs.Length - 1);
fs.Close();
In the code example you have posted you are opening a stream to read the file. A using block will dispose the stream after you exit the block. You are trying to write to the file, while the read stream is still accessing it (the read stream still exists). You've basically opened the file, you read from it, and are trying to write back to it while still holding it open. The reason this is a problem is that you are not using the stream to write. So your second, write, process is unable to access the file. I see you are closing the stream prior to write, but I'm willing to bet it's still holding the reference open.
I would try this method:
How to both read and write a file in C#
what it says is the access to the path (C:\Users\ASUS\Desktop\Root) denied for the user who is running the application. for ex: If you are running from Visual studio on user1 windows login then user1 should have appropriate rights to that root folder. If the code is running by itself (exe) then check the access for that user who is invoking that exe.
Based on the errors you posted seems that:
Maybe you're leaving some stream open pointing to the file you want to edit, use the 'using' statement to avoid this (see this link for more info)
You're trying to access a file when you don't have needed permissions (you aren't a system admin or file is read-only), try changing file ubication or setting it to be writeable (see this link for mor info about the UnauthorizedAccessException exception)
Hope this helps you!
I need to create a file, write one line of text in the file and then delete the file and estimate how long it will take to do it.
Unfortunately, I am running in couple of problems, first I cannot write in the file, it succesfully creates it but nothing is written to it.
Secondly, I cannot delete the file because it has been used by another process.
Please help.
I have been trying to delete it for quite some time.
I have also tried wrapping it in usings, to no avail.
Writing to the file is the same situation. I even changed it so the file ends in .txt but that does not make any difference.
public static void ProcessFile(string path)
{
string fullpath = path;
int lastBracket = path.LastIndexOf("\\");
// the filename does not contain .html so it can be named to .txt
string newFileName = path.Substring(lastBracket + 1, path.Length - lastBracket - 6) + " hrefcount.txt";
string newPath = Path.Combine(fullpath.Substring(0, lastBracket), newFileName);
Console.WriteLine(newPath);
int counter = 0;
foreach (var line in File.ReadAllLines(path))
{
if (line.Contains("href="))
{
counter++;
}
}
var fileCreated = File.CreateText(newPath);
fileCreated.WriteLine("The number of times href appears is " + counter);
Console.WriteLine();
File.Delete(newPath);
}
File created, nothing written to it, unable to delete due to has been used by another process.
Instead of File.CreateText() use File.WriteAllText(path, content). It writes the text and then closes the file allowing you to delete it if necessary
Instead of the following
var fileCreated = File.CreateText(newPath);
fileCreated.WriteLine("The number of times href appears is " + counter);
You may write
File.WriteAllText(newPath, $"The number of times href appears is {counter}");
Refer documentation here
The issue with your approach is that CreateText() is used to write to a stream. But in your case, it is not necessary since you're writing all the text at once to the file and that text is small in size.
The cause of your error is the fact that you don't close and dispose the variable fileCreated. This, is a FileStream and until you close and dispose this variable the file is not available to anyone, even your own code that has opened the file.
So the first thing to do is
using (var fileCreated = File.CreateText(newPath))
{
fileCreated.WriteLine("The number of times href appears is " + counter);
}
The using block ensure the proper disposal of the variable.
However there are other parts of your code that you can simplify
public static void ProcessFile(string path)
{
string folder = Path.GetDirectoryName(path);
string file = Path.GetFileName(path);
// Keep the first 6 characters from the source file?
string newFile = file.Substring(0, 6) + " hrefcount.txt";
string newPath = Path.Combine(folder, newFile);
// A single line to retrieve your counter thanks to IEnumerables and Linq
int counter = File.ReadLines(path).Count(x => x.Contains("href="));
// Create, but dispose also the file
using (var fileCreated = File.CreateText(newPath))
{
fileCreated.WriteLine("The number of times href appears is " + counter);
}
// Now you should be free to delete the file
File.Delete(newPath);
}
I cannot delete the file because it has been used by another process.
Probably you're not disposed your files after creating. To do that, you should additionally use FileStream.Dispose method:
File.Create(path).Dispose();
estimate how long it will take to do it
You can use System.Diagnostics.Stopwatch class to do that:
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
/*
do the magic
*/
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
You can use File.WriteAllText method instead of File.CreateText to write your text to file:
File.WriteAllText(path, myText);
Remember that since the .NET 4 you can use this method with array or List<T> too instead of string.
File.Create() supports Dispose method which help you to release that file resource to perform further operations
To perform operations on file follow below steps:
Create file and free up the resource using Dispose().
File.Create(newPath).Dispose();
or Use StreamWriter to create file and write text to it.
using( StreamWriter sw = new StreamWriter(newPath, true)
{
sw.Write($"The number of times href appears is {counter}"); //Used string interpolation
}
StreamWriter calls Dispose() function to release file resource.
When Writer release control over file, then you will not face issue related to I cannot delete the file because it has been used by another process.
Now you can delete file using,
File.Delete(newPath);
MSDN : IDisposable.Dispose Method
Performs application-defined tasks associated with freeing, releasing,
or resetting unmanaged resources.
Using this article from MSDN, I'm trying to search through files in a directory. The problem is, every time I execute the program, I get:
"An unhandled exception of type 'System.OutOfMemoryException' occurred in mscorlib.dll".
I have tried to some other options like StreamReader, but I can't get it to work. These files are HUGE. Some of them range in upwards to 1.5-2GB each and there could be 5 or more files per day.
This code fails:
private static string GetFileText(string name)
{
var fileContents = string.Empty;
// If the file has been deleted since we took
// the snapshot, ignore it and return the empty string.
if (File.Exists(name))
{
fileContents = File.ReadAllText(name);
}
return fileContents;
}
Any ideas what could be happening or how to make it read without memory errors?
Entire code (in case you don't want to open the MSDN article)
class QueryContents {
public static void Main()
{
// Modify this path as necessary.
string startFolder = #"c:\program files\Microsoft Visual Studio 9.0\";
// Take a snapshot of the file system.
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);
// This method assumes that the application has discovery permissions
// for all folders under the specified path.
IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles("*.*", System.IO.SearchOption.AllDirectories);
string searchTerm = #"Visual Studio";
// Search the contents of each file.
// A regular expression created with the RegEx class
// could be used instead of the Contains method.
// queryMatchingFiles is an IEnumerable<string>.
var queryMatchingFiles =
from file in fileList
where file.Extension == ".htm"
let fileText = GetFileText(file.FullName)
where fileText.Contains(searchTerm)
select file.FullName;
// Execute the query.
Console.WriteLine("The term \"{0}\" was found in:", searchTerm);
foreach (string filename in queryMatchingFiles)
{
Console.WriteLine(filename);
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
// Read the contents of the file.
static string GetFileText(string name)
{
string fileContents = String.Empty;
// If the file has been deleted since we took
// the snapshot, ignore it and return the empty string.
if (System.IO.File.Exists(name))
{
fileContents = System.IO.File.ReadAllText(name);
}
return fileContents;
}
}
The problem you're having is based on trying to load multiple gigabytes of text at the same time. If they're text files, you can stream them and just compare one line at a time.
var queryMatchingFiles =
from file in fileList
where file.Extension == ".htm"
let fileLines = File.ReadLines(file.FullName) // lazy IEnumerable<string>
where fileLines.Any(line => line.Contains(searchTerm))
select file.FullName;
I would suggest that you are getting an out of memory error because the way the query is written I believe that you will need to load the entire text of every file into memory and none of the objects can be released until the entire file set has been loaded. Could you not check for the search term in the GetFileText function and then just return a true or false?
If you did that the file text at least falls out of scope at the end of the function and the GC can recover the memory. It would actually be better to rewrite as a streaming function if you are dealing with large files/amounts then you could exit your reading early if you come across the search term and you wouldn't need the entire file in memory all the time.
Previous question on finding a term in an HTML file using a stream
I am not sure if this is the reason why its throwing error. But for me the test where I perform the File.Replace with files residing in different directories failed. Just want to know if this is the case with others too.
[TestMethod]
public void TestFileReplaceDifferentDirectory()
{
string FileToReplace = #"c:\tools\file2.txt";
string FileToDelete = #"D:\DropFolder\file0.txt";
string strToWrite;
using (var wtr = File.CreateText(FileToDelete))
{
long ticks = DateTime.Now.Ticks;
strToWrite = string.Join(",", ticks, ticks, ticks);
wtr.WriteLine(strToWrite);
wtr.Flush();
wtr.Close();
}
string BackupFileName = Path.Combine(
Path.GetDirectoryName(FileToReplace),
string.Format("{0}_{1}{2}",
Path.GetFileNameWithoutExtension(FileToReplace),
DateTime.Now.Ticks,
Path.GetExtension(FileToReplace))
);
File.Replace(FileToDelete, FileToReplace, BackupFileName, false);
using (StreamReader rdr = new StreamReader(FileToReplace))
{
string line = rdr.ReadLine();
Assert.AreEqual(strToWrite, line);
}
}
[TestMethod]
public void TestFileReplaceSameDirectory()
{
string FileToReplace = #"c:\tools\file2.txt";
string FileToDelete = #"c:\tools\file0.txt";
string strToWrite;
using (var wtr = File.CreateText(FileToDelete))
{
long ticks = DateTime.Now.Ticks;
strToWrite = string.Join(",", ticks, ticks, ticks);
wtr.WriteLine(strToWrite);
wtr.Flush();
wtr.Close();
}
string BackupFileName = Path.Combine(
Path.GetDirectoryName(FileToReplace),
string.Format("{0}_{1}{2}",
Path.GetFileNameWithoutExtension(FileToReplace),
DateTime.Now.Ticks,
Path.GetExtension(FileToReplace))
);
File.Replace(FileToDelete, FileToReplace, BackupFileName, false);
using (StreamReader rdr = new StreamReader(FileToReplace))
{
string line = rdr.ReadLine();
Assert.AreEqual(strToWrite, line);
}
}
I was trying to write to a temporary file (using Path.GetTempFileName()) and replace a file in D:\DropFolder. It wasn't happening; it threw an System.IO.Exception
So does this mean my only option is to create a kind of temporary file in the same directory as that of FileToReplace and carry out this task?
This is by design. The point of using File.Replace() is to be able to replace a file that's locked by another process. Very important if you have just one shot at saving precious data, common at machine shutdown or unexpected program termination. Needless to say, that does takes a trick or two since Windows is adamant about stopping you from overwriting a locked file.
It is possible at all because the operating system only puts a lock on the file data but not on the directory entry for a file. In other words, it is valid to rename the file, even though it is locked. The underlying system call is the same as File.Move().
Which operates two distinct ways, depending on the destFileName. If the destination path is on the same drive, the file system merely has to move the directory entry. Very fast and trouble-free. But that cannot work if it is not on the same drive, that requires moving the file data as well. Which is of course slow and not possible at all in a scenario where the file data is locked.
It is therefore imperative that the destinationBackupFileName argument you pass to File.Replace() is a path that's on the same drive as sourceFileName. Not doing this causes the exception when the move fails. Not otherwise hard to do in general, boilerplate is to make the backup filename simply the same as the source filename with, say, ".bak" appended to the path.
I've got a class with a method "GetNewsFeed", that when a page is requested:
Check to see if a file exists & it is less than 30 minutes old
If it does exist, read contents of the file, push contents onto page
If it does not exist, go to a URL and write the contents of that page to a .txt file, push contents onto page
I am not very well versed with C#, so I'm trying to cobble together a few sources. I believe I am close, but I'm unable to get the files to refresh every 30 minutes if needed (I'm not getting any compliation errors or anything). Any help would be appreciated.
public static string GetNewsFeed(string url, string fileName)
{
// Set the path to the cache file
String filePath = HttpContext.Current.Server.MapPath("/cachefeed/" + fileName + ".txt");
string fileContents = "";
// If the file exists & is less than 30 minutes old, read from the file.
if (File.Exists(filePath) && (File.GetLastWriteTime(filePath) > DateTime.Now.AddMinutes(-30)))
{
fileContents = File.ReadAllText(filePath);
}
else
{
try
{
// If the file is older than 30 minutes, go out and download a fresh copy
using (var client = new WebClient())
{
// Delete and write the file again
fileContents = client.DownloadString(url);
File.Delete(filePath);
File.WriteAllText(filePath, fileContents);
}
}
catch (Exception)
{
if (File.Exists(filePath))
{
fileContents = File.ReadAllText(filePath);
}
}
}
return fileContents;
}
Finally, I've got some code elsewhere that will read these text files and manipulate their contents onto the page. I don't have any issues with this.
Odds are, you're catching an exception in the else block and it's only returning the fileContents. Try putting a breakpoint in the exception block to see what is going on.
You'll need to change it to:
catch( Exception e )
in order to get this information.
Also, you don't need this:
File.Delete(filePath);
The WriteAllText method will overwrite the file that is already there. Try removing that line and check your directory permissions.
You may also want to change
(File.GetLastWriteTime(filePath) > DateTime.Now.AddMinutes(-30)))
to
(DateTime.Now - File.GetLastWriteTime(filePath)).TotalMinutes > 30
I added a throw to my catch and believe it or not, one of the URL's I was passing into my method was invalid. So yes, the culprit in my code was the catch statement.
I fixed this and all is working properly.
Thanks for the tips everyone.