65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Node.js 监控文件和文件夹更改

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (11投票s)

2013年12月21日

CPOL

11分钟阅读

viewsIcon

81781

downloadIcon

896

Node.js 入门 第二部分

George 的 Node.js 入门系列

引言

我继续通过本文深入了解 Node.js 的世界。

我不得不说,使用 JavaScript 不仅仅是为了操作 DOM 确实很有趣。我和我认识的许多开发者一样,主要使用 JavaScript 来处理 AJAX 调用,并让网页更具吸引力和响应性。到目前为止,我从未真正将 JavaScript 视为除了 UI 之外还有什么用处。我真的开始重新评估这种看法。

在本文中,我开始深入研究 Node.jsfs(文件系统)模块。对我来说,这与我以往用 JavaScript 做过的任何事情都完全不同。我通常使用 C# 甚至 PowerShell 等其他技术在服务器端进行文件处理。

因此,要求是:我需要一种方法来监控目录(以及可能的所有子目录)的变化,例如添加新文件、删除文件、修改文件、删除文件夹、添加文件夹等。本文将重点介绍此功能。

战壕报道(到目前为止的想法)

虽然本文未能满足我自定“入门”项目的 Sprint 2 的所有要求,但我认为我在第一篇文章中试图涵盖的内容太多,而不是一次专注于一个方面。我将尝试在未来纠正这一点,一次专注于一个项目。嘿!你永远不知道,我的写作技巧可能会像我的 JavaScript/Node 技能一样有所提高……更奇怪的事情都发生过。

到目前为止,我通过我写的几个模块学到了很多东西,但仍然有太多东西需要学习。可悲的是,在我目前的 JavaScript 使用方式中,我极其依赖 jQuery 和其他框架。这样做极大地影响了我对 JavaScript 基础知识的掌握,因此,就像第一篇文章一样,我继续避免使用框架,直到我对 Node 和 JavaScript 有了扎实的掌握。这样,当我确实使用框架时,它就是节省时间的工具,而不是拐杖。我仍然不是专家,也不假装是,所以对于阅读本文的各位:请理解,这是以“边学边做”的形式完成的,而不是由专家教新手……我 JavaScript/Node 的新手,所以请记住这一点。如果您需要 C++、C#、SQL 或汇编方面的帮助:我很乐意为您效劳,并能凭记忆回答很多问题…… JavaScript/Node?我会挠头回答“我不知道,但让我们一起找出答案。”

您可能会注意到的一个问题是,我的编码风格在每篇文章之间都在变化。我正在努力使我的风格和约定与大多数 JavaScript 代码保持一致,但这与我多年来的实践和风格相悖。我发现了 Douglas Crawford 的 JavaScript 编程语言代码约定,并从中借鉴了一些提示……但我的个人偏好仍然很明显……可能比某些代码审查人员喜欢的要多。

我还开始遇到变量作用域、闭包问题,以及 JavaScript 并非像我目前习惯的那样针对 OOP 的事实:我正在努力解决。其他问题是认识到 Node/JavaScript 是单线程的,并且您通过非阻塞异步方法和回调函数处理后台进程。在处理本文附带代码时,这个问题很快就变得很重要。我不认为我已经接近“掌握”这个概念,但我正在努力。

最后,在我们开始监控目录之前,我刚开始使用 GitHub(我习惯使用 TFS/SourceSafe)。我已经把本文的代码放到了 GitHub 上,以便人们可以协作或分支。我希望也能更熟悉 git。

那么……开始写代码吧!

使用 Node 监控(监视)目录

Node.jsfs 模块提供了一个名为 fs.watch(filename, [options], [listener]) 的方法,该方法相当简单易用。尽管文档(参见链接)指出它不稳定,但它确实非常简单易用。

注意:由于我使用的是 Visual Studio 的 Node.js 工具(因此是 Windows,我知道,我知道,我能听到一些纯粹主义者的嘶嘶声),我将假设您在您的计算机上有一个像我一样名为“C:\sim”的文件夹。您可以将其修改为 Linux 上的“/usr/whatever/whatever/”,或者随意指向您计算机上的任何文件夹。示例代码以及我编写的模块在 CentOS 和 Windows 上都能工作,您只需稍微修改一下路径即可。

所以:使用内置的 Node.js fs.watch 方法来监控目标文件夹,看起来大致如下……

// Require the file system
  fs = require("fs");
// Watch the sim directory
fs.watch("c:\\sim", { persistent: true }, function (event, fileName) {
  console.log("Event: " + event);
  console.log(fileName + "\n");
});

很简单,对吧?当 sim 目录中的内容发生变化时,侦听器函数将把发生的事件和受影响的文件名记录到控制台。因此,返回值也相当简单。如果您想要更多,那么您就必须自己获取有关文件的信息(提供更改时的文件名相当方便)。让我们看看在以下情况下上述代码的输出。

1. 向文件夹添加文件

让我们向 sim 文件夹添加一个名为 test.txt 的文件,看看结果。

  Event: rename
  test.txt

  Event: change
  test.txt

  Event: change
  test.txt 

