無用なファイルのコピーを防ぐために、ファイルの更新日時を取得して比較するということをプログラムから行ってみる、3回目です。
前回は、Visual C#でやってみましたが期待する結果が得られなかったので、今回はVisual C++で直接WindowsのAPIを叩いてみます。
Windows APIを叩くので、Visual C♯は使えません。そこで、昔使い込んでいたVisual C++をインストールすることにしました。
それにしても、なぜにMicrosoft製品はインストールが大仰なのでしょうか。相当時間がかかったあげく、立ち上げてみたら一部にService Pack 1が当たってないから終了するとか。じゃ、Service Pack 1を当てるにもあまりに待ち時間が長いので、ついつい昼寝してしまったではないですか!
Visual C#のときに、すでに同じ目に遭っていたのですが、なぜにVisual C++のインストール後にも同じことをいわれなければならないのか、理解に苦しみます。
何はともあれ、Visual C++のインストールが済みました。起動してみましょう。実は、Visual C++を使うのはバージョン6以来なのです。仕事ではVisual Basicばかりでしたからね。ファミリーの中ではもっともそっけない画面に好感が持てます。
さっそくプロジェクトを作りましょう。Visual C♯と同じようにコンソールアプリケーションとします。ただし、Visual C++ではコンソールアプリケーションにも2種類ありますから注意しましょう。CLRというのは.NET Frameworkのアプリケーションになってしまいますので、Win32を選択します。すると、すぐにプログラムソースが開きます。冒頭のinclude文が懐かしいですね。
さっそく、コードを入れていきましょう。
// checkfiletime.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
#include "stdafx.h"
#include "windows.h"
#include "windef.h"
#include "winbase.h"
int _tmain(int argc, _TCHAR* argv[])
{
const _TCHAR *targetfile = L"MailBox\\Account0\\junk.idx";
const _TCHAR *pathname1 = L"C:\\Users\\user\\AppData\\Roaming\\Justsystem\\Shuriken\\user\\";
const _TCHAR *pathname2 = L"Z:\\Backup\\Shuriken\\";
_TCHAR filename1[256], filename2[256];
wsprintf( filename1, L"%s%s", pathname1, targetfile);
wsprintf( filename2, L"%s%s", pathname2, targetfile);
HANDLE handle1 = CreateFile(filename1, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if( handle1 == NULL )
return 0;
HANDLE handle2 = CreateFile(filename2, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if( handle1 == NULL )
return 0;
FILETIME datetime1, datetime2;
GetFileTime( handle1, NULL, NULL, &datetime1);
GetFileTime( handle2, NULL, NULL, &datetime2);
printf("File1: %08lX %08lX\n", datetime1.dwHighDateTime, datetime1.dwLowDateTime);
printf("File2: %08lX %08lX\n", datetime2.dwHighDateTime, datetime2.dwLowDateTime);
SYSTEMTIME stFile1, stFile2;
FileTimeToSystemTime(&datetime1, &stFile1);
FileTimeToSystemTime(&datetime2, &stFile2);
printf( "File1: %d年 %d月 %d日 %d時 %d分 %d秒%d\n", stFile1.wYear, stFile1.wMonth,
stFile1.wDay, stFile1.wHour,
stFile1.wMinute , stFile1.wSecond, stFile1.wMilliseconds );
printf( "File2: %d年 %d月 %d日 %d時 %d分 %d秒%d\n", stFile2.wYear, stFile2.wMonth,
stFile2.wDay, stFile2.wHour,
stFile2.wMinute , stFile2.wSecond, stFile2.wMilliseconds );
CloseHandle(handle1);
CloseHandle(handle2);
return 0;
}
要はCのプログラムなので、やたら回りくどいコードになります。.NET Frameworkではファイルの更新日時を取るのはファイルを指定してGetLastWriteTimeメソッドを呼び出すだけと簡単でしたが、Win32ではいったんCreateFile関数でファイルを開いてハンドルを取得し、そのハンドルを使ってGetFileTime関数で更新日時を取得する必要があります。
しかも、取得した日時はUTC準拠のミリ秒単位のものですから、それをシステム時間などに変換してやらなくてはなりません。これをFileTimeToSystemTime関数で行います。
そして仕事が終われば忘れないようにハンドルをCloseHandle関数でクローズしておきます。
取得した日時をミリ秒まで表示してみましょう。
今度は、はっきり違いが出ましたね。仮想ドライブにあるMac側のファイルは、どうやらミリ秒の値が0になるようです。これに対して、ローカルのNTFSにあるファイルはきっちりとミリ秒までの値を保持しています。ですから、ミリ秒の分だけ新しいと判断され、毎回コピーが実行されていたわけです。
Mac側のファイル時間が荒っぽいのを問題とすべきか、ミリ秒単位まできっちり比較して処理するプログラムを律儀と思うか…。実は気になったのですが、これはネットワークドライブの仕様なのでしょうか?もしそうだとすれば、ネットワーク越しのコピーでも、同じような症状になるはずです。
しかし、CopyToなどのバックアップ・同期ソフトでは、ネットワーク越しにも期待どおり動作します。
これは調べてみる価値がありそうですね。暇なときにやってみましょう。