2010-06-16 2 views
1

Я пишу программу, которая использует FileSystemWatcher для мониторинга изменений в заданном каталоге, а когда он получает событие OnCreated или OnChanged, он копирует созданные или измененные файлы в указанные каталоги. Сначала у меня были проблемы с тем, что события OnChanged/OnCreated могут быть отправлены дважды (неприемлемо в случае необходимости обработки файла 500 МБ), но я сделал это по этому пути и с тем, что я ДЕЙСТВИТЕЛЬНО БЛОКИРОВАН, получает следующее IOException : Процесс не может получить доступ к файлу 'C: \ Where are Photos \ bookmarks (11) .html', потому что он используется другим процессом.C# Невозможно открыть файл для чтения

Таким образом, чтобы программа не копировала все файлы, она должна. Как я уже упоминал, когда пользователь использует эту программу, он задает контролируемый каталог, когда пользователь копирует/создает/изменяет файл в этом каталоге, программа должна получить событие OnCreated/OnChanged, а затем скопировать этот файл в несколько других каталогов. Ошибка во всех случаях, если пользователь копирует несколько файлов, которые должны перезаписывать другие в контролируемой папке или при копировании большого количества нескольких файлов или даже при копировании одного файла в контролируемый каталог. Вся программа довольно большая, поэтому я отправляю наиболее важные части. 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; 
} 

Копия:

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 используйте его в using блоке, чтобы даже записывать событие в журнал (класс для того, что я пропустил, поскольку, вероятно, слишком много кода уже в сообщении) не будет блокировать файл, то есть он не должен, так как все операции используют using блок оператора.

Я просто понятия не имею, кто запирает файл, если не моя программа «копирует» процесс через пользователя через Windows или что-то еще.

Прямо сейчас у меня есть два возможных «решения» (я не могу сказать, что они чистые решения, поскольку они хаки и как таковые не желательны). Так, вероятно, проблема с fileType методом (что еще может заблокировать файл?) Я попытался изменить его к этому, чтобы имитировать «блокирование-до готовности к открытой» операции:

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; 
} 

Это работает так, как я мог сказать (тест), но ... это взломать, не говоря уже о других недостатках.

Другое «решение» Я мог бы попробовать (я еще не тестировал его) использует GC.Collect() где-то в конце fileType() метод. Может быть, даже хуже «решение», чем предыдущее.

Может ли кто-нибудь просить меня сказать, что на самом деле запирает файл, препятствуя его открытию и как я могу это исправить? Что мне не хватает, чтобы увидеть?

Заранее спасибо.

ответ

2

Проблема, скорее всего, в том, что файл все еще копируется, когда вы уже пытаетесь получить к нему доступ. Это может произойти особенно в больших файлах.

Вы можете попытаться проверить, может ли файл быть открыт с разрешениями на запись, прежде чем вы начнете свою обработку. Подробнее о том, как это сделать, here.

Если вы можете влиять на процесс создания файла, может быть лучшее решение. Сначала скопируйте файл с временным расширением, а затем, после завершения копирования, переименуйте его так, чтобы событие FileSystemWatcher было запущено.

+0

Большое спасибо за быстрый ответ, но мне не нужно разрешение на запись, мне нужно разрешение _read_ поэтому я могу прочитать его и скопировать в другие пункты назначения. – Maks

+0

@Maks: Когда вы можете написать файл, вы также можете его прочитать. Если вы никогда не сможете писать, например. потому что папка защищена, вы также можете просто попытаться открыть файл только для чтения. –

+0

Что я имел в виду, так это то, что, возможно, «легче» (более вероятно, чем разрешение на запись) получить разрешение на чтение, если это Windows-процесс (копия), чем разрешение на запись (поскольку он не завершился во время события) :) – Maks

1

Вы можете попробовать с помощью тоновых копий тонов. Для получения дополнительной информации см. Www.codeproject.com/KB/dotnet/makeshadowcopy.aspx.

1

Событие FileSystemWatcher запускается, когда файл начинается с копии, а не в конце, поэтому обычное обращение к таким ошибкам.

Ваш первый подход будет работать, однако я бы рекомендовал использовать интенсивный код ввода-вывода в другом потоке и использовать инкрементный Sleep() вместо оживленного ожидания.

Однако, если у вас есть доступ к программному обеспечению, которое фактически создает файлы, изменение расширения является немного менее сложным решением. Просто будьте осторожны, что фильтр xls на FileSystemwatcher будет соответствовать файлу с именем myfile1.xls.temp, так как я нашел это из трудного пути :)

+0

Спасибо Sweko. Как я мог заметить, FSW запускается, когда файл создается для записи (0 байт), а затем _second_ time в конце процесса копирования, когда он заканчивается, так как размер файла будет изменен от 0 до исходного. Я думал, что мне нужно будет использовать первый подход с некоторым временем задержки, конечно. Это мое последнее средство, если я не нахожу, что блокирует файл и как его решить. – Maks

Смежные вопросы