I am using this code to compress files:
public class GzipCompressor : ICompressor
{
public void Compress(string input, string output)
{
using (FileStream originalFileStream = File.OpenRead(input))
using (FileStream compressedFileStream = File.OpenWrite(output))
using (GZipStream compressor = new GZipStream(compressedFileStream, CompressionMode.Compress))
originalFileStream.CopyTo(compressor);
}
}
The problem is that if someone closes the application while the file is compressing, the file will be saved to the destination path, but it will be invalid (it will not even be able to open it). Is there a way to avoid this? I would prefer such a file not to be created at all in such a situation.
You can try to use Application.ApplicationExit to perform logic when the application is attempting to close down. Keep in mind that there's still a chance something catastrophic is occurring and you wont be able to clean everything up (if the computer is forcibly shut down, there's not much you can do). The documentation also mentions the following got'cha:
Because this is a static event, you must detach any event handlers attached to this event in the ApplicationExit event handler itself. If you do not detach these handlers, they will remain attached to the event and continue to consume memory.
It can be solved by using GetTempFileName. I will provide an example based on your example.
private FileInfo? _tmpFileInfo;
public void Compress(string input, string output)
{
_tmpFileInfo = CreateTempFile();
using (FileStream originalFileStream = File.OpenRead(input))
using (FileStream compressedFileStream = File.OpenWrite(_tmpFileInfo!.FullName))
using (GZipStream compressor = new GZipStream(compressedFileStream, CompressionMode.Compress))
{
originalFileStream.CopyTo(compressor);
}
File.Copy(_tmpFileInfo.FullName, output);
File.Delete(_tmpFileInfo.FullName);
}
So basically what we do, is we create a temporary file and zip it to it, when it is done successfully, we copy it back to the output location. Here is CreateTempFile method:
public FileInfo? CreateTempFile()
{
var fileName = Path.GetTempFileName();
FileInfo? fileInfo = new FileInfo(fileName)
{
Attributes = FileAttributes.Temporary
};
return fileInfo;
}
That is it.
If you need to delete the temp file while the application makes an unhandled exception or process exists or other events, you can create an event like this:
public static void Main(string[] args)
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(GzipCompressor.ActionOnExit);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(GzipCompressor.ActionOnExit);
var zip = new GzipCompressor();
zip.Compress(#"C:\temp\in\file1.txt", #"C:\temp\out\file1.txt.gz");
}
Then in your GzipCompressor class make FileInfo static.
private static FileInfo? _tmpFileInfo;
and create a static method to delete the temp file:
public static void ActionOnExit(object? sender, EventArgs e)
{
File.Delete(_tmpFileInfo.FullName);
}
There is, of course, no 100% guarantee that the temp file is deleted, let's say if you turn the computer forcibly by powering it off. The same scenario is valid while it is copying the file.
note: what regards the event in my answer, I tried it with a console app in dot net 6, but this should be cross-compatible with a dot net core, dot net framework, etc. read more about the AppDomian events.
I left the full answer on github: https://github.com/maythamfahmi/BlogExamples/tree/main/Stackoverflow/TempFiles
Related
I have a file watcher, once the file is created in the stage directory, I want to move it to archive. When i manually copy file from a folder to stage, atleast one of those files doesnt move
Here is my code
class Program
{
public static String stagepath = #"C:\Users\a\Desktop\\stage\";
public static String archivePath = #"C:\Users\a\Desktop\archive\";
static void Main(string[] args)
{
MonitorDirectory(stagepath);
Console.ReadKey();
}
public static void MonitorDirectory(string path)
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.Created += FileCreated;
watcher.EnableRaisingEvents = true;
}
private static void FileCreated(object sender, FileSystemEventArgs e)
{
if (!FileIsReady(stagepath+e.Name)) return;
System.IO.File.Move(stagepath + e.Name, archivePath + e.Name);
//Console.WriteLine("File is available");
}
private static bool FileIsReady(string path)
{
try
{
using (var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None))
{
return true;
}
}
catch (IOException)
{
return false;
}
}
}
}
I understand there are similar questions asked, but none of them is resolving my issue, so posted again.
One option would be to subscribe to FileSystemWatcher.Changed event. In your case you can just change the event name and the code should work.
But of course, there's no universal way of detecting that the writer had finished his work. You must invent one which will suit your needs. For example:
Assume the writer will always lock the file only once. This means
that the watcher can start his work right after the file becomes
unlocked (that's what you get if you follow my advice)
Use a checksum or any other method of validating file consistency. If
you can accomplish that, you should be able to avoid unnecessary
locks and exceptions. Just don't move the file until it is valid.
Think about why you're moving a file right after it was created
somewhere else. Most of the time you should avoid that kind of
operations, because the writer starts doing somewhat a strange job.
I'm trying to build a small program to monitor my pfirewall.log, but I can't seem to open it.
I found quite many (simple) answers, that all kinda say
// use FilesystemWatcher
// open FileStream
// read from last position to end
// output new lines
The problem here is: The file seems to always be opened by another process already. I guess that's the windows process writing to the file, since it's getting written to all the time, as Notepad++ shows me.
Which means, Notepad++ can for some reason do what I can not: Read the file despite it being opened already.
I initialize my monitor in the constructor:
public FirewallLogMonitor(string path)
{
if (!File.Exists(path))
throw new FileNotFoundException("Logfile not found");
this.file = path;
this.lastPosition = 0;
this.monitor = new FileSystemWatcher(Path.GetDirectoryName(path), Path.GetFileName(path));
this.monitor.NotifyFilter = NotifyFilters.Size;
}
And try to read the file on monitor.Changed event:
private void LogFileChanged(object sender, FileSystemEventArgs e)
{
using (FileStream stream = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (StreamReader reader = new StreamReader(stream))
{
stream.Seek(this.lastPosition, SeekOrigin.Begin);
var newLines = reader.ReadToEnd();
this.lastPosition = stream.Length;
var filteredLines = filterLines(newLines);
if (filteredLines.Count > 0)
NewLinesAvailable(this, filteredLines);
}
}
It always throws the IOException on new FileStream(...) to tell me the file is already in use.
Since Notepad++ does it, there has to be a way I can do it too, right?
**Edit: ** A button does this:
public void StartLogging()
{
this.IsRunning = true;
this.monitor.Changed += LogFileChanged;
this.monitor.EnableRaisingEvents = true;
}
**Edit2: ** This is not a duplicate of FileMode and FileAccess and IOException: The process cannot access the file 'filename' because it is being used by another process, since that one assumes I have control over the writing process. Will try the other suggestions, and report back with results.
If i understand your question you can use the notepad++ itself with a plugin to monitor you need to go to:
plugins -> Document Moniter -> Start to monitor
if you dont have this plugin you can download it here:
http://sourceforge.net/projects/npp-plugins/files/DocMonitor/
So, I'm trying to create a login form, but I need to read and write to files etc, first of all; I'm creating a file then writing 'test' to the file, but if I then delete the file and try and issue my commands at the same time:
FileIO.FileCheck("Usernames.pheonix");
FileIO.WriteFile("Usernames.pheonix", "test");
It pulls me an error;
The process cannot access the file 'C:\Users\XXX\Desktop\Pheonix Launcher\Pheonix\bin\Debug\Usernames.pheonix' because it is being used by another process.
I can't seem to get me head around why it keeps on doing this, here are my Read/Write file:
public static void createFile(String FileName)
{
File.Create(FileName);
}
public static void WriteFile(String File ,String Message)
{
FileStream fs1 = new FileStream(File, FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter writer = new StreamWriter(fs1);
writer.Write(Message);
writer.Close();
}
public static void FileCheck(String fileName)
{
if (File.Exists(fileName))
Console.WriteLine("File exists.");
else
createFile(fileName);
}
File.Create does create a file - and return an open stream to it. Put it in a using block. (Don't just call close on it - that would be a bug because it is not exception safe).
Actually, looking closer I see that you don't need this at all due to FileMode.OpenOrCreate. The file will be created anyway.
I have simple page, that loads XML from filesystem, fills textboxes, these can be updated and saved. For serializing and deserializing I am using these methods:
private static readonly object FormDataLock = new object();
public static FormData getFormData(string filename)
{
FormData fd;
lock (FormDataLock)
{
XmlSerializer x = new XmlSerializer(typeof(FormData));
using (Stream s = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
return (FormData)x.Deserialize(s);
}
}
}
public void saveFormData(string filename)
{
lock (FormDataLock)
{
XmlSerializer x = new XmlSerializer(typeof(FormData));
using (Stream s = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
x.Serialize(s, this);
}
}
}
But the problem is, that I am gettig sometimes (as I have notticed when I click the "save" button too fast after PageLoad) the IOException:
IOException: The process cannot access the file ".." because it is being used by another process.
I was trying to lock the block with mutex, but it is still not working properly. The page form is quite simple, but I am using UpdatePanel on it (is it important?).
When the page is loaded and first save request was done OK, I can click the button as fast as I can and everything is OK (no exception).
XmlSerialization creates new dll's on the fly which are specific to the class you're trying to serialise in the temp directory. These are created to increase performance.
See http://msdn.microsoft.com/en-us/library/swxzdhc0.aspx
Instead of calling the GC.Collect etc... try creating the serializer as a static field on your class. This should improve performance and might solve your problem as it's only ever going to be created once.
This code will create a single xmlserializer in a thread safe way. Do NOT add a [ThreadStatic] attribute to this as this will ensure the code gets executed once per thread and make it thread unsafe again!
private static readonly XmlSerializer xmlSerializer =
new XmlSerializer(typeof(FormData));
I had similar problem and I hope this will help you too.
The problem was that garbage collector didn't clean up before your second click, so you should try to call it manually. Try to call GC before living using
GC.Collect();
GC.WaitForPendingFinalizers();
Does anyone know of a way to (reasonably simple) create a file without actually opening/locking it? In File class, the methods for file creation always return a FileStream. What I want to do is to create a file, rename it (with File.Move) and then use it.
Now I have to:
Create it
Close
Rename
Reopen for use
Maybe you can try using File.WriteAllText Method (String, String)
with the file name and an empty string.
Creates a new file, writes the
specified string to the file, and then
closes the file. If the target file
already exists, it is overwritten.
using (File.Create(...)) { }
While this will briefly open your file (but close it again right away), the code should look quite unobtrusive.
Even if you did some P/Invoke call to a Win32 API function, you would get a file handle. I don't think there's a way to silently create a file without having it open right afterwards.
I think the real issue here is why you go about creating your file in the way you've planned. Creating a file in one place simply to move it to another location doesn't seem very efficient. Is there a particular reason for it?
What about using File.WriteAllBytes method?
// Summary:
// Creates a new file, writes the specified byte array to the file, and then
// closes the file. If the target file already exists, it is overwritten.
Another way is to use FileStream and Close it after creating the file. It will not lock the file. The code will look like:
FileStream fs = new FileStream(filePath, FileMode.Create);
fs.Flush(true);
fs.Close();
You just after this you can rename it as well or move it some other location.
Below is the Test program to test functionality.
using System;
using System.Collections.Generic;
using System.IO; using
System.Linq;
using System.Text;
namespace FileLocking {
class Program
{
static void Main(string[] args)
{
string str = #"C:\Test\TestFileLocking.Processing";
FileIOTest obj = new FileIOTest();
obj.CreateFile(str);
}
}
class FileIOTest
{
internal void CreateFile(string filePath)
{
try
{
//File.Create(filePath);
FileStream fs = new FileStream(filePath, FileMode.Create);
fs.Flush(true);
fs.Close();
TryToAccessFile(filePath);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void TryToAccessFile(string filePath)
{
try
{
string newFile = Path.ChangeExtension(filePath, ".locked");
File.Move(filePath, newFile);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
} }
If you use File.Create(commented in above code) then it will give error saying file is being used by another process.
Incredibly grotty hack, probably the most complicated way to achieve your goal:
use Process class
processInfo = new ProcessStartInfo("cmd.exe", "/C " + Command);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
process = process.Start(processInfo);
process.WaitForExit();
where Command would be echo 2>> yourfile.txt