CSyntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.Fnt\\(* ---------------------------------------------------------- Backup does an incremental backup between two directories, i.e. only the files that have changed since the last backup are copied. Backup.WriteFiles ( {src dst} ~ | "^") src and dst are given as path names. If a path name contains blanks it must be written under quotes. All entries in dst which are not also in src are deleted so that after the backup the contents of dst will be equal to the contents of src. Example: Backup.WriteFiles /c:/Users/cs/Oberon.NT /c:/Temp/Oberon ~ ----------------------------------------------------------*)Syntax10i.Scn.Fnt 8pdIStampElemsAlloc13 Aug 97F8mSyntax10.Scn.FntCSyntax10i.Scn.Fnt4@ FileAttrReadOnly = 0; FileAttrHidden = 1; FileAttrSystem = 2; (* file attributes *) FileAttrDirectory = 4; FileAttrArchive = 5; FileAttrNormal = 7; FileAttrTemp = 8; FileAttrCompressed = 11; FileAttrOffline = 12; CreateNew = 1; CreateAlways = 2; OpenExisting = 3; OpenAlways = 4; (* file creation options *) GenReadWrite = 0C0000000H; GenRead = 80000000H; (* file access modes *) ShareRead = 1; ShareWrite = 2; NULL = 0; InvalidHandle = -1; (* invalid Win32 file handle *) fromSystem = 1000H; (* FORMAT_MESSAGE_FROM_SYSTEM *) neutral = 0; (* LANG_NEUTRAL *) default = 1; (* SUBLANG_DEFAULT *) save = 0; print = 1; diff = 2;8Courier10.Scn.Fnt8#Syntax10.Scn.Fnt low, high: LONGINT END ; 88#Syntax10.Scn.FntRR year, month, weekday, day: INTEGER; hour, min, sec, millisec: INTEGER END ; 88#Syntax10.Scn.FntQQ creation, lastAccess, lastWrite: FileTime; len: LONGINT; attr: SET END ; 8I8#Syntax10.Scn.Fnt FileDesc = RECORD next: File; dir: ARRAY 260 OF CHAR; name: ARRAY 46 OF CHAR; handle: LONGINT; info: FileInfo; touched: BOOLEAN END ; 8(8QSyntax10.Scn.Fnt6Syntax10i.Scn.Fnt$ DirectoryDesc* = RECORD (FileDesc) files: File; (*the files in this directory*) dirs: File (*the subdirectories in this directory*) END ; 88XSyntax10i.Scn.FntSyntax10.Scn.Fnto.(* WIN32_FIND_DATA *) attrib: SET; (* file attributes *) creation, lastAcc, lastWrite: FileTime; sizeH, sizeL: LONGINT; res0, res1: LONGINT; name: ARRAY 260 (* = MAX_PATH *) OF CHAR; altName: ARRAY 14 OF CHAR END ; 8(8QSyntax10.Scn.FntSyntax10i.Scn.Fnt3 savedFiles: LONGINT; (*number of saved files*) savedBytes: LONGINT; deletedFiles: LONGINT; (*number of deleted files*) deletedBytes: LONGINT; testing: BOOLEAN; CreateFile: PROCEDURE (name: LONGINT; accessMode: LONGINT; shareMode: LONGINT; securityAttr: LONGINT; createOpts: LONGINT; attrAndFlags: SET; template: LONGINT): LONGINT; CloseHandle: PROCEDURE (h: LONGINT): BOOLEAN; DeleteFile: PROCEDURE (name: LONGINT): BOOLEAN; ReadFile: PROCEDURE (f: LONGINT; data: LONGINT; count: LONGINT; VAR read: LONGINT; ovrlp: LONGINT): BOOLEAN; WriteFile: PROCEDURE (f: LONGINT; data, count: LONGINT; VAR written: LONGINT; ovrlp: LONGINT): BOOLEAN; GetFileSize: PROCEDURE (fd: LONGINT; fileSizeHigh: LONGINT): LONGINT; GetFileTime: PROCEDURE (fd: LONGINT; creation, lastAccess, lastWrite: LONGINT): BOOLEAN; SetFileTime: PROCEDURE (fd: LONGINT; creation, lastAccess, lastWrite: LONGINT): BOOLEAN; FindFirstFile: PROCEDURE (filename: LONGINT; data: LONGINT): LONGINT; FindNextFile: PROCEDURE (hFindFile: LONGINT; data: LONGINT): BOOLEAN; FindClose: PROCEDURE (hFindFile: LONGINT); GetFileAttributes: PROCEDURE (name: LONGINT): SET; CreateDir: PROCEDURE (name: LONGINT; secAttr: LONGINT): BOOLEAN; FileTimeToSystemTime: PROCEDURE (ft: LONGINT; st: LONGINT): BOOLEAN; FormatMessage: PROCEDURE (flags, source, msgId, langId, buffer, size, args: LONGINT): LONGINT; GetLastError: PROCEDURE (): LONGINT;8 MarkElemsAllocXWp8#Syntax10.Scn.Fntnn BEGIN COPY(path, fullName); Strings.Append("\", fullName); Strings.Append(name, fullName) END FullPathName; 8e8#Syntax10.Scn.Fnt VAR i, j: INTEGER; BEGIN i := 0; j := 0; WHILE path[i] # 0X DO IF path[i] = "\" THEN j := i END ; INC(i) END ; i := 0; IF j # 0 THEN INC(j) END ; WHILE path[j] # 0X DO name[i] := path[j]; INC(i); INC(j) END ; name[i] := 0X END ExtractLastname; 8 X,8#Syntax10.Scn.FntGG BEGIN Strings.Cap(a); Strings.Cap(b); RETURN a = b END EqualString; 8 X8#Syntax10.Scn.Fnt VAR ret: LONGINT; str: ARRAY 1024 OF CHAR; BEGIN Out.Ln; ret := FormatMessage(fromSystem, 0, GetLastError(), ASH(default, 10) + neutral, S.ADR(str), LEN(str), 0); IF ret = 0 THEN Out.String("last error code: "); Out.Int(GetLastError(), 0) ELSE Out.String(str) END END Err; 8X:P8#Syntax10.Scn.Fnt VAR f: File; BEGIN NEW(f); f.next := NIL; COPY(name, f.name); COPY(dir, f.dir); f.touched := FALSE; f.info := info; RETURN f END NewFile; 8 X(]8#Syntax10.Scn.Fnt VAR g: File; BEGIN g := d.files; WHILE (g # NIL) & ~EqualString(f.name, g.name) DO g := g.next END ; RETURN g END ThisFile; 8 X0U8Syntax10.Scn.Fnt8FoldElemsNewCSyntax10.Scn.FntSyntax10b.Scn.Fnt FullPathName(to.dir, f.name, s); IF g # NIL THEN done := DeleteFile(S.ADR(s)) ELSE NEW(g) END ; g.handle := CreateFile(S.ADR(s), GenReadWrite, ShareRead + ShareWrite, NULL, CreateNew, f.info.attr, NULL); IF g.handle = InvalidHandle THEN Err; HALT(55); RETURN END ; Syntax10i.Scn.Fnt8d8CSyntax10.Scn.FntSyntax10b.Scn.FntZFullPathName(f.dir, f.name, s); f.handle := CreateFile(S.ADR(s), GenRead, ShareRead + ShareWrite, NULL, OpenExisting, {FileAttrNormal}, NULL); IF f.handle = InvalidHandle THEN Err; HALT(54); RETURN END ; REPEAT done := ReadFile(f.handle, S.ADR(buf), LEN(buf), nofRead, NULL); done := WriteFile(g.handle, S.ADR(buf), nofRead, nofWritten, NULL) UNTIL nofRead = 0; done := CloseHandle(f.handle); ASSERT(done, 92); done := SetFileTime(g.handle, S.ADR(f.info.creation), S.ADR(f.info.lastAccess), S.ADR(f.info.lastWrite)); ASSERT(done); done := CloseHandle(g.handle); ASSERT(done, 91); 8i VAR done: BOOLEAN; nofRead, nofWritten: LONGINT; s: ARRAY 260 OF CHAR; buf: ARRAY 4096 OF CHAR; st1, st2: SystemTime; BEGIN Out.String(f.name); done := FileTimeToSystemTime(S.ADR(f.info.lastWrite), S.ADR(st1)); ASSERT(done); Out.F3(" (#.#.# ", st1.day, st1.month, st1.year); Out.F3("#:#:#", st1.hour, st1.min, st1.sec); Out.F(", #)", f.info.len); IF ~testing THEN Out.String(" saved"); IF g # NIL THEN done := FileTimeToSystemTime(S.ADR(g.info.lastWrite), S.ADR(st2)); ASSERT(done); Out.F3(", replaced (#.#.# ", st2.day, st2.month, st2.year); Out.F3("#:#:#", st2.hour, st2.min, st2.sec); Out.F(", #)", g.info.len) END ; create and open empty g on to copy data ELSE Out.String(" would be saved"); IF g # NIL THEN done := FileTimeToSystemTime(S.ADR(g.info.lastWrite), S.ADR(st2)); ASSERT(done); Out.F3(", replacing (#.#.# ", st2.day, st2.month, st2.year); Out.F3("#:#:#", st2.hour, st2.min, st2.sec); Out.F(", #)", g.info.len) END END ; Out.Ln; INC(savedFiles); INC(savedBytes, f.info.len) END SaveFile; 8 X(8#Syntax10.Scn.Fnt VAR d: Directory; BEGIN NEW(d); d.next := NIL; d.files := NIL; d.dirs := NIL; COPY(dir, d.dir); Directories.URLToLocal(d.dir); d.name := ""; d.info.attr := GetFileAttributes(S.ADR(dir)); RETURN d END NewDir; 8 KO@\8CSyntax10.Scn.FntPSyntax10i.Scn.Fnt-b VAR done: BOOLEAN; path: ARRAY 260 OF CHAR; pos: INTEGER; BEGIN pos := 2; (* skip first delimiter, e.g. c:\Users\cs\Oberon *) REPEAT pos := Strings.Pos("\", dir, pos + 1); IF pos = -1 THEN Strings.Extract(dir, 0, 260, path) ELSE Strings.Extract(dir, 0, pos, path) END ; done := CreateDir(S.ADR(path), NULL) UNTIL pos = -1 END CreateDirectory; 8 X58#Syntax10.Scn.Fnt VAR g: File; a, b: ARRAY 260 OF CHAR; found: BOOLEAN; BEGIN g := dir.dirs; found := FALSE; ExtractLastname(name, a); WHILE (g # NIL) & ~found DO ExtractLastname(g.dir, b); IF EqualString(a, b) THEN found := TRUE ELSE g := g.next END END ; RETURN g END ThisDir; 8 X*O8#Syntax10.Scn.Fnt CONST TAB = 9X; VAR f: File; i: INTEGER; BEGIN FOR i := 1 TO indent DO Out.Char(TAB) END ; Out.String("--- "); OutLocalStringAsURL(d.dir); Out.Ln; f := d.files; WHILE f # NIL DO FOR i := 1 TO indent DO Out.Char(TAB) END ; Out.String(" "); Out.String(f.name); Out.Ln; f := f.next END ; f := d.dirs; WHILE f # NIL DO PrintDir(f(Directory), indent + 1); f := f.next END END PrintDir; 8 X&g8#Syntax10.Scn.Fntww VAR f: File; d1: Directory; path: ARRAY 512 OF CHAR; b: Win32FindData; sh: LONGINT; info: FileInfo; found: BOOLEAN; BEGIN Out.Char(prompt); FullPathName(d.dir, "*.*", path); sh := FindFirstFile(S.ADR(path), S.ADR(b)); IF sh # InvalidHandle THEN REPEAT info.attr := b.attrib; info.len := b.sizeL; info.creation := b.creation; info.lastAccess := b.lastAcc; info.lastWrite := b.lastWrite; IF FileAttrDirectory IN info.attr THEN IF (b.name # ".") & (b.name # "..") THEN FullPathName(d.dir, b.name, path); Directories.LocalToURL(path); d1 := NewDir(path); d1.next := d.dirs; d.dirs := d1 END ELSE f := NewFile(d.dir, b.name, info); f.next := d.files; d.files := f END ; found := FindNextFile(sh, S.ADR(b)) UNTIL ~found; FindClose(sh) END ; f := d.dirs; WHILE f # NIL DO FillDir(f(Directory), prompt); f := f.next END END FillDir; 8 X 82Syntax10.Scn.FntSyntax10b.Scn.Fnt78FoldElemsNewCSyntax10.Scn.FntSyntax10i.Scn.Fnt(* st1.year = st2.year *)858CSyntax10.Scn.FntSyntax10i.Scn.Fnt/5(* (st1.year = st2.year) & (st1.month = st2.month) *)83s8CSyntax10.Scn.FntSyntax10i.Scn.FntFK(* (st1.year = st2.year) & (st1.month = st2.month) & (st1.day = st2.day) *)8Z8CSyntax10.Scn.FntSyntax10i.Scn.Fnt_d(* (st1.year = st2.year) & (st1.month = st2.month) & (st1.day = st2.day) & (st1.hour = st2.hour) *)8Syntax10i.Scn.FntUZ8CSyntax10.Scn.FntSyntax10i.Scn.Fnt_d(* (st1.year = st2.year) & (st1.month = st2.month) & (st1.day = st2.day) & (st1.hour = st2.hour) *)81D8CSyntax10.Scn.FntSyntax10i.Scn.Fnttz(* (st1.year = st2.year) & (st1.month = st2.month) & (st1.day = st2.day) & (st1.hour = st2.hour) & (st1.min = st2.min) *)8 8QSyntax10.Scn.FntSyntax10i.Scn.Fnt/[(* (st1.year = st2.year) & (st1.month = st2.month) & (st1.day = st2.day) & (st1.hour = st2.hour) & (st1.min = st2.min) & (st1.sec = st2.sec) *)80 VAR st1, st2: SystemTime; done: BOOLEAN; BEGIN done := FileTimeToSystemTime(S.ADR(f.info.lastWrite), S.ADR(st1)); ASSERT(done); done := FileTimeToSystemTime(S.ADR(g.info.lastWrite), S.ADR(st2)); ASSERT(done); IF st1.year # st2.year THEN RETURN st1.year > st2.year ELSIF st1.month # st2.month THEN  RETURN st1.month > st2.month ELSIF st1.day # st2.day THEN  RETURN st1.day > st2.day ELSIF st1.hour # st2.hour THEN  RETURN st1.hour > st2.hour ELSE  RETURN st1.min > st2.min END (* not all file systems support seconds and milliseconds, so only compare up to minutes ELSIF st1.min # st2.min THEN  RETURN st1.min > st2.min ELSIF st1.sec # st2.sec THEN  RETURN st1.sec > st2.sec ELSE  RETURN st1.millisec > st2.millisec END *) END Newer; 8 Xd8CSyntax10.Scn.Fnt^Syntax10i.Scn.Fnt(Z VAR f, g: File; first: BOOLEAN; name: ARRAY 260 OF CHAR; BEGIN CreateDirectory(to.dir); (* ensure that destination directory exists *) f := from.files; first := TRUE; WHILE f # NIL DO g := ThisFile(to, f); IF g = NIL THEN IF first THEN Out.String("-- "); OutLocalStringAsURL(from.dir); Out.Ln; first := FALSE END ; SaveFile(f, g, to) ELSIF Newer(f, g) THEN IF first THEN Out.String("-- "); OutLocalStringAsURL(from.dir); Out.Ln; first := FALSE END ; SaveFile(f, g, to) END ; IF g # NIL THEN g.touched := TRUE END ; f := f.next END ; f := from.dirs; WHILE f # NIL DO g := ThisDir(f.dir, to); IF g = NIL THEN ExtractLastname(f.dir, name); FullPathName(to.dir, name, name); CreateDirectory(name); g := NewDir(name); END ; SaveDir(f(Directory), g(Directory)); g.touched := TRUE; f := f.next END END SaveDir; 8 X8QSyntax10.Scn.Fnt`Syntax10i.Scn.Fnt   VAR f: File; first, done: BOOLEAN; fullName: ARRAY 512 OF CHAR; st1, st2: SystemTime; BEGIN (*delete redundant files in d*) f := d.files; first := TRUE; WHILE f # NIL DO IF ~f.touched THEN IF first THEN Out.String("-- "); OutLocalStringAsURL(d.dir); Out.Ln; first := FALSE END ; FullPathName(d.dir, f.name, fullName); Out.String(f.name); done := FileTimeToSystemTime(S.ADR(f.info.lastWrite), S.ADR(st1)); ASSERT(done); Out.F3(" (#.#.# ", st1.day, st1.month, st1.year); Out.F3("#:#:#", st1.hour, st1.min, st1.sec); Out.F(", #)", f.info.len); IF ~testing THEN done := DeleteFile(S.ADR(fullName)); Out.String(" deleted") ELSE Out.String(" would be deleted") END ; Out.Ln; INC(deletedFiles); INC(deletedBytes, f.info.len) END ; f := f.next END ; f := d.dirs; WHILE f # NIL DO CleanupDir(f(Directory)); done := DeleteFile(S.ADR(f.dir)); IF done THEN (*was empty*) IF first THEN Out.String("-- "); OutLocalStringAsURL(d.dir); Out.Ln; first := FALSE END ; END ; f := f.next END END CleanupDir; 8hXSyntax10b.Scn.Fnt 8#Syntax10.Scn.Fnt BEGIN In.Open; savedFiles := 0; deletedFiles := 0; savedBytes := 0; deletedBytes := 0; Do(save); Out.F4("$# files (# bytes) saved, # files (# bytes) deleted$", savedFiles, savedBytes, deletedFiles, deletedBytes) END WriteFiles; 8X8CSyntax10.Scn.FntWSyntax10i.Scn.FntL VAR mod: LONGINT; BEGIN mod := Kernel.LoadLibrary("Kernel32"); Kernel.GetAdr(mod, "CreateFileA", S.VAL(LONGINT, CreateFile)); Kernel.GetAdr(mod, "DeleteFileA", S.VAL(LONGINT, DeleteFile)); Kernel.GetAdr(mod, "CloseHandle", S.VAL(LONGINT, CloseHandle)); Kernel.GetAdr(mod, "GetFileSize", S.VAL(LONGINT, GetFileSize)); Kernel.GetAdr(mod, "ReadFile", S.VAL(LONGINT, ReadFile)); Kernel.GetAdr(mod, "FindFirstFileA", S.VAL(LONGINT, FindFirstFile)); Kernel.GetAdr(mod, "FindNextFileA", S.VAL(LONGINT, FindNextFile)); Kernel.GetAdr(mod, "FindClose", S.VAL(LONGINT, FindClose)); Kernel.GetAdr(mod, "WriteFile", S.VAL(LONGINT, WriteFile)); Kernel.GetAdr(mod, "GetFileTime", S.VAL(LONGINT, GetFileTime)); Kernel.GetAdr(mod, "SetFileTime", S.VAL(LONGINT, SetFileTime)); Kernel.GetAdr(mod, "FileTimeToSystemTime", S.VAL(LONGINT, FileTimeToSystemTime)); Kernel.GetAdr(mod, "GetFileAttributesA", S.VAL(LONGINT, GetFileAttributes)); Kernel.GetAdr(mod, "CreateDirectoryA", S.VAL(LONGINT, CreateDir)); mod := Kernel.LoadLibrary("Kernel32"); Kernel.GetAdr(mod, "FormatMessageA", S.VAL(LONGINT, FormatMessage)); Kernel.GetAdr(mod, "GetLastError", S.VAL(LONGINT, GetLastError)) END Init; 8Q Documentation MODULE Backup; (* CS  *) IMPORT Kernel, Directories, Strings, In, Out, S := SYSTEM; CONST  TYPE FileTime = RECORD  SystemTime = RECORD  FileInfo = RECORD  File = POINTER TO FileDesc;  Directory = POINTER TO DirectoryDesc;  Win32FindData = RECORD  VAR  (*--- auxiliaries*) PROCEDURE OutLocalStringAsURL (VAR s: ARRAY OF CHAR); VAR name: ARRAY 260 OF CHAR; BEGIN COPY(s, name); Directories.LocalToURL(name); Out.String(name) END OutLocalStringAsURL; PROCEDURE FullPathName (path: ARRAY OF CHAR; name: ARRAY OF CHAR; VAR fullName: ARRAY OF CHAR);  PROCEDURE ExtractLastname (path: ARRAY OF CHAR; VAR name: ARRAY OF CHAR); (* path in local format *) PROCEDURE EqualString (a, b: ARRAY OF CHAR): BOOLEAN;  PROCEDURE Err;  (*--- files*) PROCEDURE NewFile (dir, name: ARRAY OF CHAR; info: FileInfo): File;  PROCEDURE ThisFile (d: Directory; f: File): File;  PROCEDURE SaveFile (f: File; VAR g: File; to: Directory);  (*--- directories*) PROCEDURE NewDir (dir: ARRAY OF CHAR): Directory;  PROCEDURE CreateDirectory (dir: ARRAY OF CHAR); (* path in local format *) PROCEDURE ThisDir (name: ARRAY OF CHAR; dir: Directory): File;  PROCEDURE PrintDir (d: Directory; indent: INTEGER);  PROCEDURE FillDir (d: Directory; prompt: CHAR);  PROCEDURE Newer (f, g: File): BOOLEAN; (* TRUE, iff f is newer than g *)  PROCEDURE SaveDir (from, to: Directory);  PROCEDURE CleanupDir (d: Directory);  PROCEDURE Do (op: SHORTINT); VAR path: ARRAY 260 OF CHAR; from, to: Directory; BEGIN REPEAT IF In.Next() = In.string THEN In.String(path) ELSE In.Name(path) END ; IF In.Done THEN from := NewDir(path); IF In.Next() = In.string THEN In.String(path) ELSE In.Name(path) END ; IF In.Done THEN to := NewDir(path); Out.String("Reading directories"); FillDir(from, "-"); FillDir(to, "+"); Out.Ln; CASE op OF save, diff: testing := op = diff; SaveDir(from, to); CleanupDir(to) | print: PrintDir(from, 0); PrintDir(to, 0) END END END UNTIL ~In.Done; END Do; PROCEDURE WriteFiles*;  PROCEDURE Diff*; BEGIN In.Open; savedFiles := 0; deletedFiles := 0; savedBytes := 0; deletedBytes := 0; Do(diff); Out.F4("$# files (# bytes) would be saved, # files (# bytes) would be deleted$", savedFiles, savedBytes, deletedFiles, deletedBytes) END Diff; PROCEDURE Init;  BEGIN Init END Backup.WriteFiles /c:/Users/cs/Oberon.NT /c:/Temp/Oberon ~ Backup.Diff /l:/cs/Oberon/User /c:/users/cs/Oberon/User ~ /c:/cs/oberon/Projects /c:/temp/Oberon/Projects ~ Backup.WriteFiles /c:/cs/oberon /c:/temp/Oberon ~ System.Directory /c:/Users/cs/Oberon.NT/*\ds System.Directory /c:/Temp/Oberon/*\ds System.State Backup