I'm trying to delete .xlsx or .pdf files after using them. When files are created I display them, but then users want automatic file deletion after closing them.
I've tried couple of things, but none of them seem to work properly. Issue:
When opened multiple files (.xlsx or .pdf) I can't terminate a single process, like just a single file. Instead what happens is that file get's deleted only when I close all same processes (Excel or PDF files). As I investigated this happens because Excel or PDF works as one instance only. However code works as expected when I have only one file opened...
This is what I have so far:
var process= Process.Start(file_path); //file_path is global variable
Set_event(process);
private void Set_event(Process process)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(Delete_File);
}
public void Delete_File(object sender, EventArgs e)
{
//Delete file on close
File.Delete(file_path);
}
I've also tried with DeleteOnClose method of FileOptions, but unfortunally that doesn't display file to user and doesn't quite delete file immediately after using them, only after my win app is closed. That isn't my desired output, but at least files are deleted, so If I could fix that I would be partially satisfied too. Here is my line for that:
var open_file = new FileStream(file_path,FileMode.Open, FileAccess.ReadWrite,FileShare.ReadWrite, 512, FileOptions.DeleteOnClose);
With all that said, are there any other options I missed ? Thanks for help in advance.
I've tried almost everything I could find (different variations of Exited_Event for Process, monitoring with FileSystemWatcher, creating files with DeleteOnClose - even API), but none of them worked as expected.
Everything ends or fails with issue I described in first place - some apps, like Microsoft Excel or Adobe Acrobat uses one instance to open a file (.pdf or .xls/.xlsx), so you can't just reference a single file as object while you have opened more files. That means you either end up with an error when trying to assign Exited_event to single file, or no error but file gets deleted only when you close all files with same type...
BUT fortunate enough I figured out one thing: WHEN you have opened more than one file in question (.pdf or .xlsx) something happens in background of OS: If you loop through processes of same type at that time, you'll get a list of particular instance that is in use.
In other words, while you have 2 Excel files opened, loop through processes is showing you only a file which is currently active for "EXCEL" process.
So, that leaded me to a completely new approach that might solve this issue. In order to have a complete solution for this you have to:
1. Create a method to check whether file is no longer in use.
2. Set a Timer with a delay of 2 seconds, to make sure process really ends. Maybe this should be incremented for different purposes...
3. Set a Timer_tick event, where you loop processes to see whether particular file is listed as active, and If user has already closed this file. As described by other users this method isn't quite accurate, but with setting delay for Timer I think there shouldn't be any problems anymore.
Here is a complete code for this (for .pdf and .xlsx - that is what I needed):
//as global variable
System.Windows.Forms.Timer delete_file = new System.Windows.Forms.Timer();
Process.Start(file_path); //file_path is global variable
delete_file.Tick += new EventHandler(timer_Tick);
delete_file.Interval = (2000);
delete_file.Enabled = true;
delete_file.Start();
private void timer_Tick(object sender, EventArgs e)
{
Boolean file_is_opened = false;
// Loop processes and list active files in use
foreach (var process in Process.GetProcesses())
{
if (process.MainWindowTitle.Contains(Path.GetFileName(file_path)))
{
file_is_opened = true;
}
}
//If our file is not listed under active processes we check
//whether user has already closed file - If so, we finally delete It
if (file_is_opened==false)
{
if (!File_In_Use(new FileInfo(file_path)))
{
File.Delete(file_path);
delete_file.Enabled = false;
delete_file.Stop();
return;
}
}
}
private bool File_In_Use(FileInfo file)
{
//Method to check whether file is in use
FileStream stream = null;
try
{
//If file doesn't exist
if (!file.Exists)
{
return false;
}
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//File is unavailable:
//because someone writes to It, or It's being processed
return true;
}
finally
{
if (stream!=null)
{
stream.Close();
}
}
//File not locked
return false;
}
This is how I did It. It might not be a perfect solution, but that works for me on Win 10 with no errors so far.
If someone has a suggestion to fix upper code, please let me know. Otherwise I hope this will help someone in future as I noticed there were already some questions about this in past, with no proper answer.
Related
I'm making a program that controls a game server. One of the functions I'm making, is a live server logfile monitor.
There is a logfile (a simple textfile) that gets updated by the server as it runs.
How do I continuously check the logfile and output it's content in a RichTextBox?
I did this simple function just try and get the content of the log. It will of course just get the text row by row and output it to my textbox. Also it will lock the program for as long as the loop runs, so I know it's useless.
public void ReadLog()
{
using (StreamReader reader = new StreamReader("server.log"))
{
String line;
// Read and display lines from the file until the end of the file is reached.
while ((line = reader.ReadLine()) != null)
{
monitorTextBox.AppendText(line + "\n");
CursorDown();
}
}
}
But how would you go about solving the live monitoring as simple as possible?
*** EDIT ***
I'm using Prescots solution. great stuff.
At the moment I'm using a sstreamreader to put the text from the file to my textbox. I ran into the problem is that, whenever I tried to access any of the gui controls in my event handler the program just stopped with no error or warnings.
I found out that it has to do with threading. I solved that like this:
private void OnChanged(object source, FileSystemEventArgs e)
{
if (monitorTextField.InvokeRequired)
{
monitorTextField.Invoke((MethodInvoker)delegate { OnChanged(source, e); });
}
else
{
StreamReader reader = new StreamReader("file.txt");
monitorTextField.Text = "";
monitorTextField.Text = reader.ReadToEnd();
reader.Close();
CursorDown();
}
}
Now my only problem is that the file.txt is used by the server so I can't access it, since it's "being used by another process". I can't control that process, so maybe I'm out of luck.
But the file can be opened in notepad while the server is running, so somehow it must be possible. Perhaps I can do a temp copy of the file when it updates and read the copy. I don't know.
Check out the System.IO.FileSystemWatcher class:
public static Watch()
{
var watch = new FileSystemWatcher();
watch.Path = #"D:\tmp";
watch.Filter = "file.txt";
watch.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite; //more options
watch.Changed += new FileSystemEventHandler(OnChanged);
watch.EnableRaisingEvents = true;
}
/// Functions:
private static void OnChanged(object source, FileSystemEventArgs e)
{
if(e.FullPath == #"D:\tmp\file.txt")
{
// do stuff
}
}
Edit: if you know some details about the file, you could handle the most efficent way to get the last line. For example, maybe when you read the file, you can wipe out what you've read, so next time it's updated, you just grab whatever is there and output. Perhaps you know one line is added at a time, then your code can immediately jump to the last line of the file. Etc.
Although the FileSystemWatcher is the most simple solution I have found it to be unreliable in reality.. often a file can be updated with new contents but the FileSystemWatcher does not fire an event until seconds later and often never.
The only reliable way I have found to approach this is to check for changes to the file on a regular basis using a System.Timers.Timer object and checking the file size.
I have written a small class that demonstrates this available here:
https://gist.github.com/ant-fx/989dd86a1ace38a9ac58
Example Usage
var monitor = new LogFileMonitor("c:\temp\app.log", "\r\n");
monitor.OnLine += (s, e) =>
{
// WARNING.. this will be a different thread...
Console.WriteLine(e.Line);
};
monitor.Start();
The only real disadvantage here (apart from a slight performance delay caused by file size checking) is that because it uses a System.Timers.Timer the callback comes from a different thread.
If you are using a Windows Forms or WPF app you could easily modify the class to accept a SynchronizingObject which would ensure the event handler events are called from the same thread.
As #Prescott suggested, use a FileSystemWatcher. And make sure, you open the file with the appropriate FileShare mode (FileShare.ReadWrite seems to be appropriate), since the file might still be opened by the server. If you try to open the file exclusively while it is still used by another process, the open operation will fail.
Also in order to gain a bit of performance, you could remember the last position up to which you already have read the file and only read the new parts.
Use this answer on another post c# continuously read file.
This one is quite efficient, and it checks once per second if the file size has changed.
You can either run it on another thread (or convert to async code), but in any case you would need to marshall the text back to the main thread to append to the textbox.
Try adding a Timer and have the Timer.Tick set to an Interval of 1 second. On Timer.Tick you run the function.
private void myTimer_Tick(object sender, EventArgs e)
{
ReadLog();
}
In our C# WPF application we have some FileViewers (PDF-viewer, Image-viewer, and such) for files we've added to our WebDAV fileStore. When the file has an extension we can't open with one of our FileViewers and it also isn't a potential harmful file (like .exe or .bat) we use a Process.Start to open them.
Here is the code for this:
public void OpenFileViewer()
{
if (OurFile == null || (String.IsNullOrWhiteSpace(OurFile.FilePath))) return;
try
{
if (File.Exists(OurFile.FilePath))
{
// Local file not present in WebDAV yet
Open(OurFile.FilePath);
return;
}
using (var tempFiles = new TempFileCollection())
{
var file = tempFiles.AddExtension(OurFile.FileExtension.Trim('.'));
using (var fs = File.OpenWrite(file))
{
var content = _fileStore.LoadFile(OurFile.FilePath);
fs.Write(content, 0, content.Length);
}
Open(file);
}
}
catch (Exception e)
{
_windowManager.ShowWindow(new ExceptionViewModel(e));
}
}
private void Open(string file)
{
if (!Path.HasExtension(file)) return;
var fileExtension = Path.GetExtension(file);
if (BlacklistExtensions.Contains(fileExtension)) return;
if (ViewerExtensions.Contains(fileExtension))
{
_windowManager.ShowWindow(new FileViewModel(file));
}
else
{
Process.Start(file);
}
}
The problem that is currently occurring: The TempFileCollection using is already closed before the Process.Start is complete. If I would debug the code, the Process.Start works just fine, because the TempFileCollection using isn't closed yet. If I don't debug through it however, the Process.Start has not enough time and the using is already closed.
So, how can I keep the using open as long as the Process.Start isn't complete yet. Also keep in mind that the Process.Start can fail when the file is already in use or we don't have permission to open it or something similar. In these cases, the using should still close eventually.
I know I could just add a small Thread.Sleep, but that is always an ugly and wrong solution for these kind of problems.
I've also heard about EnableRaisingEvents for the Process, but I don't really know how I can implement this to close the using at the proper moment, without having it stay open if something went wrong when opening the file. I also can't add a while(processNotStartedYet), because the user should still be able to use the application while the Process.Start is "running" (most of the time this only takes ms, but we don't want to get stuck in a while in case something went wrong).
Use Process.WaitForInputIdle() after Process.Start, which waits for the process to enter an idle state.
Process fileViewerProc = Process.Start(file);
fileViewerProc.WaitForInputIdle();
My goal is to force a process to run for at least 5 seconds (any amount of time really). I am working of the .NET Framework 3.5, with Service Pack 1. The idea is that the document holds some information that the user must see, so to safe guard against them immediately clicking to close the document, we can force it to stay open for some time. I developed a small test UI, consisting of a button, and then three radio buttons (one for each document). Here is my code behind...
I define the strings for the file paths, the string for the chosen file's path, and int to store the process's ID, a boolean for if they can exit the program, and the thread and timer declarations such as..
string WordDocPath = #"Some file path\TestDoc_1.docx";
string PowerPointPath = #"Some file path\Test PowerPoint.pptx";
string TextFilePath = #"Some file path\TestText.txt";
string processPath;
int processID;
bool canExit = false;
System.Threading.Thread processThread;
System.Timers.Timer processTimer;
In the constructor, I initialize the thread and timer, setting the thread's start method to a method called TimerKeeper(), and then I start the thread.
processTimer = new System.Timers.Timer();
processThread = new System.Threading.Thread(new System.Threading.ThreadStart(timeKeeper));
processThread.Start();
I have the timer set to count to 5 seconds, upon which it will set the canExit boolean to true.
public void timeKeeper()
{
processTimer.Elapsed += new System.Timers.ElapsedEventHandler(processTimer_Elapsed);
processTimer.AutoReset = false;
processTimer.Interval = 5000; //5000 milliseconds = 5 seconds
}
void processTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
canExit = true;
}
The rest is my button's click event, which deicides which file path to use to start the process, starts the timer, and then starts the process itself..
private void button1_Click(object sender, RoutedEventArgs e)
{
if ((bool)PowerPointRadioButton.IsChecked)
{
processPath = PowerPointPath;
}
if ((bool)WordDocRadioButton.IsChecked)
{
processPath = WordDocPath;
}
if ((bool)TextDocRadioButton.IsChecked)
{
processPath = TextFilePath;
}
try
{
canExit = false;
processTimer.Start();
while (!canExit)
{
processID = System.Diagnostics.Process.Start(processPath).Id;
System.Diagnostics.Process.GetProcessById(processID).WaitForExit();
if (!canExit)
{
processTimer.Stop();
MessageBox.Show("Document must remain open for at least 5 seconds.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
processTimer.Start();
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error dealing with the process.\n" + ex.Message.ToString());
}
This actually works, for the most part. The user still can close the document, but if it has not been 5 seconds, it will reopen. Except for the word document (.docx). Things go smoothly for the powerpoint and text files, but the word document has some strange behavior (please note that all 3 files are in the same file directory). When I choose the word documents radio button and click the button, the word document opens, BUT I am also prompted with the message box from the catch block, alerting me that a "Object reference not set to an instance on an object" exception was thrown. This only occurs for the word document. Like I said, the word document still opens (I can see it's contents, just like the powerpoint or textfile). The exception causes the lines that check to see if they can exit to be skipped, so the document can close immediately, which is a problem.
Can anyone see my issue here? Or if there is a better way to doing all of this (I am a wpf/c# newbie)? I just don't understand why this only occurs for the word document, and not the powerpoint and text files.
If this is run on the user's desktop you are subject to the proper app being installed (e.g. Word) and how it is configured. If these are read only files on a share then I could convert them to XPS so you could show them in a DocumentViewer. And rather than force them to wait 5 seconds to click make them say yes to a dialog box that they have read and understand the document. Or have this on a page with an "I agree" button as MilkyWayJoe suggested.
The problem could be that the associated application is not the word application itself, but some intermediate application that launches word on your behalf.
To find out, keep a reference to the process object, and check if it has already terminated, what it's executable path is.
Having said that, why do you need this annoying behavior? You cant stop people from looking the other way. Is it supossed to fullfill some legal requirement or something?
In our C# WinForms application, we generate PDF files and launch Adobe Reader (or whatever the default system .pdf handler is) via the Process class. Since our PDF files can be large (approx 200K), we handle the Exited event to then clean up the temp file afterwards.
The system works as required when a file is opened and then closed again. However, when a second file is opened (before closing Adobe Reader) the second process immediately exits (since Reader is now using it's MDI powers) and in our Exited handler our File.Delete call should fail because it's locked by the now joined Adobe process. However, in Reader we instead get:
There was an error opening this document. This file cannot be found.
The unusual thing is that if I put a debugger breakpoint before the file deletion and allow it to attempt (and fail) the deletion, then the system behaves as expected!
I'm positive that the file exists and fairly positive that all handles/file streams to the file are closed before starting the process.
We are launching with the following code:
// Open the file for viewing/printing (if the default program supports it)
var pdfProcess = new Process();
pdfProcess.StartInfo.FileName = tempFileName;
if (pdfProcess.StartInfo.Verbs.Contains("open", StringComparer.InvariantCultureIgnoreCase))
{
var verb = pdfProcess.StartInfo.Verbs.First(v => v.Equals("open", StringComparison.InvariantCultureIgnoreCase));
pdfProcess.StartInfo.Verb = verb;
}
pdfProcess.StartInfo.Arguments = "/N"; // Specifies a new window will be used! (But not definitely...)
pdfProcess.SynchronizingObject = this;
pdfProcess.EnableRaisingEvents = true;
pdfProcess.Exited += new EventHandler(pdfProcess_Exited);
_pdfProcessDictionary.Add(pdfProcess, tempFileName);
pdfProcess.Start();
Note: We are using the _pdfProcessDictionary to store references to the Process objects so that they stay in scope so that Exited event can successfully be raised.
Our cleanup/exited event is:
void pdfProcess_Exited(object sender, EventArgs e)
{
Debug.Assert(!InvokeRequired);
var p = sender as Process;
try
{
if (_pdfProcessDictionary.ContainsKey(p))
{
var tempFileName = _pdfProcessDictionary[p];
if (File.Exists(tempFileName)) // How else can I check if I can delete it!!??
{
// NOTE: Will fail if the Adobe Reader application instance has been re-used!
File.Delete(tempFileName);
_pdfProcessDictionary.Remove(p);
}
CleanOtherFiles(); // This function will clean up files for any other previously exited processes in our dictionary
}
}
catch (IOException ex)
{
// Just swallow it up, we will deal with trying to delete it at another point
}
}
Possible solutions:
Detect that the file is still open in another process
Detect that the second process hasn't really been fully exited and that the file is opened in the first process instead
I just dealt with this a couple of days ago.
When there is no instance open already, the document opens in a new instance directly.
When there is an instance already open, I believe that instance spawns a new instance which you don't actually get a handle to. What happens is control returns to your function immediately, which then goes and deletes the file before the new instance has had a chance to read the file -- hence it appears to not be there.
I "solved" this by not deleting the files immediately, but keeping track of the paths in a list, and then nuking all of them when the program exits (wrap each delete in a try/catch with an empty catch block in case the file has disappeared in the meantime).
I would suggest following approach:
Create files in user's temp directory (Path.GetTempPath). You can create some sub-folder under it.
Attempt to delete files only when last instance of process gets exited (i.e. you need to count number of processes that you had launched, on exit, decrement the count and when it becomes zero, attempt to delete (all) files that are open so far)
Try to clean-up created sub-folder (under temp directory) while starting and ending the application. You can even attempt for periodic clean-up using timer.
I have an application that uses MSWord automation to edit some documents, after they save and close word I need to grab the modified file and put it back on the repository, there is only one scenario where I can't get it to work and that is
when the user makes changes to the file, selects to close word and selects yes to save the file
there are 2 events that I'm using:
DocumentBeforeSave
Quit
on the Quit event I'm trying to load the .docx file from disk but on this particular scenario I get an IOException because the file is still in use, somehow I need to wait until after the Quit event has been processed, which is when Word is actually closed and the file is no longer being used
right now I have it working using this
word.Visible = true;
while (!wordDone) { //gets changed to true on the Quit event
System.Threading.Thread.Sleep(100);
}
bool error = false;
do {
try { //need to load the contents of the modified file
ls.Content = System.IO.File.ReadAllBytes(provider.GetFileName());
error = false;
}
catch (System.IO.IOException) {
error = true;
System.Threading.Thread.Sleep(200);
}
} while (error);
while this works it is very ugly, I need a way to fire an event after the Quit event has been handled, or block the current thread while word is still running, or get an event after the document has been saved, the bottom line is I need a clean way to load the file after it has been saved and word is closed. DocumentAfterSave would be awesome, but doesn't seem to exist.
I Also tried unhooking the Quit handler and calling word.Quit on the Quit handler, that made no difference
I'm also investigating the use of ManualResetEvent or related classes, so far it almost works, but I still need to pause after it has been signaled to make sure word is closed and the file is no longer in use
I faced similar problem in the past as well. I dont think there is any nice clean way but instead of doing it like your above, how about considering this (will suit if you have a controlled environment)
Create word app
Get the Process ID immediately by using GetProcesses matching Winword and the last one in the list return should be the one you are after. This is not 100% reliable in multiuser environment.
After word quit, use the Thread.Sleep loop to ensure the PID no longer exist.
Reading the docx for your custom operations
I used to have the same problem. Using ReleaseComObject on all COM-related objects did the trick (that is, on your Word document object and your Word.Application object). That way you ensure that all dirty locks are removed after the COM object has been destroyed. Close the document and application with the Interop API. I use:
var localWordapp = new Word.Application();
localWordapp.Visible = false;
Word.Document doc = null;
// ...
if (doc != null)
{
doc.Close();
System.Runtime.InteropServices.Marshal.ReleaseComObject(doc);
}
localWordapp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(localWordapp);