如果匹配的文件存在,则只移动文件

3
我有一个应用程序需要两个文件来处理数据。一个zip文件包含实际的数据,然后是一个控制文件,指示如何处理这些数据。
这些文件通过sftp下载到暂存目录。一旦zip文件完成,我就需要检查并查看控制文件是否也在那里。它们只共享命名前缀(例如100001_ABCDEF_123456.zip与100001_ABCDEF_control_file.ctl匹配)。
我正在尝试找到一种方法,等待zip文件完成下载,然后在移动文件时即时处理,同时保持目录结构,因为这对于处理的下一步很重要。
目前,我正在等待sftp工作程序完成,然后调用robocopy移动所有内容。我想要更精致的方法。
我尝试了几种方法,但结果都一样。文件下载但从未移动。由于某种原因,我无法使比较正确工作。
我尝试使用FileSystemWatcher来查找文件部分重命名为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\");
            }
        }
    }

1
在你的监视器中,它正在监视路径@"C:\Sync\",你设置了watcher.IncludeSubdirectories = true;。在你的文件处理中,你将文件移动到@"C:\Sync\inp\"。这是监视器正在监视的子目录。这会引起问题。 - Alex
修改为 C:\INP,没有更改。 - JD Roberson
2个回答

1

Watcher_Changed会因为各种各样的事情而被调用,但并不是每次调用都需要做出反应。

在事件处理程序中,你应该首先尝试独占地打开zipFile。如果无法打开,请忽略此事件并等待另一个事件。如果这是一个FTP服务器,则每当将新数据块写入磁盘时,您都会收到更改事件。您还可以将某些内容放在“重试”队列上或使用其他机制来检查文件是否在稍后可用。我们的系统中也有类似的需求,在我们注意到第一次更改后,我们每隔5秒尝试一次。只有在我们能够独占地打开文件进行写入时,才允许它继续进行下一步。

我建议你严格限制文件名的格式。你只搜索*.zip文件,但不要仅依赖于目标目录中存在你的.zip文件。验证你对文件名的解析是否命中了意外的值。在调用dir.GetFiles()之前,你可能还想检查dir.Exists()。那可能会抛出异常。

关于丢失事件,可以看一下有关缓冲区溢出的这个好答案:FileSystemWatcher InternalBufferOverflow


我诚实地说,我只包含这个片段,因为这是我最近尝试解决问题的方法。我愿意接受更好的解决方案。我也在下载器的fileCompleted事件处理程序中尝试过这个方法。但我始终无法将文件配对。 - JD Roberson
你能为我澄清一下是哪里出了问题吗?根据你最初的描述,似乎程序只是因为抛出异常而提前终止了。你是说你可以得到一个完全稳定的执行,但匹配逻辑却无法找到控制文件吗? 其他问题:控制文件和压缩文件是否以固定顺序到达? - Will Stoltenberg
哦 - 我认为也许你真正想要的是:file.Name.StartsWith(e.Name.Substring(0, 14))),而不是(file.Name.StartsWith(e.Name.Substring(0, (e.Name.Length - 14))))。也就是说,查看 e.Name 的前 14 个字符,看看文件是否以这些字符开头。 - Will Stoltenberg
我需要截掉最后的14个字符以获得唯一的字符串。 - JD Roberson
是的,应用程序可以正常执行,只是它无法形成一对。 - JD Roberson
你能输出一份比较记录日志吗?类似于: log.Debug("Zip: {0} = Control: {1}", file.Name.Substring(0, e.Name.Length - 14), e.Name.Substring(0, (e.Name.Length - 14))); 这可能会清楚地显示您的匹配中出了什么问题。 - Will Stoltenberg

0

FileSystemWatcher类因为会对正在被写入、移动或复制的单个文件产生多个事件而难以正确使用,正如@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异常,并检查共享冲突(这可能意味着尚未完成写入文件)。


经过一些调整,这样做就可以了!非常感谢你借给我一些脑力时间! - JD Roberson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接