仅当匹配文件存在时才移动文件
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 异常,并检查共享冲突(这可能意味着写入文件尚未完成)。
我有一个应用程序需要两个文件来处理数据。一个包含实际数据的 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 异常,并检查共享冲突(这可能意味着写入文件尚未完成)。