using TempFileCollection closes before Process.Start finished opening - c#

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();

Related

Delete .xlsx or .pdf after closing file

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.

Many threads, one method fails

My application is System Tray Application, using C#, .NET 4.0.
I'm trying to display many PDF files at a time and each PDF should split screen with other window, that I determine by ProcessName, that's all.
The difficulty is that I need to wait until user closes that window (.WaitForExit() method), because later I'm deleting PDF file. And here problem comes.
In first thread everything goes fine but the problem is when i try to show second PDF file window processList[0] THOUGH MoveWindow function returns true, and handle to that window is also correct only pdf window is resizing, the other window fails.
In main method the only thread that I'm creating (I call this piece of code couple of times, everytime user want to see pdf file):
Thread pdfThread = new Thread(() => ShowPdfFile(fullPath));
pdfThread.Start();
Then (simplified code)
public static void ShowPdfFile(string fileName)
{
try
{
Process pdfProcess = Process.Start(fileName);
Thread.Sleep(500);
string windowTitle = GetActiveWindowTitle();
IntPtr pdfHandle = (IntPtr)FindWindow(null, windowTitle);
MoveWindow(pdfHandle, 0, 0, 0, 0, true);
Process[] processList = Process.GetProcessesByName("someProcess");
MoveWindow(processList[0].MainWindowHandle, 0, 0, 0, 0, true);
pdfProcess.WaitForExit();
MoveWindow(processList[0].MainWindowHandle, 0, 0, max, max, true);
}
catch (Exception ex)
{
LogToFile(ex);
}
finally
{
try
{
File.Delete(fileName);
}
catch
{
LogToFile("Cannot delete file");
}
}
UPDATE: Well, I was debugging it whole day but just now i note that on second thread it doesn't wait on line pdfProcess.WaitForExit();
What should I change to force thread to wait for that exact pdfProcess exit?
There is a basic problem to your usage:
You start the external PDF application for each file. However this doesn't ensure that you have more than one process.
For example Acrobat reader only starts a single process. Additional files will just be "added" as new windows to the first process (You can check this by trying to manually open Acrobat reader twice --> won't work).
So in short: If you cannot control which PDF reader is used (and can ensure that you have a single process for EACH file) your approach will not work.
Note: Theoretically it would be possible to wait until the user closes the "reader window" that contains the specific file. However I strongly advise against this:
Looking up a window in a different process is very error prone (unless the process is explicitly designed in such a way...)
The solution would again depend on the reader application (you cannot be sure that Acrobat and Nitro use similar architecture to just name two readers)
Principally, if this feature is very important, you should consider to buy a PDF viewer component that allows to show the PDF's as windows of your own process.
Edit
The reason that the second thread isn't waiting is that the functionality to "add" a file to the first process uses a temporary process:
Temporary process is started for new file
Temporary process checks if the application is already running
Temporary process notifies the first process to open the specific file
Temporary process shuts down.
So waiting for that process will return almost immediately since the process in question already has stopped (or will stop in just a couple of milliseconds).
Daniel is right, however I found a solution! Here, maybe someone will use it.
Small comment: We must use winapi solutions instead of .WaitForExit().
The most important part is while loop, that waits for close pdf window.
Remember that FindWindow() and IsWindow() methods are from user32.dll (winapi).
This code run process based on it's paths, then gets it's handle and wait for it's closure.
Process pdfProcess = new Process();
pdfProcess.StartInfo.FileName = filePath;
if (pdfProcess.Start())
{
Thread.Sleep(500);
Process[] processlist = Process.GetProcesses();
string windowTitle = string.Empty;
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle) && process.MainWindowTitle.Contains(fileName))
{
windowTitle = process.MainWindowTitle;
}
}
IntPtr pdfHandle = FindWindow(null, windowTitle);
while (IsWindow(pdfHandle) && userExitedApp == false)
Thread.Sleep(100);
}
Multiple threads could utilize one method one after the other. Perhaps this video will help
https://www.youtube.com/watch?v=XhGXh9Z5GTw&feature=em-upload_owner

detecting that a file is currently being written to

