2010-06-16 9 views
1

FileSystemWatcher을 사용하는 프로그램을 작성하고 OnCreated 또는 OnChanged 이벤트를 받으면 작성된/변경된 파일을 지정된 디렉터리에 복사합니다. 처음에는 OnChanged/OnCreated 이벤트를 두 번 보낼 수있는 문제 (500MB 파일을 처리해야 할 경우에는 허용되지 않음)와 관련하여 문제가 있었지만이 문제를 해결할 수있는 방법을 만들었고 실제로 차단 된 것은 다음과 같습니다. 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 :

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; 
} 
아마 때문에 문제 (? 다른 어떤 파일을 잠글 수) 내가 "차단-까지-바로 열기"동작을 시뮬레이션하기 위해,이로 변경 시도 fileType 방법입니다

이것은 (테스트) 말할 수있는만큼 효과가 있지만, 다른 결함은 말할 것도없고 해킹입니다.

내가 시도해 볼 수있는 다른 "솔루션"은 메서드의 끝에 어딘가에 GC.Collect()을 사용하고 있습니다. 어쩌면 이전의 것보다 더 나쁜 "해결책"일 수도 있습니다.

누군가가 파일을 잠그고 그 파일을 열지 못하게하고 어떻게 해결할 수 있습니까? 나는 무엇을보고 싶니?

미리 감사드립니다.

답변

2

문제는 이미 파일에 액세스하려고 시도하는 동안 파일이 여전히 복사되고있는 것일 수 있습니다. 이것은 대용량 파일에서 특히 발생할 수 있습니다.

실제로 처리를 시작하기 전에 쓰기 사용 권한으로 파일을 열 수 있는지 확인할 수 있습니다. 해당 확인 방법에 대한 자세한 내용은 here을 참조하십시오.

파일을 만드는 프로세스에 영향을 줄 수있는 경우 더 나은 해결책이 될 수 있습니다. 임시 확장명을 가진 파일을 먼저 복사 한 다음 복사가 완료된 후 FileSystemWatcher 이벤트가 트리거되도록 이름을 바꿉니다.

+0

빠른 응답을 위해 고맙지 만 쓰기 권한이 필요하지 않으므로 _read_ 권한이 필요합니다. 그래서 나는 그것을 읽고 다른 목적지로 복사 할 수 있습니다. – Maks

+0

@Maks : 파일을 쓸 수 있으면 읽을 수도 있습니다. 예를 들어 글을 쓸 수 없으면 폴더가 보호되어 있기 때문에 파일을 읽기 전용으로 열려고 시도 할 수도 있습니다. –

+0

내가 의미하는 바는 쓰기 권한 (쓰기 권한보다 가능성이 높음)이 쓰기 권한 (Windows 이벤트 (이벤트가 발생했을 때 완료되지 않았기 때문에)보다 Windows 프로세스 인 경우)입니다. – Maks

1

볼륨 섀도 복사본을 시도 할 수 있습니다. 자세한 내용은 www.codeproject.com/KB/dotnet/makeshadowcopy.aspx를 참조하십시오.

1

FileSystemWatcher 이벤트는 파일이 끝날 때가 아니라 복사를 시작할 때 트리거되므로 일반적으로 이러한 종류의 오류가 발생합니다.

첫 번째 방법은 효과가 있지만 다른 스레드에서 I/O 집중 코드를 회전시키고 바쁜 대기 대신 증분 슬립()을 사용하는 것이 좋습니다.

그러나 실제로 파일을 만드는 소프트웨어에 액세스 할 수있는 경우 확장명 변경은 약간 덜 복잡한 해결책입니다. 단지 xls 필터가 FileSystemwatcher 일 때 myfile1.xls.temp이라는 파일과 일치하므로주의해야합니다.

+0

Thanks Sweko. FSW는 파일 크기가 0에서 원래 크기로 변경되기 때문에 복사가 끝나면 파일 쓰기 (0 바이트) 후 _second_time이 될 때 트리거됩니다. 나는 약간의 지연 시간을 가지고 첫 번째 접근법을 사용해야 할 것이라고 생각했다. 파일을 잠그고있는 것을 찾지 못하고 해결하는 방법을 찾지 못했다면 최후의 수단입니다. – Maks

관련 문제