閑古鳥

オールドプログラマの日記。プログラミングとか病気(透析)の話とか。

ファイルの更新時刻(タイムスタンプ)の比較

CompareFileTime という便利な API があることを、今日始めて知りました。

この関数を使用すれば、ふたつの FILETIME 構造体に収められている時刻を比較することができるようです。いちいち FILETIME をこねくりまわして自力で比較する必要など無かったと。

ネット上にあるファイルと前回ローカルにダウンロードしたファイルの更新時間を比較して、更新があったかどうかをチェックする処理を書いていたのですが、この関数のおかげでかなり処理を簡略化することができました。

HttpQueryInfo という指定した URL のヘッダを取得できる関数があるのですが、これの第二引数に HTTP_QUERY_LAST_MODIFIED を指定すると Last-Modified ヘッダを引っ張ってこれるんですがこの時に HTTP_QUERY_FLAG_SYSTEMTIME をあわせて指定すると時刻を SYSTEMTIME 構造体に収めてから返してくれるようになるので、それを SystemTimeToFileTime 関数を使って FILETIME に変換してやるとかなり幸せなことに。

/*!
    @param[in] file ローカルにあるファイルのフルパス(↓のキャッシュ)
    @param[in] uri  更新チェックを行いたいURI
*/
bool IsUpdate(const char* file, const char* uri)
{
    FILETIME fLocal = {}, fNet = {};
    fLocal = ファイルの更新時間を拾ってくる(); // こっちは割愛...

    // uri のアドレスにあるファイルの Last-Modified ヘッダを取得する
    HINTERNET hInet = ::InternetOpen("UserAgent", INTERNET_OPEN_TYPE_PRECONFIG, "", "", 0);
    HINTERNET hFile = ::InternetOpenUrl(hInet, uri, 0, 0, 0, 0);
    SYSTEMTIME st = {};
    unsigned long len = sizeof(SYSTEMTIME);
    ::HttpQueryInfo(hFile, HTTP_QUERY_LAST_MODIFIED  | HTTP_QUERY_FLAG_SYSTEMTIME, &st, &len, 0);
    ::SystemTimeToFileTime(&st, &fNet);
    ::InternetCloseHandle(hFile);
    ::InternetCloseHandle(hInet);

    return ::CompareFileTime(&fLocal, &fNet) == -1; // fNet格納されている時間の方が大きければ更新されているとみなす
}

みたいな感じ。ただ Last-Modified ヘッダの返す時刻とローカルにあるファイルのタイムスタンプとで、基準時間が違う場合どうなるかとかは、検証していないのでわかりませんが、多分うまくいかないと思う。あと、指定した URL が Last-Modified を返してくれない場合は 0 で埋まったままになるので、 IsUpdate の戻り値が必ず false になってしまう問題も。 Last-Modified がない場合は上限の値が埋めとくとかの対応が必要でしょう。ただそれでも、レスポンスヘッダを文字列として自力で処理するよりは遥かに簡単確実なものになった……はず。文字列として処理するにしても前述の問題はありますしね。

やっぱり手元に MSDN が欲しいです。ネットからも参照できますが、いちいち何かあるたびに参照する気にはなれないんですよねぇ。どうしても一番最初は Google にいってしまい、最適かどうかわからない解決策でとりあえず満足してしまう。で、後から今回みたいな書き直しが発生してくると。まあ、家で趣味としてやっているだけなので構いませんけど。会社には MSDN あるし。