2

I'm writing a program that uses FileSystemWatcher to monitor changes to a given directory, and when it receives OnCreated or OnChanged event, it copies those created/changed files to a specified directories. At first I had problems with the fact that OnChanged/OnCreated events can be sent twice (not acceptable in case it needed to process 500MB file) but I made a way around this and with what I'm REALLY BLOCKED with is getting the following IOException: The process cannot access the file 'C:\Where are Photos\bookmarks (11).html' because it is being used by another process.

Thus, preventing the program from copying all the files it should. So as I mentioned, when user uses this program he/she specifes monitored directory, when user copies/creates/changes file in that directory, program should get OnCreated/OnChanged event and then copy that file to few other directories. Above error happens in all cases, if user copies few files that needs to overwrite other ones in folder being monitored or when copying bulk of several files or even sometimes when copying one file in a monitored directory. Whole program is quite big so I'm sending the most important parts. OnCreated:

private void OnCreated(object source, FileSystemEventArgs e) {
        AddLogEntry(e.FullPath, "created", "");

        // Update last access data if it's file so the same file doesn't
        // get processed twice because of sending another event.
        if (fileType(e.FullPath) == 2) {
            lastPath = e.FullPath;
            lastTime = DateTime.Now;
        }

        // serves no purpose now, it will be remove soon
        string fileName = GetFileName(e.FullPath);

        // copies file from source to few other directories
        Copy(e.FullPath, fileName);

        Console.WriteLine("OnCreated: " + e.FullPath);
}

OnChanged:

private void OnChanged(object source, FileSystemEventArgs e) {
    // is it directory
    if (fileType(e.FullPath) == 1)
        return; // don't mind directory changes itself

    // Only if enough time has passed or if it's some other file
    // because two events can be generated
    int timeDiff = ((TimeSpan)(DateTime.Now - lastTime)).Seconds;
    if ((timeDiff < minSecsDiff) && (e.FullPath.Equals(lastPath))) {
        Console.WriteLine("-- skipped -- {0}, timediff: {1}", e.FullPath, timeDiff);
        return;
    }

    // Update last access data for above to work
    lastPath = e.FullPath;
    lastTime = DateTime.Now;

    // Only if size is changed, the rest will handle other handlers
    if (e.ChangeType == WatcherChangeTypes.Changed) {
        AddLogEntry(e.FullPath, "changed", "");
        string fileName = GetFileName(e.FullPath);
        Copy(e.FullPath, fileName);

        Console.WriteLine("OnChanged: " + e.FullPath);
    }
}

fileType:

private int fileType(string path) {
    if (Directory.Exists(path))
        return 1; // directory
    else if (File.Exists(path))
        return 2; // file
    else
        return 0;
}

Copy:

