LoginSignup
4
2

More than 3 years have passed since last update.

Node.js - chokidar で CSVファイルの変更検知して deep-diff で差分比較した情報をログ出力する

Last updated at Posted at 2019-07-15

はじめに

Node.js - chokidar と forever でファイルとディレクトリの変更を検知してログする - Qiita」で実装したファイル検知の仕組みを応用して、CSVファイルの読み込みと差分検査を行う処理を実装してみました。

作るもの

今回の実装するアプリケーションの概要をまとめます。

前提条件

前提として、以下の npmnode がインストール済みであるものとします。

command_powershell
> node -v
v10.16.0

> npm -v
6.10.1

動作仕様

今回のアプリケーションは次のような構成で動作することを想定します。

image.png

アプリケーションは次のような機能を持ちます。

  • ファイルの変更検知は chokidar を使用します。
  • 変更検知する対象ディレクトリ複数の異なるディレクトリ です。
  • CSVファイルが更新されたら、既存のバックアップファイルと差分を比較します。
  • 比較結果はコンソール出力します。
  • 比較が終わったら、既存ファイルを更新されたファイルで上書きます。

実装

実装に関する情報をまとめます。

バージョン

関連モジュールのバージョンは次の通りです。

  • node : v10.16.0
  • npm : 6.10.1
  • chokidar : 3.0.2
  • fs : 0.0.1-security
  • csv-parse : 4.4.3
  • deep-diff : 1.0.2
  • forever : 1.0.0

初期設定

まず、リポジトリを作成し、必要なモジュールをインストールします。

command_bash
# プロジェクトフォルダの作成
> mkdir node-app && cd $_

# リポジトリの初期化
> npm init -y

# 関連モジュールのインストール
> npm install --save chokidar
> npm install fs
> npm install csv-parse
> npm install deep-diff

# Global Install.
> sudo npm install -g forever

最終的には次のようなディレクトリ構成になります。
矢印で示した資源は自分で作成します。

command_bash
> tree -L 2
├── backup
│   └── input.csv        <-- 既存のバックアップファイル
├── data
│   └── input.csv        <-- 更新するファイル(1)
├── data-001
│   └── input.csv        <-- 更新するファイル(2)
├── node_modules
├── package-lock.json
├── package.json
└── watch.js             <-- アプリケーション本体

コード

完成版のコードは次の通りです。

watch.js
watch.js
'use strict';

// require
var chokidar = require("chokidar");
const fs = require('fs');
const csvSync = require('csv-parse/lib/sync');
var diff = require('deep-diff')

// Constants
// ________________________________
// Monitoring Target Directory
const target_directory_01 = 'data/';
const target_directory_02 = 'data-001/';
const backup_filename = 'backup/input.csv';

// Something to use when events are received.
const log = console.log.bind(console);

// Initialize
// ________________________________
// initialize chokidar
var watcher = chokidar.watch(target_directory_01, {
    ignored: /[\/\\]\./,
    persistent: true
});
// Add monitoring target.
watcher.add(target_directory_02);

// Monitoring
// ________________________________
// Ready for changes
watcher.on('ready', function () {

    // ready
    log('Initial scan complete. Ready for changes');

    // watched Paths
    var watchedPaths = watcher.getWatched();
    log("watchedPaths :", watchedPaths);

    // Files
    // _ _ _ _ _ _ _ _ _ _ _ _ _ _
    // Detect File added
    watcher.on('add', function (path, stats) {
        log(`File ${path} has been added`);
        if (stats) log(`File ${path} added size to ${stats.size}`, stats);

        var watchedPaths = watcher.getWatched();
        log("watchedPaths :", watchedPaths);
    });

    // Detect File changed
    watcher.on('change', function (path, stats) {
        log(`File ${path} has been changed`);
        if (stats) log(`File ${path} changed size to ${stats.size}`, stats);

        // 変更前後の CSVファイルを読み込みオブジェクトに変換する。
        let updatedData = readCSV(path);
        let originalData = readCSV(backup_filename);

        // 変更前後の CSVファイルの差分を検査する。
        var differences = diff(originalData, updatedData);
        log("differences: ", differences);
        log("differences: ", JSON.stringify(differences));

        // 変更後のCSVファイルで、変更前のCSVファイルを上書きする。
        fs.copyFileSync(path, backup_filename);
    });

    // Detect File removed
    watcher.on('unlink', function (path) {
        log(`File ${path} has been removed`);
    });

    // Directories
    // _ _ _ _ _ _ _ _ _ _ _ _ _ _
    // Detect Directory added
    watcher.on('addDir', function (path) {
        log(`Directory ${path} has been added`);
    });

    // Detect Directory removed
    watcher.on('unlinkDir', function (path) {
        log(`Directory ${path} has been removed`);
    });

    // Error
    // _ _ _ _ _ _ _ _ _ _ _ _ _ _
    // Detect Watcher Error
    watcher.on('error', function (path) {
        log(`Watcher error: ${error}`);
    });

});

/**
 * CSVファイルを読み込む。
 * 
 * @param {String} path 
 * @return {Object} res 
 */
function readCSV(path) {
    let data = fs.readFileSync(path);
    let res = csvSync(data);    
    return res;
}

データ

事前に用意するテスト用の CSVデータは次の通りです。

data/input.csv
2019/07/15,yamada,000001,100
2019/07/16,tanaka,000100,240
2019/07/17,sasaki,010000,260
2019/07/19,suzuki,200000,400
2019/07/20,okayama,003000,301
backup/input.csv
2019/07/15,yamada,000001,100
2019/07/16,tanaka,000100,240
2019/07/17,sasaki,010000,260
2019/07/19,suzuki,200000,400
2019/07/20,okayama,003000,301

デバッグ

動作確認は次のように行います。
node watch.js でアプリケーションを起動したら、監視対象のフォルダ配下で適当な操作をしてみましょう。

command_bash
> node watch.js
Initial scan complete. Ready for changes
watchedPaths : {}

# `data/input.csv` に次の行を追加して保存します。
# 2019/07/20,okayama,003000,303

File data/input.csv has been changed
File data/input.csv changed size to 180 Stats {
  dev: 12,
  mode: 33279,
  nlink: 1,
  uid: 1000,
  gid: 1000,
  rdev: 0,
  blksize: 512,
  ino: 11258999068805782,
  size: 180,
  blocks: 0,
  atimeMs: 1563177207926.31,
  mtimeMs: 1563181200379.9946,
  ctimeMs: 1563181200379.9946,
  birthtimeMs: 1563181200379.9946,
  atime: 2019-07-15T07:53:27.926Z,
  mtime: 2019-07-15T09:00:00.380Z,
  ctime: 2019-07-15T09:00:00.380Z,
  birthtime: 2019-07-15T09:00:00.380Z }
differences:  [ DiffArray {
    kind: 'A',
    index: 5,
    item: DiffNew { kind: 'N', rhs: [Array] } } ]
differences:  [{"kind":"A","index":5,"item":{"kind":"N","rhs":["2019/07/20","okayama","003000","303"]}}]

おわりに

Node.js の使い勝手の良さに驚いています。

参考

今回の記事で参考にしたサイトを記載します。

csv-parse

use strict

fs

deep-diff

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2