(2002.12.07/2002.12.12更新)
| 目 的: | 複数のPCと一つのftpサーバ(HP用)間でファイルの同期を取る。具体的には、仕事場と自宅の両方でHPのメンテナンスがしたい。 |
| 問題点: | @RxFtpを使用した場合、ftpサーバ上のファイルのフル形式のタイムスタンプを簡単に取得する方法がない。 Aファイルのタイムスタンプはダウンロード/アップロードの度に更新されてしまう。 |
【注】古くて時刻がわからないファイルは、すべて00:00:00として扱えばよい。そうすると、ローカルのファイルと中身が同じであっても一度は更新対象になってしまうが、同時にタイムスタンプも更新されるため(Aの特徴)、その後は更新対象から外れる。もっとも、半年立つと自動的に省略形表示になって時間情報が欠落するので、中身の変更の有無にかかわらず半年に一度は更新されることにはなるが。
理想的な解決方法としては、アップ/ダウン時のタイムスタンプを、元ファイルのタイムスタンプとすること。ローカル側ならばそれもできないことはない。実はOS/2だとタイムスタンプの変更はちと厄介で、REXXだけで処理するのは厳しいかも知れないが(C++には専用の関数がある)、wgetを使えば何とかなる。しかし、ftpサーバ上のファイルのタイムスタンプを任意に変更することは不可能ではないか? 少なくとも、RxFTPでは無理。QUOTEで処理できる命令でタイムスタンプが変更出来れば別だが望み薄。
【独り言】ftpサーバでは根本的に別の考え方でタイムスタンプを付けてんのかな? たとえば、パケットのヘッダ部から時間情報を抽出するとか…。で、それがなければ現在時刻を設定するとか…。その辺りはでんでん知らないからなあ。本気でやるなら、こっちの方向で考えるべきだろう。
ダウンロードするときは、最終ダウンロード日時よりも新しい更新履歴から落すべきファイルを判断する。ただし、自マシンでアップしたファイルはダウンする必要がないので外す。具体的には、以下のような感じになる。
* 20021011221545 mypc1 2 /html/index.htm /html/camera/om1.htm * 20021205103614 mypc2 5 /html/pc/iroiro.htm /html/index.htm ……行頭の「*」はデータの区切りのマーカー。で、このファイルは当然累積的に大きくなっていくから、どこかでサイズ調整が必要。まあ、上限を3000行程度にして、古いものから順に廃棄して行くのが妥当かと…(行数ではなく時間で区切るべきかな?)。
また、シンクロ(アップ&ダウン)の具体的な順序は、
2.の問題は技術的にそれほど難しくない。ダウンロード済みのファイル名を記憶しておいて、重複ダウンロードを避ければよい。
3.の問題は難問だが、これは手法に寄らず、更新管理には常につきまとう問題。運用に気を付けるか、不整合を発見したときにワーニングを出すなどの処置で対処するしかない。
4.の問題はもうどうしようもない。しかし、1.の問題の特殊な場合と見なせばよいわけで、他の方法でアップ/ダウンした後は再初期化処理を行うという方法で対処する。まあ、リモートのタイムスタンプを変更できれば一気に解決するんだけどねえ。
/*****************************************************************************/
/* */
/* ftpsync -- remote-local synchronizer 2002.12.04 Nogure,Ten */
/* */
/* 2002.12.07 テスト版作成(アップロードのみ) */
/* 2002.12.09 テスト版作成(ダウンロードも) */
/* 2002.12.15 若干の改良(確認機能) */
/* 2003.01.01 非接続時の中断機能 */
/* */
/*****************************************************************************/
Call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
Call SysLoadFuncs
rc = RxFuncAdd("FtpLoadFuncs","rxFtp","FtpLoadFuncs")
rc = FtpLoadFuncs()
NUMERIC DIGITS 14
SAY '------------------------------------'
SAY 'ftpsync -- remote-local synchronizer'
SAY ' ver.alpha-1 2002.12.09 Nogure,Ten'
SAY '------------------------------------'
envfile='ftpsync.env'; /* 環境ファイル(最終シンクロ日)*/
updfile='ftpsync.dat'; /* 更新履歴ファイル */
tmpfile='ftpsync.$$$'; /* テンポラリ・ファイル */
maxline=3000; /* 更新履歴ファイルの最大行数 */
/*---------------------------------------------------------------------------*/
/* 環境ファイル・テンポラリファイルの指定 */
/*---------------------------------------------------------------------------*/
PARSE SOURCE ThisProgram
ThisProgram=DelWord(ThisProgram,1,2);
ProgDir=FileSpec("Drive",ThisProgram)||FileSpec("Path",ThisProgram);
envfile=ProgDir||envfile;
tmpfile=ProgDir||tmpfile;
CALL SysFileDelete tmpfile;
/*---------------------------------------------------------------------------*/
/* 環境ファイルの読み込み */
/*---------------------------------------------------------------------------*/
Do While Lines(envfile)
st=Linein(envfile);
PARSE VAR st stKey stVal dmy;
If stKey='HOSTNAME' Then hostname =stVal;
If stKey='REMOTEHOST' Then remotehost =stVal;
If stKey='USERID' Then userid =stVal;
If stKey='PASSWORD' Then password =stVal;
If stKey='REMOTEHOME' Then remotehome =stVal;
If stKey='LOCALHOME' Then localhome =stVal;
If stKey='LASTSYNCHRO' Then lastsynchro =stVal;
End;
rc=STREAM(envfile,'C','C');
/*---------------------------------------------------------------------------*/
/* 現在の全ローカル・ファイルのタイムスタンプの取得 */
/*---------------------------------------------------------------------------*/
Call SysFileTree localhome||'\*.*', files, 'FSO'
Do n=1 to files.0
nowFile.n.name=files.n;
dt = stream(nowFile.n.name,'C','query datetime');
If substr(dt,7,2)>80 then century=19; else century=20
nowFile.n.time = century||substr(dt,7,2)||left(dt,2)||substr(dt,4,2),
||substr(dt,11,2)||substr(dt,14,2)||substr(dt,17,2);
End;
nowFile.0=files.0
/*---------------------------------------------------------------------------*/
/* 更新アップロードするファイルの抽出 */
/*---------------------------------------------------------------------------*/
m=0
Do n=1 to nowFile.0
If nowFile.n.time>lastsynchro Then
Do
m=m+1;
updFile.m.name=nowFile.n.name
SAY '*** NEW/UPD ***' nowFile.n.name
End;
End;
updFile.0=m;
/*---------------------------------------------------------------------------*/
/* 更新アップロード対象がない場合の処理 */
/*---------------------------------------------------------------------------*/
IF updFile.0=0 THEN
DO
SAY;
SAY '*** NO NEW FILE (UPLOAD) ***';
SAY;/* ダウンロードしないで終わっちゃいかんのだ */
END;
/*---------------------------------------------------------------------------*/
/* ユーザー情報と転送モードの設定 */
/*---------------------------------------------------------------------------*/
rc = FtpSetUser(remotehost,userid,password);
rc = FtpSetBinary("Binary");
/*---------------------------------------------------------------------------*/
/* アップロード */
/*---------------------------------------------------------------------------*/
IF updFile.0>0 THEN
DO
SAY '=== HIT ENTER KEY TO UPLOAD ==='
PULL
END;
Do n=1 to updFile.0
p=length(localhome)+1;
remoteFname=remotehome||substr(updFile.n.name,p);
remoteFname=translate(remoteFname,'/','\');
SAY '*** UPLOAD' updFile.n.name '>>' remoteFname;
rc = FtpPut(updFile.n.name, remoteFname);
IF RC<>0 THEN
DO
SAY "!!! CONNECTION FAILED !!!"
EXIT;
END;
End;
/*---------------------------------------------------------------------------*/
/* 更新履歴ファイルのダウンロード */
/*---------------------------------------------------------------------------*/
/* 本来はローカルファイルは一旦削除しておくべきだが… */
remoteUpdfile=remotehome||'/'||updfile;
localUpdfile=ProgDir||updfile;/* 保存場所は一考の余地ありだが… */
rc=FtpGet(localUpdfile,remoteUpdfile);
IF (rc<>0)&(FTPERRNO='FTPHOST') THEN
DO
SAY "!!! CONNECTION FAILED !!!"
EXIT;
END;
/*---------------------------------------------------------------------------*/
/* ftpsync.datがアップロードされていない場合(ダウンロードしない) */
/*---------------------------------------------------------------------------*/
rc=STREAM(localUpdfile,'c','OPEN READ')
IF rc\='READY:' THEN
DO
SAY;
SAY '*** FTPSYNC.DAT NOT FOUND ! ***';
SAY '*** NO FILE DOWNLOAD ***'
SAY;
CALL LINEOUT localUpdfile,''; /* これでダウンロード・ループをスキップ */
END;
/*---------------------------------------------------------------------------*/
/* ダウンロードするファイルのピックアップ */
/*---------------------------------------------------------------------------*/
flFirst=1; /* 初回処理判別フラグ */
L=0; /* ftpsync.dat行数カウンタ */
dlFile.0=0; /* DLピックアップ済みファイル */
nDL=0; /* DLピックアップ済みファイル数 */
DO WHILE LINES(localUpdfile)>0
st=LINEIN(localUpdfile);L=L+1;
IF left(st,1)='*' Then
DO
PARSE VAR st dmy dt id nfl dmy/* 日時、ホスト名、ファイル数 */
IF flFirst=1 THEN
Do/* 初回処理:場合によっては全ファイルのDLを実行 */
If dt>lastsynchro Then /*最終シンクロ日が最も古いアップ日よりも古い*/
Do
SAY '*** ALL FILE DOWN LOAD(未実装)***'
/*CALL ALL FILE DOWNLOAD*/
/*LEAVE;*//*Do Whileループから脱出*/
/*後ろのルーチンでLを参照しているので注意*/
End;
End;
flFirst=0;/* lastsynchroよりも新しく、かつ他のマシンでアップしたもの */
If (dt>lastsynchro)&(id<>hostname) Then
Do n=1 to nfl
st=linein(localUpdfile);L=L+1;
flgDL=0;/* 重複DLの回避 */
DO nn=1 to dlFile.0
IF st=dlFile.nn THEN
DO
flgDL=1;/* 重複発見 */
LEAVE;
END;
END;
IF flgDL=0 THEN/* DLするファイルのピックアップ */
DO
SAY '*** DOWNLOAD ***' st;
nDL=nDL+1;
dlFile.nDL=st;
dlFile.0=nDL;
END;
End;
End;
End;
rc=stream(localUpdfile,'C','C');
IF dlFile.0=0 THEN
DO
SAY ;
SAY '*** NO NEW FILE (DOWNLOAD) ***';
SAY ;
END;
/*---------------------------------------------------------------------------*/
/* ダウンロードの実行 */
/*---------------------------------------------------------------------------*/
IF dlFile.0>0 THEN
DO
SAY '=== HIT ENTER KEY TO DOWNLOAD ==='
PULL
END;
Do n=1 to dlFile.0
p=length(remotehome)+1;
localFname=localhome||substr(dlFile.n,p);
localFname=TRANSLATE(localFname,'\','/');
SAY '*** DOWNLOAD' dlFile.n '>>' localFname;
rc=FtpGet(localFname,dlFile.n);
IF RC<>0 THEN
DO
SAY "!!! CONNECTION FAILED !!!"
EXIT;
END;
End;
/*---------------------------------------------------------------------------*/
/* 最終シンクロ日時の更新 */
/*---------------------------------------------------------------------------*/
TIMES=TIME();
lastsynchro=DATE(sorted)||Left(TIMES,2)|| Substr(times,4,2)||right(times,2);
/*---------------------------------------------------------------------------*/
/* 更新履歴ファイルの更新 */
/*---------------------------------------------------------------------------*/
IF updFile.0>0 THEN
Do
LL=L+updFile.0+1; /* +1はヘッダ分(時刻とマシン名とファイル数) */
Ld=LL-maxline; /* ファイルの上限は行数で判断している k*/
/*** 古いデータを書き出す ***/
Do n=1 to L /*Lはftpsync.datの行数(前の処理で取得)*/
st=Linein(localUpdfile);
IF n>=LD THEN CALL LINEOUT tmpfile, st;
End;
CALL STREAM localUpdfile,'C','C'
/*** 更新分のデータを追加する ***/
CALL LINEOUT tmpfile,'*' lastsynchro hostname updFile.0
Do n=1 to updFile.0
p=length(localhome)+1;
remoteFname=remotehome||substr(updFile.n.name,p);
remoteFname=translate(remoteFname,'/','\');
CALL LINEOUT tmpfile, remoteFname;
End;
CALL STREAM tmpfile,'C','C'
/*** ローカルのftpsync.datの更新 ****/
CALL SysFileDelete localUpdfile;
'@COPY' tmpfile localUpdfile '>nul'
CALL SysFileDelete tmpfile;
/*** 更新履歴ファイルのアップロード***/
SAY '*** FTPSYNC.DAT UPLOAD ***'
rc = FtpPut(localUpdfile, remoteUpdfile);
End;
/*---------------------------------------------------------------------------*/
/* ftpサーバの切断 */
/*---------------------------------------------------------------------------*/
rc = FtpLogoff();
/*---------------------------------------------------------------------------*/
/* 環境ファイルの最終シンクロ日時の更新 */
/*---------------------------------------------------------------------------*/
CALL SysFileDelete envfile;
CALL LINEOUT envfile,'HOSTNAME' hostname
CALL LINEOUT envfile,'REMOTEHOST' remotehost
CALL LINEOUT envfile,'USERID' userid;
CALL LINEOUT envfile,'PASSWORD' password;
CALL LINEOUT envfile,'REMOTEHOME' remotehome;
CALL LINEOUT envfile,'LOCALHOME' localhome;
CALL LINEOUT envfile,'LASTSYNCHRO' lastsynchro;
CALL STREAM envfile,'C','C';
SAY
SAY '*** FTPSYNC.ENV UPDATE ***'
EXIT;
HOSTNAME mypc1 REMOTEHOST www.abc.efg.ne.jp USERID pokosuke PASSWORD tororoimo REMOTEHOME /html LOCALHOME g:\myhome\html LASTSYNCHRO 20021211122417