by WebSurfer
2010年8月30日 16:21
クライアントがサーバーからファイルをダウンロードし、自分の HDD などに保存した場合、そのファイルの更新日時は HDD に保存した日時になります。
これをサーバー側で設定した日付にすることはできるでしょうか? ActiveX などのプラグインは使わないという条件でです。
結論から言えば、世間一般に使われている解凍ツールと互換性のある zip アーカイブを使えば可能です。
サーバーからは zip アーカイブに更新日時情報を含めて送信し、クライアント側で解凍して保存する時に zip アーカイブに含まれている更新日時に設定してもらうということです。
Web アプリケーションとしては、以下ような手順になります。
-
データベースまたはファイルシステムからバイト列データを取り出す。
-
既存のライブラリを利用して zip アーカイブをバイト列として作成。更新日時やファイル名はその時設定。
-
作成したバイト列を Response.BinaryWrite メソッドで HTTP 出力ストリームに書き込み
参考に、フリーのライブラリ SharpZipLib を使った例をアップしておきます。実行するには Bin フォルダに ICSharpCode.SharpZipLib.dll を入れておく必要があります。下記のサイトから入手できます。
The Zip, GZip, BZip2 and Tar Implementation For .NET
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="ICSharpCode.SharpZipLib.Zip" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
// 例として、SQL Server DB からデータを取得する
// ことで考えています。
ConnectionStringSettings cs =
ConfigurationManager.ConnectionStrings["MyDB"];
string connString = cs.ConnectionString;
SqlConnection connection = new SqlConnection(connString);
string query =
"SELECT filename, data FROM files WHERE id=@id";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("@id",
Convert.ToInt32(Request.QueryString["id"]));
string filename = null;
byte[] data = null;
try
{
connection.Open();
SqlDataReader reader = command.ExecuteReader();
if (reader.Read())
{
filename = (string)reader[0];
data = (byte[])reader[1];
}
}
finally
{
connection.Close();
}
if (filename == null || data == null)
{
// DB から取得できなかった場合の処置。
// ここでは単に return
return;
}
Response.AppendHeader("Content-Disposition",
"attachment; filename=" +
HttpUtility.UrlEncode(filename) + ".zip");
Response.ContentType = "application/x-zip-compressed";
// 何故かバイト列のサイズを指定した方が圧縮率
// が高くなる。圧縮前よりは小さくなるはずなの
// で、とりあえず data.Length とする。
byte[] zippedData = new byte[data.Length];
int zippedDataLength = 0;
using (MemoryStream memoryStream =
new MemoryStream(zippedData))
{
using (ZipOutputStream zipOutStream =
new ZipOutputStream(memoryStream))
{
// 圧縮度の設定。9 が最高
zipOutStream.SetLevel(9);
ZipEntry entry = new ZipEntry(filename);
// ここで更新日時を設定
entry.DateTime = new DateTime(2000, 1, 1);
zipOutStream.PutNextEntry(entry);
zipOutStream.Write(data, 0, data.Length);
zipOutStream.Finish();
// 圧縮後のサイズを取得。Finish() の前に置く
// のは NG。zipOutStream.Length は NG。
zippedDataLength = (int)zipOutStream.Position;
zipOutStream.Close();
}
}
Array.Resize(ref zippedData, zippedDataLength);
Response.BinaryWrite(zippedData);
Response.End();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
そもそも、ファイル名、更新日時その他のメタデータはファイルシステムが保持しているもので、ファイル本体には含まれていません。
ファイル名のみは、ブラウザが自動的に attachment; filename= に指定された名前で保存してくれますが(日本語の文字化けの問題はありますが)、その他の情報はダメなようです。
Content-Disposition ヘッダーフィールドにファイル名、作成日時、更新日時等の情報を追加してクライアントに送ることは可能です。でもそれは送る手段が用意されているというだけで、ブラウザが自動的にそれに書き換えてくれることはありません。