Question

Preface: I'm very new to coding and mostly self-taught.

I'm writing a little app that will automatically copy my game save files to my backup directory. It uses the FileSystemWatcher class to watch a game's save directory and look for changes while I'm playing and calls OnChanged if a file is resized or written to.

The problem I'm having is that when my program is copying a file, it sometimes crashes the GAME (in this case Terraria). I'm guessing this has something to do with access restrictions, which I don't have much experience with. I'm surprised that File.Copy() would cause issues, since it's just reading, but it's a consistent crash with Terraria.

Stepping through the OnChanged calls has shown that Terraria writes temporary files, then copies them to the actual save files, and delete the temps. Pretty common.

So here's my code with the klutzy workaround I've got. It uses two timers: _delayTimer that starts when OnChanged is first called, and _canBackupTimer when _delayTimer elapses. I haven't had any issues in the games I've tested it with.

For each Timer, the interval is 5 seconds and AutoReset = True, so it will stop after elapsing once.

Is this the only way I can avoid IO Exceptions with the game being monitored? There has to be a better way, but I'm not sure where to look. I'm surprised that File.Copy would be restricting access to the game's save process.

Should I look at file access rights?

    private static void _delayTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
        _canBackupTimer.Enabled = true;
        _canBackupTimer.Start();
        _delayTimer.Enabled = false;
    }

    private static void _canBackupTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
        _lastAutoBackupTime = DateTime.Now;
        _canBackupTimer.Enabled = false;
    }


    private static void OnChanged(object source, FileSystemEventArgs e) {
        while (true) {

            if (!_canBackupTimer.Enabled && !_delayTimer.Enabled && (DateTime.Now - _lastAutoBackupTime).Seconds > 10) {
                //If neither timer is running and 10 seconds 
                //have elapsed since canBackupTimer stopped

                _delayTimer.Enabled = true;
                _delayTimer.Start();
                continue;
            }
            if (_canBackupTimer.Enabled) {
                //if canBackupTimer is running, do autobackup

                Game autoBackupGame = null;

                //_gamesToAutoBackup is a List<Game> for the program to watch.
                //Check to identify which Game is being backed up
                //g.RootFolder is the game's base directory, e.g. "...\Terraria\"
                foreach (var g in _gamesToAutoBackup) { 
                    if (e.FullPath.Contains(g.Name) || e.FullPath.Contains(g.RootFolder))
                        autoBackupGame = g;
                }

                if (autoBackupGame.RootFolder == null) {
                    var dir = new DirectoryInfo(autoBackupGame.Path);
                    autoBackupGame.RootFolder = dir.Name;
                }

                //Find the base directory of the file being changed and trim off
                //the unneeded pieces, e.g. "C:\Users\Rob\..."
                var indexOfGamePart = e.FullPath.IndexOf(autoBackupGame.RootFolder);
                var friendlyPath = e.FullPath.Substring(0, indexOfGamePart);
                var newPath = e.FullPath.Replace(friendlyPath, "\\");


                if (Directory.Exists(e.FullPath) && autoBackupGame != null) {
                    //True if directory, else it's a file.
                    //Do stuff for backing up a directory here.
                    //Currently nothing written here.
                }
                else { //If Directory.Exists is false, the path is a file.

                    try {
                        var copyDestinationFullPath = new FileInfo(_specifiedAutoBackupFolder + newPath);
                        if (!Directory.Exists(copyDestinationFullPath.DirectoryName))
                            Directory.CreateDirectory(copyDestinationFullPath.DirectoryName);
                        File.Copy(e.FullPath, copyDestinationFullPath.ToString(), true);
                    }
                    catch (FileNotFoundException ex) {
                        Logger.Log(ex); //My class, Logger, writes the exception text to a file.
                    }
                }
            }
            break;
        }
    }
Was it helpful?

Solution

My post takes the comments about Terraria into account. I haven't tried this, but here's how I would approach the problem if I wanted the best chance of copying the file while keeping Terraria from crashing.

Problem

My guess is that Terraria is crashing because it needs write access to the file while you are trying to copy it. I also thereby assume that File.Copy opens the file with no sharing access, so a different mechanism for opening and copying the file is needed to solve the problem.

Solution

You need some way to open and read from the file, while still leaving Terraria the right to simultaneously open and write to the file. I'm not 100% positive that this approach will do that, and it will depend on how Terraria is trying to open the file, but you can at least open the file so that sharing is still allowed. Instead of File.Copy, try:

using (FileStream inStream = new FileStream(filename, FileMode.Open, 
       FileAccess.Read, FileShare.ReadWrite) {
    // Specifying shared access as FileShare.ReadWrite is the key, as it lets 
    // Terraria open the file with write access.
    // Then add code here to copy the file out to your destination... 
    // this depends a bit on which version of .Net you are using, but 
    // inStream.CopyTo(outStream) is probably the easiest as long as you 
    // are using .Net 4+.
}

Conclusion

If your open fails, or your copy fails (exception), or your FileSystemWatcher signals while you are copying, it means Terraria is using the file and you will need to retry later. In any case, hopefully you will be able to stay out of Terraria's way.

I am very interested to hear if this approach works for you. Please comment if you give it a try.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top