private void Copy(string srcPath, string fileName) {
    foreach (string dstDirectoy in paths) {
        string eventType = "copied";
        string error = "noerror";
        string path = "";
        string dirPortion = "";

        // in case directory needs to be made
        if (srcPath.Length > fsw.Path.Length) {
            path = srcPath.Substring(fsw.Path.Length,
                    srcPath.Length - fsw.Path.Length);

            int pos = path.LastIndexOf('\\');
            if (pos != -1)
                dirPortion = path.Substring(0, pos);
        }

        if (fileType(srcPath) == 1) {
            try {
                Directory.CreateDirectory(dstDirectoy + path);
                //Directory.CreateDirectory(dstDirectoy + fileName);
                eventType = "created";
            } catch (IOException e) {
                eventType = "error";
                error = e.Message;
            }
        } else {
            try {
                if (!overwriteFile && File.Exists(dstDirectoy + path))
                    continue;

                // create new dir anyway even if it exists just to be sure
                Directory.CreateDirectory(dstDirectoy + dirPortion);

                // copy file from where event occured to all specified directories
                using (FileStream fsin = new FileStream(srcPath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                    using (FileStream fsout = new FileStream(dstDirectoy + path, FileMode.Create, FileAccess.Write)) {
                        byte[] buffer = new byte[32768];
                        int bytesRead = -1;

                        while ((bytesRead = fsin.Read(buffer, 0, buffer.Length)) > 0)
                            fsout.Write(buffer, 0, bytesRead);
                    }
                }

            } catch (Exception e) {
                if ((e is IOException) && (overwriteFile == false)) {
                    eventType = "skipped";
                } else {
                        eventType = "error";
                        error = e.Message;
                        // attempt to find and kill the process locking the file.
                        // failed, miserably
                        System.Diagnostics.Process tool = new System.Diagnostics.Process();
                        tool.StartInfo.FileName = "handle.exe";
                        tool.StartInfo.Arguments = "\"" + srcPath + "\"";
                        tool.StartInfo.UseShellExecute = false;
                        tool.StartInfo.RedirectStandardOutput = true;
                        tool.Start();
                        tool.WaitForExit();
                        string outputTool = tool.StandardOutput.ReadToEnd();
                        string matchPattern = @"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";
                        foreach (Match match in Regex.Matches(outputTool, matchPattern)) {
                            System.Diagnostics.Process.GetProcessById(int.Parse(match.Value)).Kill();
                        }

                        Console.WriteLine("ERROR: {0}: [ {1} ]", e.Message, srcPath);
                }
            }
        }

        AddLogEntry(dstDirectoy + path, eventType, error);
    }
}

I checked everywhere in my program and whenever I use some file I use it in using block so even writing event to log (class for what I omitted since there is probably too much code already in post) wont lock the file, that is it shouldn't since all operations are using using statement block.

I simply have no clue who's locking the file if not my program "copy" process from user through Windows or something else.

Right now I have two possible "solutions" (I can't say they are clean solutions since they are hacks and as such not desirable). Since probably the problem is with fileType method (what else could lock the file?) I tried changing it to this, to simulate "blocking-until-ready-to-open" operation:

fileType:

private int fileType(string path) {
    FileStream fs = null;
    int ret = 0;
    bool run = true;

    if (Directory.Exists(path))
        ret = 1;
    else {
        while (run) {
            try {
                fs = new FileStream(path, FileMode.Open);
                ret = 2;
                run = false;
            } catch (IOException) {
            } finally {
                if (fs != null) {
                    fs.Close();
                    fs.Dispose();
                }
            }
        }
    }

    return ret;
}

This is working as much as I could tell (test), but... it's hack, not to mention other deficients.

The other "solution" I could try (I didn't test it yet) is using GC.Collect() somewhere at the end of fileType() method. Maybe even worse "solution" than previous one.

Can someone pleas tell me, what on earth is locking the file, preventing it from opening and how can I fix that? What am I missing to see?

Thanks in advance.

Maks
  • 117
  • 1
  • 3
  • 9

3 Answers3

3

The problem is most likely that the file is still being copied while you already try to access it. This can happen especially on large files.

You can try to check whether the file can be opened with write permissions before you actually start your processing. For details how to do that check here.

If you can influence the process creating the file there might be a better solution. First copy the file with a temporary extension, and then, after the copying is completed, rename it so that the FileSystemWatcher event will be triggered.

Community
  • 1
  • 1
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • Thanks a lot for quick response, but I don't need write permission, I need _read_ permission so I can read from it and copy it to other destinations. – Maks Jun 16 '10 at 21:17
  • @Maks: When you can write the file, you can also read it. If you will never be able to write, e.g. because the folder is protected you can also simply try to open the file read-only. – Dirk Vollmar Jun 16 '10 at 21:31
  • What I meant is that probably it's "easier" (more probable than write permission) to acquire read permission if it's windows process (copy) than write permission (since it didn't finish at the time of event) :) – Maks Jun 17 '10 at 13:22
  • Your solution looks kinda like my first "solution". In case I don't find anything better (cleaner) I will use something similar. Thanks again :) – Maks Jun 17 '10 at 13:29
1

You can try with Volume Shadow Copies. See www.codeproject.com/KB/dotnet/makeshadowcopy.aspx for more details.

Matej
  • 7,517
  • 2
  • 36
  • 45
1

FileSystemWatcher events trigger when the file begins the copy, not at the end, so it's common to run into this kind of errors.

Your first approach will work, however, I would recommend spinning the I/O intensive code on another thread, and using an incremental Sleep() instead of the busy waiting you do.

However, if you have access to the software that actually creates the files, the extension changing is a slightly less complicated solution. Just beware, that a xls filter on the FileSystemwatcher will match a file called myfile1.xls.temp, as I found that out the hard way :)

SWeko
  • 30,434
  • 10
  • 71
  • 106
  • Thanks Sweko. As mush as I could notice, FSW gets triggered when file is made for writing (0 bytes) and then _second_ time at the end of copy process when it finishes, since file size will be changed from 0 to original size. I thought I would need to use the first approach with some delay time of course. That's my last resort if I don't find what is locking the file and how to solve that. – Maks Jun 16 '10 at 21:59