(I know It's a common problem but I couldn't find an exact answer)
I need to write a windows service that monitors a directory, and upon the arrival of a file, opens it, parses the text, does something with it and moves it to another directory afterwards. I used IsFileLocked method mentioned in this post to find out if a file is still been written. My problem is that I don't know how much it takes for another party to complete writing into the file. I could wait a few seconds before opening the file but this is not a perfect solution since I don't know in which rate is the file written to and a few seconds may not suffice.
here's my code:
while (true)
{
var d = new DirectoryInfo(path);
var files = d.GetFiles("*.txt").OrderBy(f => f);
foreach (var file in files)
{
if (!IsFileLocked(file))
{
//process file
}
else
{
//???
}
}
}
I think you might use a FileSystemWatcher (more info about it here: http://msdn.microsoft.com/it-it/library/system.io.filesystemwatcher(v=vs.110).aspx ).
Specificially you could hook to the OnChanged event and after it raises you can check IsFileLocked to verify if it's still being written or not.
This strategy should avoid you to actively wait through polling.

How do I delete a file that was opened by xmlreader in c#?

I've researched several questions but none of the answers I found has helped. The goal of this function is to modify an xml file. I read the original file and write the old stuff and the new stuff to the new file. All of this works perfectly. The problem arises when I'm done and need to delete the old file and move the new one.
The error being received is that the jnv_config.xml is in use by another process (the reader file.)
Removing the Close and/or Dispose does not solve the problem.
using (XmlReader reader = XmlReader.Create("jnv_config.xml"))
using (XmlWriter writer = XmlWriter.Create("jnv_temp.xml"))
{
writer.WriteStartDocument();
while (reader.Read())
{
// Read the file, write to the other file - this part works perfectly.
// No filestreams nor anything else is created in here.
}
writer.WriteEndElement();
writer.WriteEndDocument();
reader.Close();
writer.Close();
reader.Dispose();
writer.Dispose();
}
// Delete the old file and copy the new one
File.Delete("jnv_config.xml");
//File.Move("jnv_temp.xml", "jnv_config.xml");
I'm using VS2012 (NET 4.5), C#, Standard Windows Forms project.
Are you sure that it's this XmlReader that still has the file open? Have you tried using Process Explorer to confirm that there are no open file handles for the config file before this code executes?
Check if the file is ready before you delete it. If your working with large files perhaps call code via a loop for a couple of seconds.
private void IsFileOpen(FileInfo file)
{
FileStream stream = null;
try {
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (Exception ex) {
if (ex is IOException && IsFileLocked(ex)) {
// do something here, either close the file if you have a handle or as a last resort terminate the process - which could cause corruption and lose data
}
}
}
private static bool IsFileLocked(Exception exception)
{
int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
return errorCode == 32 || errorCode == 33;
}
It has been my experience that many NTFS file-handling functions (especially DELETEs) are slightly asynchronous. Trying adding a Sleep or Wait for at least 0.2 sec before the RENAME.
As that did not work, I would instead suggest putting the Sleep/Wait before and then slowly increasing it until it works. If you get to some unreasonably large time span (like say 10 seconds) and it still does not work, then I think that you can fairly conclude that the problem is that you XmlReader is not being released as long as you stay in this code.
In which case you may need to do something to insure that it is getting Disposed completely, like forcing GC to run.

I don't want to use Thread.Sleep with SHDocVw.ShellWindows

I have a program where I have to get a SHDocVw.InternetExplorer instance from a running IE8 process. To get the instance the example code below runs. For some reason it won't work without the Thread.Sleep.
The Browser.HWND throws InvalidCastException for all instances in the m_IEFoundBrowsers if the Thread.Sleep is removed. When using the Thread.Sleep it works for the IE8 windows.
Does anyone know how to do this in a way not using Thread.Sleep? (I don't like to use the sleep function, usually it just pushes the problems into the future...)
Example code:
InternetExplorer m_IEBrowser = null;
ShellWindows m_IEFoundBrowsers = new ShellWindowsClass();
Thread.Sleep(10);
foreach (InternetExplorer Browser in m_IEFoundBrowsers)
{
try
{
if (Browser.HWND == (int)m_Proc.MainWindowHandle)
{
m_IEBrowser = Browser;
break;
}
}
catch(InvalidCastException ice)
{
//Do nothing. Browser.HWND could not execute for this item.
}
}
I came across the following link which seems to back up Hans's comment: http://weblogs.asp.net/joberg/archive/2005/05/03/405283.aspx
The article states:
The Internet Controls Library contains
the “ShellWindowsClass” which is
basically a collection of all the
shell windows (e.g.: IE) spawned
across the desktop. That component
provides an event handler called
“Windows Registered” that we are going
to hook up to. Once the process has
been launched, we will wait until the
corresponding window is registered
then we are going to connect our
Internet Explorer control to the shell
window found. To determine if the
window is found, we iterate through
the registered windows and we try to
find a handle that matches the handle
of the process we previously launched.
We will use the “ManualResetEvent”
synchronization primitive to wait a
certain amount of time for the window
to be registered.
I expect you'd be able to map these ideas across to your problem relatively easily.
The article posted by David solved the problem. The first time the code runs in my program it works as described in the article. But if I exit the program, leave the opened IE8 open, open my program again then the the windows_WindowRegistered method got problems with InvalidCastExceptions. Handling these exceptions as shown below made it work as needed.
EXAMPLE CODE:
private void windows_WindowRegistered(int lCookie)
{
if (process == null)
return; // This wasn't our window for sure
for (int i = 0; i < windows.Count; i++)
{
try
{
InternetExplorerLibrary.InternetExplorer ShellWindow = windows.Item(i) as InternetExplorerLibrary.InternetExplorer;
if (ShellWindow != null)
{
IntPtr tmpHWND = (IntPtr)ShellWindow.HWND;
if (tmpHWND == process.MainWindowHandle)
{
IE = ShellWindow;
waitForRegister.Set(); // Signal the constructor that it is safe to go on now.
return;
}
}
}
catch (InvalidCastException ice)
{
//Do nothing. Browser.HWND could not execute for this item.
}
}
}

Categories

Resources