0

I'm writing a windows forms application i C#, where I'll need to open a document/file (.doc, .pdf, .xlsx, .tiff etc.) to the user in an associated program installed on the client pc.

The file should be deleted as soon the user closes the displaying program.

I've tried several options for creating and opening the file, but haven't found the golden egg yet.

public static void databaseFileRead(string file_name, byte[] file)
{
    path = file_name;
    int file_size_int = file.Length;
    FileStream create = new FileStream(@path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, file_size_int, FileOptions.DeleteOnClose);
    create.Write(file, 0, file_size_int);
    FileStream open = File.Open(@path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
}

In the above method I'm getting an IOException stating that "The process cannot access the file 'xxx' because it is being used by another process" on last line (FileStream open = ...).

public static void databaseFileRead(string file_name, byte[] file)
{
    path = file_name;
    int file_size_int = file.Length;
    FileStream create = File.OpenWrite(@path);
    var attributes = File.GetAttributes(@path);
    File.SetAttributes(@path, attributes | FileAttributes.ReadOnly|FileAttributes.Temporary);
    create.Close();
    Process p = Process.Start(@path);
    p.WaitForExit();
    File.Delete(@path);
}

And in this method I'm also getting an IOException stating that "The process cannot access the file 'xxx' because it is being used by another process" on last line (File.Delete(@path);) meaning that the file is still in use, which is correctly. It seems that p.WaitForExit(); is not waiting for all programs e.g. OpenOffice...

Is it possible to open/display a file created with FileOptions.DeleteOnClose in an external program? If so, how?

I do like the idea, that Windows is deleting the file as soon that the file isn't used anymore.

It is important that the files disappear from the users hard drive automatically, the best option, would be to read and open the file from a stream or equal. But as far that I have read, this is not possible...

SOLVED:

However I'm not sure if it's a prober way to catch the exception and call the closeIfReady method again until the file is released...

   public static void databaseFileRead(string file_name, byte[] file)
    {
        var path = file_name;
        int file_size_int = file.Length;
        if (File.Exists(path))
        {
            File.Delete(path);
        }
        FileStream create = File.OpenWrite(path);
        create.Write(file, 0, file_size_int);
        var attributes = File.GetAttributes(path);
        File.SetAttributes(path, attributes | FileAttributes.Temporary);
        create.Close();
        Process p = Process.Start(path);

        while (!p.HasExited)
        {
            Thread.Sleep(500);
        }
        closeIfReady(path);
    }

    static void closeIfReady(string path)
    {
        try
        { File.Delete(@path); }
        catch
        {
            Thread.Sleep(1000);
            closeIfReady(path);
        }
    }

2 Answers2

3

OK, after the commenting, here's a working 2nd method:

void Method2(string file_name, byte[] file)
{
    var path = file_name;
    int file_size_int = file.Length;
    if (File.Exists(path))
    {
        File.Delete(path);
    }
    FileStream create = File.OpenWrite(path);
    var attributes = File.GetAttributes(path);
    File.SetAttributes(path, attributes | FileAttributes.Temporary);
    create.Close();
    Process p = Process.Start(path);

    while (!p.HasExited)  
    {
        Thread.Sleep(100);  
    }

    File.Delete(path);
}

You don't strictly need the if File.Exists/File.Delete at the start, I just needed it for debugging in mine - probably a good idea to leave it in there however, since if someone exits the application before the file gets removed it will try and create it again.

the problem line in this method however was: File.SetAttributes(@path, attributes | FileAttributes.ReadOnly|FileAttributes.Temporary); Because the file was created as read-only, you can't remove it with File.Delete. In windows explorer it doesn't care about this if you have admin, which is why you can remove it from there, but C# does care about file attributes. I changed that line to File.SetAttributes(@path, attributes | FileAttributes.Temporary); and it now works.

This question is the reference I used for your p.WaitForExit problem. I would suggest starting this on a different thread in your application since otherwise your app will hang while it's checking the thread has exited (turns white/grey in windows and tells you it's not responding). There are things you can do like invalidating visuals if you're using wpf, or redrawing your GUI so you can still use it but those are hacky and can consume a lot of CPU.

Community
  • 1
  • 1
uNople
  • 554
  • 5
  • 10
  • If I do that in the first method, the file is deleted right away before it is opened, because of the `FileOption` – Claus Hjort Bube Mar 20 '13 at 00:52
  • In your first method then, put `FileOptions.DeleteOnClose` in the FileStream after the `create.Close()`, remove the DeleteOnClose from the first FileStream. That way when you close the process it should remove the file. – uNople Mar 20 '13 at 01:07
  • The `File.Open` don't take `FileOption` as a parameter, and I can't see how I can display the file using `new FileStream`. – Claus Hjort Bube Mar 20 '13 at 07:11
  • I've edited my answer with a working example. I had to set path inside the method myself since it wasn't a global variable. – uNople Mar 20 '13 at 20:21
  • Thanks, but it seems like `p.HasExited` have the same problem as `p.WaitForExit`, is there some way to check if the file is locked? – Claus Hjort Bube Mar 20 '13 at 21:09
  • There's a few ways to do this. You could check to see if the specific process name is running still, and if not, exit it. You will get a problem if say you're checking for a PDF reader and you have more than one open. You could check a specific PID is running, however if you launch an app that then launches another app to do its work you would end up getting an incorrect result. You could try deleting the file, and if it doesn't work, just keep retrying on a delay. Once the file lock is cleared your program can then remove it, so a while loop that sleeps with a try/catched delete in there. – uNople Mar 20 '13 at 22:00
  • Thanks a lot, though I didn't see your last comment before I tried using a try/catch-block. – Claus Hjort Bube Mar 20 '13 at 22:28
0

A couple of things.

First, if this is running in the main UI thread of a winforms app, your application will become non responsive using either solution.

Second, both solutions depend on a graceful exit from several different applications. This is risky.

If the external process fails to properly clean up the file handle, your solution would cause infinite recursion. I also would be uncomfortable depending on Process.HasExited as I have seen many threads regarding the reliability of this property.

I would suggest using a temporary directory relative to your application, and cleaning up that directory on both application exit and start (just in case your own app fails to exit gracefully).

gravidThoughts
  • 613
  • 5
  • 18