$ include "seed7_05.s7i";
include "stdio.s7i";
include "osfiles.s7i";
include "time.s7i";
include "duration.s7i";
const type: syncFlags is new struct
var boolean: doChanges is TRUE;
var boolean: doCopy is TRUE;
var boolean: doUpdate is TRUE;
var boolean: doTimeCorrection is TRUE;
var boolean: doRemoveFileAtDest is FALSE;
var boolean: doOverwriteNewerDestFile is FALSE;
end struct;
const proc: syncFile (in string: sourcePath, in string: destPath,
in syncFlags: flags) is forward;
const proc: syncDir (in string: sourcePath, in string: destPath,
in syncFlags: flags) is func
local
var array string: sourceContent is 0 times "";
var array string: destContent is 0 times "";
var boolean: updateMtime is FALSE;
var integer: sourceIndex is 1;
var integer: destIndex is 1;
var string: sourceName is "";
var string: destName is "";
begin
if getMTime(sourcePath) + 1 . SECONDS >= getMTime(destPath) then
updateMtime := TRUE;
end if;
sourceContent := readDir(sourcePath);
destContent := readDir(destPath);
while sourceIndex <= length(sourceContent) and
destIndex <= length(destContent) do
sourceName := sourceContent[sourceIndex];
destName := destContent[destIndex];
if sourceName = destName then
syncFile(sourcePath & "/" & sourceName,
destPath & "/" & destName, flags);
incr(sourceIndex);
incr(destIndex);
elsif sourceName < destName then
syncFile(sourcePath & "/" & sourceName,
destPath & "/" & sourceName, flags);
incr(sourceIndex);
else
syncFile(sourcePath & "/" & destName,
destPath & "/" & destName, flags);
incr(destIndex);
end if;
end while;
while sourceIndex <= length(sourceContent) do
sourceName := sourceContent[sourceIndex];
syncFile(sourcePath & "/" & sourceName,
destPath & "/" & sourceName, flags);
incr(sourceIndex);
end while;
while destIndex <= length(destContent) do
destName := destContent[destIndex];
syncFile(sourcePath & "/" & destName,
destPath & "/" & destName, flags);
incr(destIndex);
end while;
if updateMtime then
if flags.doTimeCorrection then
if flags.doChanges then
setMTime(destPath, getMTime(sourcePath));
end if;
end if;
end if;
end func;
const func boolean: equalFileContent (in string: sourcePath, in string: destPath) is func
result
var boolean: equal is FALSE;
local
var file: sourceFile is STD_NULL;
var file: destFile is STD_NULL;
var string: sourceBlock is "";
var string: destBlock is "";
begin
sourceFile := open(sourcePath, "r");
if sourceFile <> STD_NULL then
destFile := open(destPath, "r");
if destFile <> STD_NULL then
equal := TRUE;
while equal and not eof(sourceFile) and not eof(destFile) do
sourceBlock := gets(sourceFile, 67108864);
destBlock := gets(destFile, 67108864);
equal := sourceBlock = destBlock;
end while;
if not eof(sourceFile) or not eof(destFile) then
equal := FALSE;
end if;
close(destFile);
end if;
close(sourceFile);
end if;
end func;
const proc: syncFile (in string: sourcePath, in string: destPath,
in syncFlags: flags) is func
local
var fileType: sourceType is FILE_ABSENT;
var fileType: destType is FILE_ABSENT;
var time: sourceTime is time.value;
var time: destTime is time.value;
var array string: dirContent is 0 times "";
var string: fileName is "";
begin
sourceType := fileTypeSL(sourcePath);
destType := fileTypeSL(destPath);
if sourceType = FILE_ABSENT then
if destType = FILE_DIR then
if flags.doRemoveFileAtDest then
writeln("remove directory " <& literal(destPath));
if flags.doChanges then
removeTree(destPath);
end if;
end if;
elsif destType <> FILE_ABSENT then
if flags.doRemoveFileAtDest then
writeln("remove file " <& literal(destPath));
if flags.doChanges then
removeFile(destPath);
end if;
end if;
end if;
elsif sourceType = FILE_SYMLINK then
if destType = FILE_ABSENT then
block
if flags.doCopy then
if flags.doChanges then
cloneFile(sourcePath, destPath);
end if;
writeln("copy symlink " <& literal(sourcePath) <& " to " <& literal(destPath));
end if;
exception
catch FILE_ERROR:
writeln(" *** Cannot copy symlink " <& literal(sourcePath) <& " to " <& literal(destPath));
end block;
elsif destType = FILE_SYMLINK then
if readLink(sourcePath) <> readLink(destPath) then
writeln(" *** Source link " <& literal(sourcePath) <&
" and destination link " <& literal(destPath) <&
" point to different paths");
end if;
elsif destType = FILE_REGULAR and
fileSize(sourcePath) = fileSize(destPath) and equalFileContent(sourcePath, destPath) then
writeln(" *** Destination " <& literal(destPath) <& " is not a symbolic link but has the same content as the source");
else
writeln(" *** Destination " <& literal(destPath) <& " is not a symbolic link");
end if;
elsif sourceType = FILE_DIR then
if destType = FILE_ABSENT then
if flags.doCopy then
writeln("copy directory " <& literal(sourcePath) <& " to " <& literal(destPath));
if flags.doChanges then
makeDir(destPath);
syncDir(sourcePath, destPath, flags);
setMTime(destPath, getMTime(sourcePath));
end if;
end if;
elsif destType = FILE_DIR then
syncDir(sourcePath, destPath, flags);
else
writeln(" *** Destination " <& literal(destPath) <& " is not a directory");
end if;
elsif sourceType = FILE_REGULAR then
if destType = FILE_ABSENT then
block
if flags.doCopy then
writeln("copy file " <& literal(sourcePath) <& " to " <& literal(destPath));
if flags.doChanges then
cloneFile(sourcePath, destPath);
end if;
end if;
exception
catch FILE_ERROR:
writeln(" *** Cannot copy file " <& literal(sourcePath) <& " to " <& literal(destPath));
end block;
elsif destType = FILE_REGULAR then
sourceTime := getMTime(sourcePath);
destTime := getMTime(destPath);
if sourceTime > destTime + 1 . SECONDS then
if fileSize(sourcePath) = fileSize(destPath) and equalFileContent(sourcePath, destPath) then
if flags.doTimeCorrection then
writeln("Correct time of identical files " <& literal(sourcePath) <& " - " <& literal(destPath));
if flags.doChanges then
setMTime(destPath, sourceTime);
end if;
end if;
else
if flags.doUpdate then
writeln("update file " <& literal(sourcePath) <& " to " <& literal(destPath));
if flags.doChanges then
removeFile(destPath);
cloneFile(sourcePath, destPath);
end if;
end if;
end if;
elsif sourceTime < destTime - 1 . SECONDS then
if fileSize(sourcePath) = fileSize(destPath) and equalFileContent(sourcePath, destPath) then
if flags.doTimeCorrection then
writeln("Correct time of identical files " <& literal(sourcePath) <& " - " <& literal(destPath));
if flags.doChanges then
setMTime(destPath, sourceTime);
end if;
end if;
elsif flags.doOverwriteNewerDestFile then
writeln("replace newer dest file " <& literal(sourcePath) <& " to " <& literal(destPath));
if flags.doChanges then
removeFile(destPath);
cloneFile(sourcePath, destPath);
end if;
else
if flags.doUpdate then
writeln(" *** Destination newer " <& literal(sourcePath) <& " - " <& literal(destPath));
end if;
end if;
elsif fileSize(sourcePath) <> fileSize(destPath) then
if flags.doUpdate then
writeln("Correct file " <& literal(sourcePath) <& " to " <& literal(destPath));
if flags.doChanges then
removeFile(destPath);
cloneFile(sourcePath, destPath);
end if;
end if;
end if;
else
writeln(" *** Destination " <& literal(destPath) <& " is not a regular file");
end if;
else
writeln(" *** Source " <& literal(sourcePath) <& " has file type " <& sourceType);
end if;
end func;
const proc: main is func
local
var integer: numOfFileNames is 0;
var string: parameter is "";
var string: fromName is "";
var string: toName is "";
var boolean: commandOptionProvided is FALSE;
var boolean: error is FALSE;
var syncFlags: flags is syncFlags.value;
var string: answer is "";
begin
if length(argv(PROGRAM)) < 2 then
writeln("Sydir7 Version 1.1 - Utility to synchronize directory trees");
writeln("Copyright (C) 2009 - 2019, 2021, 2023 Thomas Mertes");
writeln("This is free software; see the source for copying conditions. There is NO");
writeln("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.");
writeln("Sydir7 is written in the Seed7 programming language");
writeln("Homepage: http://seed7.sourceforge.net");
writeln;
writeln("usage: sydir7 {-c|-n|-t|-a} source destination");
writeln;
writeln("Options:");
writeln(" -c Copy files. Remove destination files, if they are missing in source.");
writeln(" Overwrite newer destination files with older source files.");
writeln(" -n No change. Write, what should be done to sync, but do not change");
writeln(" anything.");
writeln(" -t Just do time corrections of identical files.");
writeln(" -a Just add files that are missing in the destination.");
writeln;
else
for parameter range argv(PROGRAM) do
if startsWith(parameter, "-") then
if parameter = "-c" then
flags.doRemoveFileAtDest := TRUE;
flags.doOverwriteNewerDestFile := TRUE;
elsif parameter = "-n" then
flags.doChanges := FALSE;
elsif parameter = "-t" then
flags.doCopy := FALSE;
flags.doUpdate := FALSE;
elsif parameter = "-a" then
flags.doUpdate := FALSE;
flags.doTimeCorrection := FALSE;
else
writeln(" *** Unknown option: " <& parameter);
error := TRUE;
end if;
if parameter in {"-c", "-t", "-a"} then
if commandOptionProvided then
writeln(" *** Only one option of -c, -t or -a is allowed.");
error := TRUE;
end if;
commandOptionProvided := TRUE;
end if;
else
incr(numOfFileNames);
case numOfFileNames of
when {1}: fromName := convDosPath(parameter);
when {2}: toName := convDosPath(parameter);
end case;
end if;
end for;
if numOfFileNames <> 2 then
writeln(" *** Wrong number of parameters.");
error := TRUE;
end if;
if not error and flags.doChanges and
(flags.doRemoveFileAtDest or flags.doOverwriteNewerDestFile) then
writeln("This will remove newer files at the destination!");
write("To proceed type \"yes\": ");
readln(answer);
if answer <> "yes" then
error := TRUE;
end if;
end if;
if not error then
syncFile(fromName, toName, flags);
end if;
end if;
end func;