仅当匹配文件存在时才移动文件

Moving files only if matching file exists

我有一个应用程序需要两个文件来处理数据。一个包含实际数据的 zip 文件,然后是一个说明如何处理所述数据的控制文件。

这些文件通过 sftp 下载到暂存目录。 zip 文件完成后,我需要检查控制文件是否也存在。它们仅共享一个命名前缀(例如 100001_ABCDEF_123456.zip 与 100001_ABCDEF_control_file.ctl 配对。

我正在尝试找到一种方法来等待 zip 文件完成下载,然后即时移动文件,同时保持目录结构,因为这对下一步处理很重要。

目前我正在等待 sftp worker 完成,然后调用 robocopy 来移动所有内容。我想要一个更完善的方法。

我已经尝试了几种方法,但都得到了相同的结果。文件下载但从不移动。出于某种原因,我无法让比较正常工作。

我尝试使用 FileSystemWatcher 寻找从 filepart 到 zip 的重命名,但它似乎错过了几次下载,并且由于某种原因,当我到达我的 foreach 以搜索控制文件的目录时,该功能消失了。 下面是 FileSystemWatcher 事件,我将其称为创建和更改。 下面还有 filesystemwatcher 的设置。

        watcher.Path = @"C:\Sync\";
        watcher.IncludeSubdirectories = true;
        watcher.EnableRaisingEvents = true;
        watcher.Filter = "*.zip";
        watcher.NotifyFilter = NotifyFilters.Attributes |
                               NotifyFilters.CreationTime |
                               NotifyFilters.FileName |
                               NotifyFilters.LastAccess |
                               NotifyFilters.LastWrite |
                               NotifyFilters.Size |
                               NotifyFilters.Security | 
                               NotifyFilters.CreationTime | 
                               NotifyFilters.DirectoryName;
        watcher.Created += Watcher_Changed;
        watcher.Changed += Watcher_Changed;

 private void Watcher_Changed(object sender, FileSystemEventArgs e)
    {
        var dir = new DirectoryInfo(e.FullPath.Substring(0, e.FullPath.Length - e.Name.Length));
        var files = dir.GetFiles();

        FileInfo zipFile = new FileInfo(e.FullPath);

        foreach (FileInfo file in files)
        {
            MessageBox.Show(file.Extension);
            if (file.Extension == "ctl" && file.Name.StartsWith(e.Name.Substring(0, (e.Name.Length - 14))))
            {
                file.CopyTo(@"C:\inp\");
                zipFile.CopyTo(@"C:\inp\");
            }
        }
    }

Watcher_Changed 会因为各种各样的事情而被调用,并不是每次被调用时你都想对此做出反应。

您应该在事件处理程序中做的第一件事是尝试以独占方式打开 zipFile。如果您做不到,请忽略此事件并等待另一个事件。如果这是一个 FTP 服务器,每次将新数据块写入磁盘时,您都会收到一个更改事件。您还可以将某些内容放在 "retry" 队列中,或使用其他机制来检查该文件稍后是否可用。我在我们的系统中有类似的需求,我们在发现第一个变化后每 5 秒尝试一次。只有当我们可以以独占方式打开文件进行写入时,我们才允许它继续下一步。

我会加强你对文件名的假设。您将搜索限制为 *.zip,但不仅仅依赖于该目标目录中存在的 .zip 文件。验证您对文件名所做的解析没有遇到意外值。您可能还想在调用 dir.GetFiles() 之前检查 dir.Exists()。那可能会抛出异常。

关于丢失的事件,请参阅关于缓冲区溢出的这个很好的答案:FileSystemWatcher InternalBufferOverflow

众所周知,FileSystemWatcher class 很难正确使用,因为对于正在写入、移动或复制的单个文件,您会得到多个事件,@WillStoltenberg 在他的文章中也提到过.

我发现设置一个定期运行的任务(例如每 30 秒)要容易得多。对于您的问题,您可以轻松地执行以下操作。请注意,使用定时器而不是 Task.Delay 的类似实现可能更可取。

public class MyPeriodicWatcher 
{
    private readonly string _watchPath;
    private readonly string _searchMask;
    private readonly Func<string, string> _commonPrefixFetcher;
    private readonly Action<FileInfo, FileInfo> _pairProcessor;
    private readonly TimeSpan _checkInterval;
    private readonly CancellationToken _cancelToken;

    public MyPeriodicWatcher(
        string watchPath,
        string searchMask,
        Func<string, string> commonPrefixFetcher,
        Action<FileInfo, FileInfo> pairProcessor,
        TimeSpan checkInterval,
        CancellationToken cancelToken)
    {
        _watchPath = watchPath;
        _searchMask = string.IsNullOrWhiteSpace(searchMask) ? "*.zip" : searchMask;
        _pairProcessor = pairProcessor;
        _commonPrefixFetcher = commonPrefixFetcher;
        _cancelToken = cancelToken;
        _checkInterval = checkInterval;
    }

    public Task Watch()
    {
        while (!_cancelToken.IsCancellationRequested)
        {
            try
            {
                foreach (var file in Directory.EnumerateFiles(_watchPath, _searchMask))
                {
                    var pairPrefix = _commonPrefixFetcher(file);
                    if (!string.IsNullOrWhiteSpace(pairPrefix))
                    {
                        var match = Directory.EnumerateFiles(_watchPath, pairPrefix + "*.ctl").FirstOrDefault();
                        if (!string.IsNullOrEmpty(match) && !_cancelToken.IsCancellationRequested)
                            _pairProcessor(
                                new FileInfo(Path.Combine(_watchPath, file)),
                                new FileInfo(Path.Combine(_watchPath, match)));
                    }
                    if (_cancelToken.IsCancellationRequested)
                        break;
                }
                if (_cancelToken.IsCancellationRequested)
                    break;

                Task.Delay(_checkInterval, _cancelToken).Wait().ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                break;
            }
        }
    }
}

您需要提供

  • 监控路径
  • 第一个文件(即 *.zip)的搜索掩码
  • 从 zip 文件名中获取通用文件名前缀的函数委托
  • 一个区间
  • 将执行移动并接收要处理/移动的对的 FileInfo 的代表。
  • 和一个取消令牌以彻底取消监控。

在您的 pairProcessor 委托中,捕获 IO 异常,并检查共享冲突(这可能意味着写入文件尚未完成)。