initial commit
[emacs-init.git] / nxhtml / nxhtml / html-upl / ftpsync.pl
1 #!/usr/bin/perl
2 #
3 # ftpsync.pl
4 #
5 # See attached README file for any details, or call
6 # ftpsync.pl -h
7 # for quick start.
8 #
9 # LICENSE
10 #
11 #    FTPSync.pl (ftpsync) is free software; you can redistribute it and/or modify
12 #    it under the terms of the GNU General Public License as published by
13 #    the Free Software Foundation; either version 2 of the License, or
14 #    (at your option) any later version.
15 #
16 #    This program is distributed in the hope that it will be useful,
17 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
18 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 #    GNU General Public License for more details.
20 #
21 #    You should have received a copy of the GNU General Public License
22 #    along with this program; if not, write to the Free Software
23 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24 #
25 # FTPSync.pl (ftpsync) is also eMail-Ware, which means that the initial author
26 # (Christoph Lechleitner) would like to get an eMail at ftpsync@ibcl.at, if
27 # - if anyone uses the script on production level,
28 # - if anyone distributes or advertises it in any way,
29 # - if anyone starts to (try to) improve it.
30 #
31 #
32 ################################################################################
33
34 #
35 # Options etc.
36 #
37 #print "Starting imports.\n"; # For major problem debugging
38 printf STDERR "argv=@ARGV\n";
39
40 use File::Find;
41 use File::Listing;
42 use Net::FTP;
43 use strict;
44 # flushing ...
45 use IO::Handle;
46 STDOUT->autoflush(1);
47 STDERR->autoflush(1);
48
49 sub dosync();
50 sub print_syntax();
51 sub print_options();
52 sub buildremotetree();
53 sub buildlocaltree();
54 sub listremotedirs();
55 sub parseRemoteURL();
56
57 # Option Variables
58 #print "Defining variables.\n"; # For major problem debugging
59 # meta
60 my $returncode=0;
61 my $configfile=$ENV{"HOME"}."/.ftpsync";
62 # basics
63 my $localdir="";
64 my $remoteURL="";
65 my $syncdirection="";
66 my $ftpuser="ftp";
67 my $ftppasswd="anonymous";
68 my $ftpserver="localhost";
69 my $ftpdir="";
70 my $ftptimeout=120;
71 my $syncoff=0;
72 # verbosity
73 my $doverbose=1;
74 my $dodebug=0;
75 my $doquiet=0;
76 my $doinfoonly=0;
77 my $infotext="";
78 my $docheckfirst=0;
79
80 # Read command line options/parameters
81 #print "Reading command line options.\n"; # For major problem debugging
82 my $curopt;
83 my @cloptions=();
84 for $curopt (@ARGV) {
85   if ($curopt =~ /^cfg=/) {
86     $configfile=$';
87     if (! -r $configfile) { print "Config file does not exist: ".$configfile."\n"; $returncode+=1; }
88   } else {
89     push @cloptions, $curopt;
90   }
91 }
92
93 # Read Config File, if given
94 my @cfgfoptions=();
95 if ($configfile ne "") {
96   if (-r $configfile) {
97     #print "Reading config file.\n"; # For major problem debugging
98     open (CONFIGFILE,"<$configfile");
99     while (<CONFIGFILE>) {
100       $_ =~ s/([        \n\r]*$|\.\.|#.*$)//gs;
101       if ($_ eq "") { next; }
102       if ( ($_ =~ /[^=]+=[^=]+/) || ($_ =~ /^-[a-zA-Z]+$/) ) { push @cfgfoptions, $_; }
103     }
104     close (CONFIGFILE);
105   } # else { print "Config file does not exist.\n"; } # For major problem debugging
106 } # else { print "No config file to read.\n"; } # For major problem debugging
107
108 # Parse Options/Parameters
109 print "Parsing all options.\n"; # For major problem debugging
110 my $noofopts=0;
111 for $curopt (@cfgfoptions, @cloptions) {
112   if ($curopt =~ /^-[a-zA-Z]/) {
113     my $i;
114     for ($i=1; $i<length($curopt); $i++) {
115       my $curoptchar=substr($curopt,$i,1);
116       $noofopts++;
117       if    ($curoptchar =~ /[cC]/)  { $docheckfirst=1; }
118       elsif ($curoptchar =~ /[dD]/)  { $dodebug=1; $doverbose=1; $doquiet=0; }
119       elsif ($curoptchar =~ /[gG]/)  { $syncdirection="get"; }
120       elsif ($curoptchar =~ /[hH?]/) { print_syntax(); exit 0; }
121       elsif ($curoptchar =~ /[iI]/)  { $doinfoonly=1; }
122       elsif ($curoptchar =~ /[pP]/)  { $syncdirection="put"; }
123       elsif ($curoptchar =~ /[qQ]/)  { $dodebug=0; $doverbose=0; $doquiet=1; }
124       elsif ($curoptchar =~ /[vV]/)  { $doverbose++; }
125       else  { print "ERROR: Unknown option: \"-".$curoptchar."\"\n"; $returncode+=1; }
126     }
127   }
128   elsif ($curopt =~ /^ftp:\/\/(([^@\/\\\:]+)(:([^@\/\\\:]+))?@)?([a-zA-Z01-9\.]+)\/(.*)/) {
129     $remoteURL = $curopt;
130     parseRemoteURL();
131     if ( $syncdirection eq "" )
132     { $syncdirection="get"; }
133   }
134   elsif ($curopt =~ /^[a-z]+=.+/) {
135     my ($fname, $fvalue) = split /=/, $curopt, 2;
136     if    ($fname eq "cfg")       { next; }
137     elsif ($fname eq "ftpdir")    { $ftpdir     =$fvalue;
138                                     if ($ftpdir ne "/") { $ftpdir=~s/\/$//; }
139                                     if ( $syncdirection eq "" ) { $syncdirection="get"; }
140                                   }
141     elsif ($fname =~ m/ftppass(w(or)?d)?/i)
142                                   { $ftppasswd=$fvalue;
143                                     if ( $syncdirection eq "" ) { $syncdirection="get"; }
144                                   }
145     elsif ($fname eq "ftpserver") { $ftpserver  =$fvalue;
146                                     if ( $syncdirection eq "" ) { $syncdirection="get"; }
147                                   }
148     elsif ($fname eq "ftpuser")   { $ftpuser    =$fvalue;
149                                     if ( $syncdirection eq "" ) { $syncdirection="get"; }
150                                   }
151     elsif ($fname eq "localdir")  { $localdir   =$fvalue; $localdir=~s/\/$//;
152                                     if ( $syncdirection eq "" ) { $syncdirection="put"; }
153                                   }
154     elsif ($fname eq "timeout")   { if ($fvalue>0) { $ftptimeout =$fvalue; } }
155   }
156   else {
157     if ($localdir eq "") {
158       $localdir = $curopt;
159       if ( $syncdirection eq "" )
160       { $syncdirection="put"; }
161     } else {
162       print "ERROR: Unknown parameter: \"".$curopt."\"\n"; $returncode+=1
163     }
164   }
165 }
166 if ($noofopts == 0) { print_syntax(); exit 0; }
167
168 if($ftpuser   eq "?") { print "User: ";     $ftpuser=<STDIN>;   chomp($ftpuser);   }
169 if($ftppasswd eq "?") { print "Password: "; $ftppasswd=<STDIN>; chomp($ftppasswd); }
170
171 if ($dodebug) { print_options(); }
172 # check options
173 if ( ($localdir  eq "") || (! -d $localdir) )
174 { print "ERROR: Local directory does not exist: ".$localdir."\n"; $returncode+=1; }
175 #if ($localdir  eq "")   { print "ERROR: No localdir given.\n";  $returncode+=1; }
176 #if ( ($remoteURL eq "") { print "ERROR: No remoteURL given.\n"; $returncode+=1; }
177 if ($ftpserver eq "") { print "ERROR: No FTP server given.\n"; $returncode+=1; }
178 if ($ftpdir    eq "") { print "ERROR: No FTP directory given.\n"; $returncode+=1; }
179 if ($ftpuser   eq "") { print "ERROR: No FTP user given.\n"; $returncode+=1; }
180 if ($ftppasswd eq "") { print "ERROR: No FTP password given.\n"; $returncode+=1; }
181 if ($returncode > 0) { die "Aborting due to missing or wrong options! Call ftpsync -? for more information.\n";  }
182
183
184 #print "Exiting.\n"; exit 0;
185
186 if ($dodebug) { print "\nFind out if ftp server is online & accessible.\n"; }
187 my $doftpdebug=($doverbose > 2);
188 my $ftpc = Net::FTP->new($ftpserver,Debug=>$doftpdebug,Timeout=>$ftptimeout) || die "Could not connect to $ftpserver\n";
189 if ($dodebug) { print "Logging in as $ftpuser with password $ftppasswd.\n" }
190 $ftpc->login($ftpuser,$ftppasswd) || die "Could not login to $ftpserver as $ftpuser\n";
191 my $ftpdefdir=$ftpc->pwd();
192 if ($dodebug) { print "Remote directory is now ".$ftpdefdir."\n"; }
193 if ($ftpdir !~ /^\//) # insert remote login directory into relative ftpdir specification
194 { if ($ftpdefdir eq "/")
195   { $ftpdir = $ftpdefdir . $ftpdir; }
196   else
197   { $ftpdir = $ftpdefdir . "/" . $ftpdir; }
198   if (!$doquiet)
199   { print "Absolute remote directory is $ftpdir\n"; }
200 }
201 if (substr($ftpdir, -1) eq "/") {
202   if (!$doquiet)
203   { print "  Remote directory ends in /, removing this\n"; }
204   chop($ftpdir);
205 }
206 if ($dodebug) { print "Changing to remote directory $ftpdir.\n" }
207 $ftpc->binary()
208     or die "Cannot set binary mode :\n\t" . $ftpc->message;
209 $ftpc->cwd($ftpdir)
210     or die "Cannot cwd to $ftpdir :\n\t" . $ftpc->message;
211 if ($ftpc->pwd() ne $ftpdir) {
212   my $pwd = $ftpc->pwd();
213   die "Could not change to remote base directory $ftpdir (at $pwd)\n"; }
214 if ($dodebug) { print "Remote directory is now ".$ftpc->pwd()."\n"; }
215
216 if (! $doquiet) { print "\nDetermine s offset.\n"; }
217 if ($syncdirection eq "put") { clocksync($ftpc,"syncfile"); }
218
219 #  local & remote tree vars
220 #chdir $localdir;
221 my $ldl=length($localdir) + 1;
222 #my $ldl=length($localdir);
223 my %localfiledates=();
224 my %localfilesizes=();
225 my %localdirs=();
226 my %locallinks=();
227
228 my %remotefilesizes=();
229 my %remotefiledates=();
230 my %remotedirs=();
231 my %remotelinks=();
232 my $curremotesubdir="";
233
234 # Build local & remote tree
235 if (! $doquiet) { print "\nBuilding local file tree.\n"; }
236 buildlocaltree();
237 if (! $doquiet) { print "\nBuilding remote file tree.\n"; }
238 buildremotetree();
239 listremotedirs();
240 #if ($dodebug) { print "Quitting FTP connection.\n" }
241 #$ftpc->quit();
242
243 #print "Exiting.\n"; exit 0;
244
245 # Work ...
246 if ($doinfoonly) { $docheckfirst=0; }
247 if ($docheckfirst)
248 { print "Simulating synchronization.\n";
249   $doinfoonly=1;
250   dosync();
251   $doinfoonly=0;
252   print "\nOK to really update files? (y/n) [n] ";
253   my $yn=<STDIN>;
254   if ($yn =~ /^y/i)
255   { print "OK, going to do it.\n";
256   }
257   else
258   { print "OK, exiting without actions.\n";
259     exit 1;
260   }
261 }
262 if ($doinfoonly)   { print "\nSimulating synchronization.\n"; }
263 elsif (! $doquiet) { print "\nStarting synchronization.\n"; }
264 dosync();
265
266 if (!$doquiet) { print "Done.\n"; }
267
268 if ($dodebug) { print "Quitting FTP connection.\n" }
269 $ftpc->quit();
270
271 exit 0;
272
273
274
275 #
276 # Subs
277 #
278
279 sub buildlocaltree() {
280   find (\&noticelocalfile, $localdir."/");
281   sub noticelocalfile {
282     if ($ldl > length($File::Find::name)) { return; }
283     #printf "name=%s, length(name)=%d, ldl=$ldl\n", $File::Find::name, length($File::Find::name);
284     my $relfilename=substr($File::Find::name,$ldl);
285     if (length($relfilename) == 0) { return; }
286     if (-d $_) {
287       if ($dodebug) { print "Directory: ".$File::Find::name."\n"; }
288       elsif (! $doquiet) { print ":"; }
289       $localdirs{$relfilename}="$relfilename";
290     }
291     elsif (-f $_) {
292       #my @curfilestat=lstat $File::Find::name;
293       my @curfilestat=lstat $_;
294       my $curfilesize=$curfilestat[7];
295       my $curfilemdt=$curfilestat[9];
296       if ($dodebug) { print "File: ".$File::Find::name."\n";
297                       print "Modified ".$curfilemdt."\nSize ".$curfilesize." bytes\n"; }
298       elsif (! $doquiet) { print "."; }
299       $localfiledates{$relfilename}=$curfilemdt;
300       $localfilesizes{$relfilename}=$curfilesize;
301     }
302     elsif (-l $_) {
303       if ($dodebug) { print "Link: ".$File::Find::name."\n"; }
304       elsif (! $doquiet) { print ","; }
305       $locallinks{$relfilename}="$relfilename";
306     } else {
307       #print "u ".$File::Find::name."\n";
308       if (! $doquiet) { print "Ignoring file of unknown type: ".$File::Find::name."\n"; }
309     }
310     #if (! ($doquiet || $dodebug)) { print "\n"; }
311     #print "File mode is ".@curfilestat[2]."\n";
312   }
313   if ($dodebug) {
314     print "Local dirs (relative to ".$localdir."/):\n";
315     my $curlocaldir="";
316     foreach $curlocaldir (keys(%localdirs))
317     { print $curlocaldir."/\n"; }
318     print "Local files (relative to ".$localdir."/):\n";
319     my $curlocalfile="";
320     foreach $curlocalfile (keys(%localfiledates))
321     { print $curlocalfile."\n"; }
322   }
323 }
324
325
326 sub buildremotetree() {
327   my @currecursedirs=();
328   #$ftpc->ls()
329   #  or die $ftpc->message . "\nCannot ls remote dir " . $ftpc->pwd();
330   my @rfl = $ftpc->dir();
331   # or @rfl=(); # we have to survive empty remote directories !!!
332   my $currf="";
333   my $curyear = (gmtime(time))[5] + 1900;
334   my %monthtonr=();
335   $monthtonr{"Jan"}=1; $monthtonr{"Feb"}=2; $monthtonr{"Mar"}=3; $monthtonr{"Apr"}=4; $monthtonr{"May"}=5; $monthtonr{"Jun"}=6;
336   $monthtonr{"Jul"}=7; $monthtonr{"Aug"}=8; $monthtonr{"Sep"}=9; $monthtonr{"Oct"}=10; $monthtonr{"Nov"}=11; $monthtonr{"Dec"}=12;
337   if ($dodebug) { print "Remote pwd is ".$ftpc->pwd()."\nDIRing.\n"; }
338   my $curlsline;
339   foreach $curlsline (parse_dir(\@rfl)) {
340     my ($cfname,$cftype,$cfsize,$cftime,$mode)=@$curlsline;
341     #if ($dodebug) { print "Analysing remote file/dir ".$currf."\n" };
342     if ( $cftype ) {
343       if ($cfname eq ".") { next; }
344       if ($cfname eq "..") { next; }
345       if (substr($cftype,0,1) eq 'l') {  # link, rest of string = linkto
346         my $curnrl;
347         if ($curremotesubdir eq "") { $curnrl = $cfname; }
348         else                        { $curnrl = $curremotesubdir."/".$cfname; }
349         $remotelinks{$curnrl}=$cfname;
350         if ($dodebug) { print "Link: ".$curnrl." -> ".$cfname."\n"; }
351       }
352       elsif ($cftype eq 'd') {
353         my $curnewrsd;
354         if ($curremotesubdir eq "") { $curnewrsd = $cfname; }
355         else                        { $curnewrsd = $curremotesubdir."/".$cfname; }
356         $remotedirs{$curnewrsd}=$curnewrsd;
357         if ($dodebug) { print "Directory: ".$curnewrsd."\n"; }
358         elsif (! $doquiet) { print ":"; }
359         push @currecursedirs, $cfname;
360       }
361       elsif ($cftype eq 'f') {  #plain file
362         my $curnewrf;
363         if ($curremotesubdir eq "") { $curnewrf = $cfname; }
364         else                        { $curnewrf = $curremotesubdir."/".$cfname; }
365         #$remotefiledates{$curnewrf}=$cftime;
366         $remotefiledates{$curnewrf}=$ftpc->mdtm($cfname)+$syncoff;
367         if ($remotefiledates{$curnewrf} le 0) { die "Timeout detecting modification time of $curnewrf\n"; }
368         $remotefilesizes{$curnewrf}=$cfsize;
369         if ($remotefilesizes{$curnewrf} lt 0) { die "Timeout detecting size of $curnewrf\n"; }
370         if ($dodebug) { print "File: ".$curnewrf."\n"; }
371         elsif (! $doquiet) { print "."; }
372       }
373       elsif (! $doquiet) { print "Unkown file: $curlsline\n"; }
374     }
375     elsif ($dodebug) { print "Ignoring.\n"; }
376   }
377   #recurse
378   my $currecurseddir;
379   foreach $currecurseddir (@currecursedirs)
380   { my $oldcurremotesubdir;
381     $oldcurremotesubdir=$curremotesubdir;
382     if ($curremotesubdir eq "") { $curremotesubdir = $currecurseddir; }
383     else                        { $curremotesubdir .= "/".$currecurseddir; }
384     my $curcwddir="";
385     if ($ftpdir eq "/")
386     { $curcwddir=$ftpdir.$curremotesubdir; }
387     else
388     { $curcwddir=$ftpdir."/".$curremotesubdir; }
389     if ($dodebug) { print "Change dir: ".$curcwddir."\n"; }
390     $ftpc->cwd($curcwddir)
391       or die "Cannot cwd to $curcwddir :\n\t" . $ftpc->message ;
392     if ($ftpc->pwd() ne $curcwddir) {
393       die "Could not cwd to $curcwddir :\n\t" . $ftpc->message ; }
394     if (! $doquiet) { print "\n"; }
395     buildremotetree();
396     $ftpc->cdup();
397     $curremotesubdir = $oldcurremotesubdir;
398   }
399 }
400
401
402 # Synchronize clocks.
403 sub clocksync {
404     my $conn = shift @_;
405     my $fn = shift @_;
406     my $fndidexist=1;
407
408     if(! -f $fn) {
409       open(SF, ">$fn") or die "Cannot create $fn for time sync option";
410       close(SF);
411       $fndidexist=0;
412     }
413     -z $fn or
414       die "File $fn for time sync must be empty.";
415     my $putsyncok=1;
416     $conn->put($fn) or $putsyncok=0;
417     if (!$putsyncok)
418     { unlink($fn);  # cleanup!
419       die "Cannot send timesync file $fn";
420     }
421
422     my $now_here1 = time();
423     my $now_there = $conn->mdtm($fn) or
424       die "Cannot get write time of timesync file $fn";
425     my $now_here2 = time();
426
427     if ($now_here2 < $now_there)      # remote is in the future
428     { $syncoff=($now_there - $now_here1);
429       $syncoff -= $syncoff % 60;
430       $syncoff = 0-$syncoff;
431     }
432     else
433     #if ($now_here1 > $now_there)      # remote is the past # or equal
434     { $syncoff=($now_here2 - $now_there);
435       $syncoff -= $syncoff % 60;
436     }
437
438     $conn->delete($fn);
439
440     my $hrs = int(abs($syncoff)/3600);
441     my $mins = int(abs($syncoff)/60) - $hrs*60;
442     my $secs = abs($syncoff) - $hrs*3600 - $mins*60;
443     if (! $doquiet) {
444       printf("Clock sync offset: %d:%02d:%02d\n", $hrs, $mins, $secs);
445     }
446     unlink ($fn) unless $fndidexist;
447 }
448
449
450 sub dosync()
451 {
452   chdir $localdir || die "Could not change to local base directory $localdir\n";
453   if ($syncdirection eq "put") {
454     # create dirs missing at the target
455     if    ($doinfoonly) { print "\nWould create new remote directories.\n"; }
456     elsif (! $doquiet)  { print "\nCreating new remote directories.\n"; }
457     my $curlocaldir;
458     foreach $curlocaldir (sort { return length($a) <=> length($b); } keys(%localdirs))
459     { if (! exists $remotedirs{$curlocaldir})
460       { if ($doinfoonly) { print $curlocaldir."\n"; next; }
461         if ($doverbose)  { print $curlocaldir."\n"; }
462         elsif (! $doquiet) { print "d"; }
463         if ($ftpc->mkdir($curlocaldir) ne $curlocaldir) { die "Could not create remote subdirectory $curlocaldir\n"; }
464       }
465     }
466     # copy files missing or too old at the target, synchronize timestamp _after_ copying
467     if    ($doinfoonly) { print "\nWould copy new(er) local files.\n"; }
468     elsif (! $doquiet)  { print "\nCopying new(er) local files.\n"; }
469     my $curlocalfile;
470     foreach $curlocalfile (sort { return length($b) <=> length($a); } keys(%localfiledates))
471     { my $dorefresh=0;
472       if    (! exists $remotefiledates{$curlocalfile}) {
473         $dorefresh=1;
474         $infotext="New: ".$curlocalfile." (".$localfilesizes{$curlocalfile}." bytes)\n";
475         if ($doinfoonly)   { print $infotext; next; }
476         elsif ($doverbose) { print $infotext; }
477         elsif (! $doquiet) { print "n"; }
478       }
479       elsif ($remotefiledates{$curlocalfile} < $localfiledates{$curlocalfile}) {
480         $dorefresh=1;
481         $infotext="Newer: ".$curlocalfile." (".$localfilesizes{$curlocalfile}." bytes, ".$localfiledates{$curlocalfile}." versus ".$remotefiledates{$curlocalfile}.")\n";
482         if ($doinfoonly) { print $infotext; next; }
483         if ($doverbose)  { print $infotext; }
484         elsif (! $doquiet) { print "u"; }
485       }
486       elsif ($remotefilesizes{$curlocalfile} != $localfilesizes{$curlocalfile}) {
487         $dorefresh=1;
488         $infotext="Changed (different sized): ".$curlocalfile." (".$localfilesizes{$curlocalfile}."  versus ".$remotefilesizes{$curlocalfile}." bytes)\n";
489         if ($doinfoonly) { print $infotext; next; }
490         if ($doverbose)  { print $infotext; }
491         elsif (! $doquiet) { print "u"; }
492       }
493       if (! $dorefresh) { next; }
494       if ($dodebug) { print "Really PUTting file ".$curlocalfile."\n"; }
495       if ($ftpc->put($curlocalfile, $curlocalfile) ne $curlocalfile)
496       { print STDERR "Could not put localfile $curlocalfile\n"; }
497       my $retries = 3;
498       while ( ($ftpc->size($curlocalfile) != (lstat $curlocalfile)[7]) and ($retries-- > 0) )
499       { if (! $doquiet) { print "Re-Transfering $curlocalfile\n"; }
500         if ($ftpc->put($curlocalfile, $curlocalfile) ne $curlocalfile)
501         { print STDERR "Could not re-put localfile $curlocalfile\n"; }
502       }
503       my $newremotemdt=$ftpc->mdtm($curlocalfile)+$syncoff;
504       utime ($newremotemdt, $newremotemdt, $curlocalfile);
505     }
506     # delete files too much at the target
507     if    ($doinfoonly) { print "\nWould delete obsolete remote files.\n"; }
508     elsif (! $doquiet)  { print "\nDeleting obsolete remote files.\n"; }
509     my $curremotefile;
510     foreach $curremotefile (keys(%remotefiledates))
511     { if (not exists $localfiledates{$curremotefile})
512       { if ($doinfoonly) { print $curremotefile."\n"; next; }
513         if ($doverbose)  { print $curremotefile."\n"; }
514         elsif (! $doquiet) { print "r"; }
515         if ($ftpc->delete($curremotefile) ne 1) { die "Could not delete remote file $curremotefile\n"; }
516       }
517     }
518     # delete dirs too much at the target
519     if    ($doinfoonly) { print "\nWould delete obsolete remote directories.\n"; }
520     elsif (! $doquiet)  { print "\nDeleting obsolete remote directories.\n"; }
521     my $curremotedir;
522     foreach $curremotedir (sort { return length($b) <=> length($a); } keys(%remotedirs))
523     { if (! exists $localdirs{$curremotedir})
524       { if ($doinfoonly) { print $curremotedir."\n"; next; }
525         if ($doverbose)  { print $curremotedir."\n"; }
526         elsif (! $doquiet) { print "R"; }
527         if ($ftpc->rmdir($curremotedir) ne 1) { die "Could not remove remote subdirectory $curremotedir\n"; }
528       }
529     }
530   } else { # $syncdirection eq "GET"
531     # create dirs missing at the target
532     if    ($doinfoonly) { print "\nWould create new local directories.\n"; }
533     elsif (! $doquiet)  { print "\nCreating new local directories.\n"; }
534     my $curremotedir;
535     foreach $curremotedir (sort { return length($a) <=> length($b); } keys(%remotedirs))
536     { if (! exists $localdirs{$curremotedir})
537       { if ($doinfoonly) { print $curremotedir."\n"; next; }
538         if ($doverbose)  { print $curremotedir."\n"; }
539         elsif (! $doquiet) { print "d"; }
540         mkdir($curremotedir) || die "Could not create local subdirectory $curremotedir\n";
541       }
542     }
543     # copy files missing or too old at the target, synchronize timestamp _after_ copying
544     if    ($doinfoonly) { print "\nWould copy new(er) remote files.\n"; }
545     elsif (! $doquiet)  { print "\nCopying new(er) remote files.\n"; }
546     my $curremotefile;
547     foreach $curremotefile (sort { return length($b) <=> length($a); } keys(%remotefiledates))
548     { my $dorefresh=0;
549       if    (! exists $localfiledates{$curremotefile}) {
550         $dorefresh=1;
551         $infotext="New: ".$curremotefile." (".$remotefilesizes{$curremotefile}." bytes)\n";
552         if ($doinfoonly) { print $infotext; next; }
553         if ($doverbose)  { print $infotext; }
554         elsif (! $doquiet) { print "n"; }
555       }
556       elsif ($remotefiledates{$curremotefile} > $localfiledates{$curremotefile}) {
557         $dorefresh=1;
558         $infotext="Newer: ".$curremotefile." (".$remotefilesizes{$curremotefile}." bytes, ".$remotefiledates{$curremotefile}." versus ".$localfiledates{$curremotefile}.")\n";
559         if ($doinfoonly) { print $infotext; next; }
560         if ($doverbose)  { print $infotext; }
561         elsif (! $doquiet) { print "u"; }
562       }
563       elsif ($remotefilesizes{$curremotefile} != $localfilesizes{$curremotefile}) {
564         $dorefresh=1;
565         $infotext="Changed (different sized): ".$curremotefile." (".$remotefilesizes{$curremotefile}." bytes)\n";
566         if ($doinfoonly) { print $infotext; next; }
567         if ($doverbose)  { print $infotext; }
568         elsif (! $doquiet) { print "c"; }
569       }
570       if (! $dorefresh) { next; }
571       if ($dodebug) { print "Really GETting file ".$curremotefile."\n"; }
572       my $rc=$ftpc->get($curremotefile, $curremotefile);
573       if ( ($rc eq undef) or ($rc ne $curremotefile) )
574       { print STDERR "Could not get file ".$curremotefile."\n"; }
575       my $retries=3;
576       while ( ($ftpc->size($curremotefile) != (lstat $curremotefile)[7]) and ($retries-- > 0) )
577       { if (! $doquiet) { print "Re-Transfering $curremotefile\n"; }
578         if ( ($rc eq undef) or ($rc ne $curremotefile) )
579         { print STDERR "Could not get file ".$curremotefile."\n"; }
580       }
581       my $newlocalmdt=$remotefiledates{$curremotefile};
582       utime ($newlocalmdt, $newlocalmdt, $curremotefile);
583     }
584     # delete files too much at the target
585     if    ($doinfoonly) { print "\nWould delete obsolete local files.\n"; }
586     elsif (! $doquiet)  { print "\nDeleting obsolete local files.\n"; }
587     my $curlocalfile;
588     foreach $curlocalfile (sort { return length($b) <=> length($a); } keys(%localfiledates))
589     { if (not exists $remotefiledates{$curlocalfile})
590       { if ($doinfoonly) { print $curlocalfile."\n"; next; }
591         if ($doverbose)  { print $curlocalfile."\n"; }
592         elsif (! $doquiet) { print "r"; }
593         if (unlink($curlocalfile) ne 1) { die "Could not remove local file $curlocalfile\n"; }
594       }
595     }
596     # delete dirs too much at the target
597     if    ($doinfoonly) { print "\nWould delete obsolete local directories.\n"; }
598     elsif (! $doquiet)  { print "\nDeleting obsolete local directories.\n"; }
599     my $curlocaldir;
600     foreach $curlocaldir (keys(%localdirs))
601     { if (! exists $remotedirs{$curlocaldir})
602       { if ($doinfoonly) { print $curlocaldir."\n"; next; }
603         if ($doverbose)  { print $curlocaldir."\n"; }
604         elsif (! $doquiet) { print "d"; }
605         rmdir($curlocaldir) || die "Could not remove local subdirectory $curlocaldir\n";
606       }
607     }
608   }
609 }
610
611
612 sub listremotedirs() {
613   if ($dodebug) {
614     print "Remote dirs (relative to ".$ftpdir."):\n";
615     my $curremotedir="";
616     foreach $curremotedir (keys(%remotedirs))
617     { print $curremotedir."/\n"; }
618     print "Remote files (relative to ".$ftpdir."):\n";
619     my $curremotefile="";
620     foreach $curremotefile (keys(%remotefiledates))
621     { print $curremotefile."\n"; }
622     print "Remote links (relative to ".$ftpdir."):\n";
623     my $curremotelink="";
624     foreach $curremotelink (keys(%remotelinks))
625     { print $curremotelink." -> ".$remotelinks{$curremotelink}."\n"; }
626   }
627 }
628 sub parseRemoteURL() {
629   if ($remoteURL =~ /^ftp:\/\/(([^@\/\\\:]+)(:([^@\/\\\:]+))?@)?([a-zA-Z01-9\.]+)\/(.*)/) {
630     #print "DEBUG: parsing ".$remoteURL."\n";
631     #print "match 1 = ".$1."\n";
632     #print "match 2 = ".$2."\n";
633     #print "match 3 = ".$3."\n";
634     #print "match 4 = ".$4."\n";
635     #print "match 5 = ".$5."\n";
636     #print "match 6 = ".$6."\n";
637     #print "match 7 = ".$7."\n";
638     if (length($2) > 0) { $ftpuser=$2; }
639     if (length($4) > 0) { $ftppasswd=$4; }
640     $ftpserver=$5;
641     $ftpdir=$6;
642     #if ($ftpdir eq "") { $ftpdir="/"; }
643   }
644 }
645
646
647 sub print_syntax() {
648   print "\n";
649   print "FTPSync.pl 1.27 (2004-08-23)\n";
650   print "\n";
651   print " ftpsync [ options ] [ localdir remoteURL ]\n";
652   print " ftpsync [ options ] [ remoteURL localdir ]\n";
653   print " options = [-dgpqv] [ cfg|ftpuser|ftppasswd|ftpserver|ftpdir=value ... ] \n";
654   print "   localdir    local directory, defaults to \".\".\n";
655   print "   ftpURL      full FTP URL, scheme\n";
656   print '               ftp://[ftpuser[:ftppasswd]@]ftpserver/ftpdir'."\n";
657   print "               ftpdir is relative, so double / for absolute paths as well as /\n";
658   print "   -c | -C     like -i, but then prompts whether to actually do work\n";
659   print "   -d | -D     turns debug output (including verbose output) on\n";
660   print "   -g | -G     forces sync direction to GET (remote to local)\n";
661   print "   -h | -H     turns debugging on\n";
662   print "   -i | -I     forces info mode, only telling what would be done\n";
663   print "   -p | -P     forces sync direction to PUT (local to remote)\n";
664   print "   -q | -Q     turnes quiet operation on\n";
665   print "   -v | -V     turnes verbose output on\n";
666   print "   cfg=        read parameters and options from file defined by value.\n";
667   print "   ftpserver=  defines the FTP server, defaults to \"localhost\".\n";
668   print "   ftpdir=     defines the FTP directory, defaults to \".\" (/wo '\"') \n";
669   print "   ftpuser=    defines the FTP user, defaults to \"ftp\".\n";
670   print "   ftppasswd=  defines the FTP password, defaults to \"anonymous\".\n";
671   print "\n";
672   print " Later mentioned options and parameters overwrite those mentioned earlier.\n";
673   print " Command line options and parameters overwrite those in the config file.\n";
674   print " Don't use '\"', although mentioned default values might motiviate you to.\n";
675   print "\n";
676 }
677
678
679 sub print_options() {
680   print "\nPrinting options:\n";
681   # meta
682   print "returncode    = ", $returncode    , "\n";
683   print "configfile    = ", $configfile    , "\n";
684   # basiscs
685   print "syncdirection = ", $syncdirection , "\n";
686   print "localdir      = ", $localdir      , "\n";
687   # FTP stuff
688   print "remoteURL     = ", $remoteURL     , "\n";
689   print "ftpuser       = ", $ftpuser       , "\n";
690   print "ftppasswd     = ", $ftppasswd     , "\n";
691   print "ftpserver     = ", $ftpserver     , "\n";
692   print "ftpdir        = ", $ftpdir        , "\n";
693   # verbsityosity
694   print "doverbose     = ", $doverbose     , "\n";
695   print "dodebug       = ", $dodebug       , "\n";
696   print "doquiet       = ", $doquiet       , "\n";
697   #
698   print "doinfoonly    = ", $doinfoonly    , "\n";
699   print "\n";
700 }