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






4.85/5 (11投票s)
Node.js 入门 第二部分
George 的 Node.js 入门系列
- 第一部分: 获取查询字符串数据、POST 数据以及响应序列化对象
- 第二部分:本文(使用 Node.js 监控文件和文件夹更改)
引言
我继续通过本文深入了解 Node.js 的世界。
我不得不说,使用 JavaScript 不仅仅是为了操作 DOM 确实很有趣。我和我认识的许多开发者一样,主要使用 JavaScript 来处理 AJAX 调用,并让网页更具吸引力和响应性。到目前为止,我从未真正将 JavaScript 视为除了 UI 之外还有什么用处。我真的开始重新评估这种看法。
在本文中,我开始深入研究 Node.js 的 fs
(文件系统)模块。对我来说,这与我以往用 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.js 的 fs
模块提供了一个名为 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
所以……鉴于以下条件适用,
fs.watch
方法并不完全符合我的要求,- 我正尝试学习如何在 JavaScript/Node 中做这些事情,而不是使用其他技术,
- 这似乎是一个完美的学习机会,并且
- 我有点疯狂……我决定“自己动手”制作一个解决方案。
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
对象具有以下属性
root
:正在监视的目录的根目录路径
recursive
:确定是否像根目录一样监视该目录的子文件夹directoryStructure
:一个对象,表示扫描的结构及其文件详细信息timer
:对象的当前计时器
suppressInitialEvents
:确定当DirectoryWatcher
第一次扫描目录时(调用.start()
时)是否触发事件
DirectoryWatcher 的公开方法
DirectoryWatcher
对象公开以下方法
scanDirectory(dir, suppressEvents)
:主要的扫描方法。尽可能尝试非阻塞。扫描给定的目录,然后尝试记录目录中的每个文件。dir
= 要扫描的目录suppressEvents
= 抑制本次扫描迭代将触发的任何事件true
= 将抑制事件false
= 将触发事件。
注意:在大多数情况下,我找不到直接使用此方法的理由,请使用start(interval)
和stop()
。start(interval)
:启动此DirectoryWatcher
实例监视给定的根路径(在创建对象时设置),并定义检查更改的间隔。interval
= 检查给定监视目录更新之间的时间(以毫秒为单位)。stop()
:停止此DirectoryWatcher
实例的监视。
DirectoryWatcher 将触发的事件
DirectoryWatcher
当前触发六个事件,这些事件是
fileAdded
:当文件添加到监视文件夹时触发。它还将返回一个FileDetail
对象,该对象描述已添加的文件。
fileChanged
:当文件已更改时触发。它还将返回一个FileDetail
对象,描述已更改的文件,以及一个关联数组对象,描述具体发生了哪些更改。
fileRemoved
:当文件被删除时触发。还会返回已删除文件的完整路径。
folderAdded
:当文件夹添加到监视目录时触发,还会返回新添加文件夹的路径。(仅在递归模式下触发)
folderRemoved
:当文件夹从监视目录或其子目录中删除时触发,还会返回已删除文件夹的路径。(仅在递归模式下触发)
scannedDirectory
:每次目录扫描完成时触发(这个触发得很多)
关联对象:FileDetail
有一个与之关联的对象(包含在模块中),名为 FileDetail
。该对象由 fileAdded
和 fileChanged
事件返回,并且也包含在 directoryStructure
对象中,用于表示目录中的文件。FileDetail
对象具有以下属性
directory
:文件的父目录
fullPath
:包括文件目录在内的完整路径。
fileName
:仅文件名,不带路径
size
:文件的字节大小
extension
:文件的扩展名(.js、.txt 等)
accessed
:文件的最后访问日期
modified
:文件的最后修改日期
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。