i'm trying to create a json file if it doesn't exist, and then write all lines to it. The creation of the file works, but the problem i have is that File.WriteAllLines says "the process cannot access the file because it is being used by another process".
Here's my code:
public void generateFolderFiles(string folderToSearch, string fileToEdit) {
string[] filesToSearch = Directory.GetFiles(folderToSearch);
string completeJson = "";
for(int i=0;i<filesToSearch.Length;i++) {
try {
File.Delete(fileToEdit);
File.Create(fileToEdit);
} catch(Exception e){}
string[] fsLines = File.ReadAllLines(filesToSearch[i]);
string fsText = string.Join("", fsLines);
fsText=fsText.Replace("[", "").Replace("]", "");
completeJson+=fsText;
}
completeJson="["+completeJson+"]";
string[] lnes = new string[1]{completeJson};
File.WriteAllLines(fileToEdit, lnes);
}
File.Create() returns a FileStream which you can use to do the file IO. While FileStream is open (and in your case it remains open for who knows how long because you do not call neither Close(), nor Dispose() on it), it does not not allow anybody else to access it (in reality, things are a bit more complex), which is why your WriteAllLines() call fails.
Related
I have folder full of *.msg files saved from Outlook and I'm trying to convert them to Word.
There is a loop that loads each *.msg as MailItem and saves them.
public static ConversionResult ConvertEmailsToWord(this Outlook.Application app, string source, string target)
{
var word = new Word.Application();
var emailCounter = 0;
var otherCounter = 0;
var directoryTree = new PhysicalDirectoryTree();
foreach (var node in directoryTree.Walk(source))
{
foreach (var fileName in node.FileNames)
{
var currentFile = Path.Combine(node.DirectoryName, fileName);
var branch = Regex.Replace(node.DirectoryName, $"^{Regex.Escape(source)}", string.Empty).Trim('\\');
Debug.Print($"Processing file: {currentFile}");
// This is an email. Convert it to Word.
if (Regex.IsMatch(fileName, #"\.msg$"))
{
if (app.Session.OpenSharedItem(currentFile) is MailItem item)
{
if (item.SaveAs(word, Path.Combine(target, branch), fileName))
{
emailCounter++;
}
item.Close(SaveMode: OlInspectorClose.olDiscard);
}
}
// This is some other file. Copy it as is.
else
{
Directory.CreateDirectory(Path.Combine(target, branch));
File.Copy(currentFile, Path.Combine(target, branch, fileName), true);
otherCounter++;
}
}
}
word.Quit(SaveChanges: false);
return new ConversionResult
{
EmailCount = emailCounter,
OtherCount = otherCounter
};
}
The save method looks likes this:
public static bool SaveAs(this MailItem mail, Word.Application word, string path, string name)
{
Directory.CreateDirectory(path);
name = Path.Combine(path, $"{Path.GetFileNameWithoutExtension(name)}.docx");
if (File.Exists(name))
{
return false;
}
var copy = mail.GetInspector.WordEditor as Word.Document;
copy.Content.Copy();
var doc = word.Documents.Add();
doc.Content.Paste();
doc.SaveAs2(FileName: name);
doc.Close();
return true;
}
It works for most *.msg files but there are some that crash Outlook when I call copy.Content on a Word.Document.
I know you cannot tell me what is wrong with it (or maybe you do?) so I'd like to findit out by myself but the problem is that I am not able to catch the exception. Since a simple try\catch didn't work I tried it with AppDomain.CurrentDomain.UnhandledException this this didn't catch it either.
Are there any other ways to debug it?
The mail that doesn't let me get its content inside a loop doesn't cause any troubles when I open it in a new Outlook window and save it with the same method.
It makes sense to add some delays between Word calls. IO operations takes some time to finish. Also there is no need to create another document in Word for copying the content:
var copy = mail.GetInspector.WordEditor as Word.Document;
copy.Content.Copy();
var doc = word.Documents.Add();
doc.Content.Paste();
doc.SaveAs2(FileName: name);
doc.Close();
Instead, do the required modifications on the original document instance and then save it to the disk. The original mail item will remain unchanged until you call the Save method from the Outlook object model. You may call the Close method passing the olDiscard which discards any changes to the document.
Also consider using the Open XML SDK if you deal with open XML documents only, see Welcome to the Open XML SDK 2.5 for Office for more information.
Do you actually need to use Inspector.WordEditor? You can save the message in a format supported by Word (such as MHTML) using OOM alone by calling MailItem.Save(..., olMHTML) and open the file in Word programmatically to save it in the DOCX format.
I have a small csv file with about 40 lines of 30-ish characters each. I also have several slow threads going at the same time, so they will rarely attempt to access the file at the same time, however it can happen. So I need to make sure that they all wait for turn if necessary.
As I understand I can do:
string[] text = File.ReadAllLines(path);
text[23] = "replacement";
File.WriteAllLines(path, text);
But doing this I assume I'm running the risk of the file being updated by a different thread in between readAllLines and WriteAllLines.
What I want to do is something like:
using (FileStream fs = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
StreamReader sr = new StreamReader(fs);
StreamWriter sw = new StreamWriter(fs);
//Replace the line
}
However I don't see a way to do something like writeAllLines with either the FileStream object or the StreamWriter object. Is there a simple way to do this, or do I need to write each line to a new file and then replace the old file?
With a StreamWriter you don't need to specify to write all lines. You just write what you have. Either write to the file line by line using the WriteLine method or join your text array into one large string and write it all at once using the Write method.
As for preventing multiple threads from accessing your file at the same time you should use a lock.
public static object FileAccessLock = new object(); // Put this in a global area.
lock(FileAccessLock)
{
string[] text = File.ReadAllLines(path);
text[23] = "replacement";
File.WriteAllLines(path, text);
}
The lock will allow only one thread to access that section of code at a time. All other threads will wait until it has completed.
That doesn't mean that the File.WriteAllLines method wont be locked by something else so you should handle an exception here.
You need to lock write to only one thread access.
Like this:
public class FileHelper
{
private static object fileLock = new object();
public static void ReplaceLine(string path, int line, string replace)
{
lock(fileLock)
{
string[] text = File.ReadAllLines(path);
text[line] = replace;
File.WriteAllLines(path, text);
}
}
}
I am trying to write a log file, but it constantly says "File being used by another process". Here's my code:
//_logFile = "system.log"
if(!File.Exists(Path.Combine("logs", _logFile)))
{
File.Create(Path.Combine("logs", _logFile)).Close();
sw = File.AppendText(Path.Combine("logs", _logFile));
}
else
{
sw = File.AppendText(Path.Combine("logs", _logFile));
}
When I run it, it points to the File.Create(Path.Combine("logs", _logFile)).Close() line and gives me the error.
Edit:
I changed if(!File.Exists(_logFile)) to if(!File.Exists(Path.Combine("logs", _logFile))) but I still get the same error.
Assuming you don't need access to this stream outside the context of this method, I'd refactor your code to this:
var filePath = Path.Combine("logs", _logFile);
using (var sw = File.AppendText(filePath))
{
//Do whatever writing to stream I want.
sw.WriteLine(DateTime.Now.ToString() + ": test log entry");
}
This way, no matter what happens inside the using block, you know the file will be closed so you can use it again later.
Note that File.AppendText will create the file if it doesn't already exist, so File.Create is not needed.
Currently trying to create a string from a text file, however their seems to be an error preventing the stream reader from reading the text file correctly.
private string testString = "Cheese";
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
if (openFileDialog.ShowDialog() != DialogResult.Cancel)
{
fileName = openFileDialog.FileName;
LoadFile();
}
}
private void LoadFile()
{
String lineFromFile = "Chicken";
*StringBuilder RawFileInput = new StringBuilder();
using (StreamReader reader = new StreamReader(fileName))
{
while ((lineFromFile = reader.ReadLine()) != null)
{
RawFileInput.AppendLine(lineFromFile);
}
}*
testString = lineFromFile;
testTB.Text = testString;
}
The output should the code execute has the output textbox be empty, however should the block of code between the asterisks be commented out, the output textbox obviously displays the test phrase of Chicken. As such I'm pretty sure there is a problem with this particular block, however I can't seem to figure out what.
Thanks in advance.
If I understood well your code, you are trying to set the testTB.Text with the text in your file. Taking that in account, shouldn't your last lines be:
testString = RawFileInput.ToString();
testTB.Text = testString;
You can achieve the same result with no need of a StringBuilder, replacing your whole LoadFile method with this line:
testTB.Text = File.ReadAllText(fileName);
You should be able to read a document in entirety, like the following:
var builder = new StringBuilder();
using(var reader = new StreamReader(path))
builder.Append(reader.ReadToEnd());
That would be the ideal, as it is more performant than ReadAllText.
ReadToEnd works best when you need to read all the input from the
current position to the end of the stream. If more control is needed
over how many characters are read from the stream, use the
Read(Char[], Int32, Int32) method overload, which generally results in
better performance. ReadToEnd assumes that the stream knows when it
has reached an end. For interactive protocols in which the server
sends data only when you ask for it and does not close the connection,
ReadToEnd might block indefinitely because it does not reach an end,
and should be avoided.
If you're wanting the contents of a file to populate a textbox, just set the Multiline property to true, and use File.ReadAllLines()
testTb.Lines = File.ReadAllLines(fileName);
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.