2. 现在将 test.txt 重命名为 blah.txt

  Event: rename
  null

  Event: rename
  blah.txt

  Event: rename
  blah.txt

3. 现在删除该文件

  Event: rename
  null

4. 现在添加一个文件夹“New Folder”

  Event: rename
  New Folder

5. 现在将文件夹“Test Folder”重命名

  Event: rename
  null

  Event: rename
  Test Folder

6. 最后,删除该文件夹

  Event: rename
  null

如您所见,我们没有获得很多有用的信息。我们知道发生了某些事情,一般的事件,有时是添加的文件/文件夹的名称。这适用于许多用途,并且在监视单个文件而不是文件夹时会获得稍微好一些的结果,但是它仍然不是我需要的。我需要更详细的信息。

引入 DirectoryWatcher

所以……鉴于以下条件适用,

  1. fs.watch 方法并不完全符合我的要求,
  2. 我正尝试学习如何在 JavaScript/Node 中做这些事情,而不是使用其他技术,
  3. 这似乎是一个完美的学习机会,并且
  4. 我有点疯狂……我决定“自己动手”制作一个解决方案。

DirectoryWatcher 模块(附加到本文,并在 GitHub 上可用)大量使用了 fs 模块。它使用 setInterval 形式的计时器来控制检查您正在监视的目录的频率,并且可以递归检查任何子文件夹。我不建议在任何生产应用程序中使用它,因为它是一个由承认的新手编写的模块,但我认为这是一个很好的起点。您可以随意查看大量注释的代码,了解我是如何详细构建它的。

描述 DirectoryWatcher

DirectoryWatcher 模块导出一个对象(创意十足地命名为)DirectoryWatcher。该对象扫描给定的目录(如果 recursive 设置为 true,则包括子目录),并根据发现的内容触发六个事件。DirectoryWatcher 还公开了一个 directoryStructure 对象,该对象代表扫描过程中被监视目录的视图。

DirectoryWatcher 构造函数

DirectoryWatcher 有一个构造函数 DirectoryWatcher(root, recursive),其中 root 是您要监视的目录的路径,recursive 确定它是否监视子文件夹(true = 监视子文件夹,false = 不监视)。

因此,当您创建一个新的 DirectoryWatcher 对象时,您会像这样操作。

// Imports / Requires
var dirwatch = require("./modules/DirectoryWatcher.js");

// Create a monitor object that will watch a directory
// and all it's sub-directories (recursive) in this case
// we'll assume you're on a windows machine with a folder 
// named "sim" on your c: drive.
// should work on both linux and windows, update the path
// to some appropriate test directory of your own.
// you can monitor only a single folder and none of its child
// directories by simply changing the recursive parameter to
// to false
var simMonitor = new dirwatch.DirectoryWatcher("C:\\sim", true);

DirectoryWatcher 属性

DirectoryWatcher 对象具有以下属性

  1. root:正在监视的目录的根目录路径
  2. recursive:确定是否像根目录一样监视该目录的子文件夹
  3. directoryStructure:一个对象,表示扫描的结构及其文件详细信息
  4. timer:对象的当前计时器
  5. suppressInitialEvents:确定当 DirectoryWatcher 第一次扫描目录时(调用 .start() 时)是否触发事件

DirectoryWatcher 的公开方法

DirectoryWatcher 对象公开以下方法

  1. scanDirectory(dir, suppressEvents):主要的扫描方法。尽可能尝试非阻塞。扫描给定的目录,然后尝试记录目录中的每个文件。
    • dir = 要扫描的目录
    • suppressEvents = 抑制本次扫描迭代将触发的任何事件
    • true = 将抑制事件
    • false = 将触发事件。
      注意:大多数情况下,我找不到直接使用此方法的理由,请使用 start(interval)stop()
  2. start(interval):启动此 DirectoryWatcher 实例监视给定的根路径(在创建对象时设置),并定义检查更改的间隔。
    • interval = 检查给定监视目录更新之间的时间(以毫秒为单位)。
  3. stop():停止此 DirectoryWatcher 实例的监视。

DirectoryWatcher 将触发的事件

DirectoryWatcher 当前触发六个事件,这些事件是

  1. fileAdded:当文件添加到监视文件夹时触发。它还将返回一个 FileDetail 对象,该对象描述已添加的文件。
  2. fileChanged:当文件已更改时触发。它还将返回一个 FileDetail 对象,描述已更改的文件,以及一个关联数组对象,描述具体发生了哪些更改。
  3. fileRemoved:当文件被删除时触发。还会返回已删除文件的完整路径。
  4. folderAdded:当文件夹添加到监视目录时触发,还会返回新添加文件夹的路径。(仅在递归模式下触发
  5. folderRemoved:当文件夹从监视目录或其子目录中删除时触发,还会返回已删除文件夹的路径。(仅在递归模式下触发
  6. scannedDirectory每次目录扫描完成时触发(这个触发得很多)

关联对象:FileDetail

有一个与之关联的对象(包含在模块中),名为 FileDetail。该对象由 fileAddedfileChanged 事件返回,并且也包含在 directoryStructure 对象中,用于表示目录中的文件。FileDetail 对象具有以下属性

  1. directory:文件的父目录
  2. fullPath:包括文件目录在内的完整路径。
  3. fileName:仅文件名,不带路径
  4. size:文件的字节大小
  5. extension:文件的扩展名(.js、.txt 等)
  6. accessed:文件的最后访问日期
  7. modified:文件的最后修改日期
  8. created:文件的最后创建日期

FileDetail 对象还有一个方法 compareTo(fileDetail),该方法将当前的 FileDetail 对象与传入的对象进行比较,并返回有关文件是否不同以及如何不同的信息。它返回的对象有一个 different 属性,这是一个布尔值,指示文件是否不同,还有一个 differences 属性,这是一个包含发现的差异的关联数组。

整合(使用代码)

好的,在描述完上面的模块后,让我们用一个非常简单的示例应用程序来说明如何使用这个模块。该示例将模仿我们最初只使用 fs.watch 方法的应用程序。

使用上的主要区别是我们必须捕获事件,而不是将它们传递给侦听器函数……请参见下面的代码。

注意:我假设 DirectoryWatcher 模块位于 app.js 代码所在目录下的名为“modules”的文件夹中。(此示例包含在附加的代码文件和 GitHub 链接中。)

// Imports / Requires
var dirwatch = require("./modules/DirectoryWatcher.js");

// Create a monitor object that will watch a directory
// and all it's sub-directories (recursive) in this case
// we'll assume you're on a windows machine with a folder 
// named "sim" on your c: drive.
// should work on both linux and windows, update the path
// to some appropriate test directory of your own.
// you can monitor only a single folder and none of its child
// directories by simply changing the recursive parameter to
// to false
var simMonitor = new dirwatch.DirectoryWatcher("C:\\sim", true);

// start the monitor and have it check for updates
// every half second.
simMonitor.start(500);

// Log to the console when a file is removed
simMonitor.on("fileRemoved", function (filePath) {
  console.log("File Deleted: " + filePath);
});

// Log to the console when a folder is removed
simMonitor.on("folderRemoved", function (folderPath) {
  console.log("Folder Removed: " + folderPath);
});

// log to the console when a folder is added
simMonitor.on("folderAdded", function (folderPath) {
  console.log("Folder Added: " + folderPath);
});

// Log to the console when a file is changed.
simMonitor.on("fileChanged", function (fileDetail, changes) {
  console.log("File Changed: " + fileDetail.fullPath);
  for (var key in changes) {
    console.log("  + " + key + " changed...");
    console.log("    - From: " + ((changes[key].baseValue instanceof Date) ? 
    changes[key].baseValue.toISOString() : changes[key].baseValue));
    console.log("    - To  : " + ((changes[key].comparedValue instanceof Date) ? 
    changes[key].comparedValue.toISOString() : changes[key].comparedValue));
  }
});

// log to the console when a file is added.
simMonitor.on("fileAdded", function (fileDetail) {
  console.log("File Added: " + fileDetail.fullPath);
});

// Let us know that directory monitoring is happening and where.
console.log("Directory Monitoring of " + simMonitor.root + " has started");

现在,让我们运行上面的示例应用程序,并在 sim 目录中执行与之前相同的操作……

1. 向 sim 文件夹添加一个名为 test.txt 的文件

  Directory Monitoring of C:\sim has started
  File Added: C:\sim\test.txt

2. 现在将 test.txt 重命名为 blah.txt

  File Added: C:\sim\blah.txt
  File Deleted: C:\sim\test.txt

3. 现在删除该文件

  File Deleted: C:\sim\blah.txt

4. 现在让我们添加一个文件夹“New Folder”

  Folder Added: C:\sim\New Folder

5. 现在将文件夹“Test Folder”重命名

  Folder Removed: C:\sim\New Folder
  Folder Added: C:\sim\Test Folder

6. 让我们删除该文件夹

  Folder Removed: C:\sim\Test Folder

7. 最后,将 test.txt 文件添加回 sim 文件夹,然后修改它

  File Changed: C:\sim\test.txt
    + size changed...
      - From: 51
      - To  : 112
    + modified changed...
      - From: 2013-12-21T03:15:02.000Z
      - To  : 2013-12-21T03:15:31.000Z

我认为这比以前更有信息量。性能损失可能有点大,但作为第一次尝试……我可以接受。

从这里开始,我可以构建一个应用程序来监视目录,报告何时发生更改,或者使用此对象构建一个应用程序,在文件被放入目录后等待处理它。

我能想到很多用途。我相信您也能想到。该模块需要稍作重构并添加错误处理,但它可以工作。

最终想法

我喜欢构建 DirectoryWatcher,并希望本文的描述对您有所帮助,并且附带的代码能帮助您与我一起学习。

我仍然计划尝试每月写两篇文章(在工作和孩子允许的情况下),下一篇文章仍将围绕 fs 模块的“空间”。由于时间限制,即使是这个小模块也花了我一个半星期的业余时间,所以我不会承诺超过每月两次,但是,公开声明我的发布承诺应该会让我继续努力。

祝大家节日快乐,[愉快、快乐、未定义] $holidayName。

© . All rights reserved.