--------------------------------------------------------------------------
MODULE FileInfo;
--------------------------------------------------------------------------
IMPORT
Fmt, FmtTime, Fingerprint, Text, TextSeq, Wr;
IMPORT
APN AS AbsPathn, APNSeq AS AbsPathnSeq,
FileStatus, APNStatusTbl AS AbsPathnStatTbl,
Dummy, TextHashTbl, APNHashTbl AS AbsPathnHashTbl,
FileSystem, DirectoryTbl, SMsg AS Msg, MsgX, MsgIF, FindExpr;
--------------------------------------------------------------------------
REVEAL
T = Public BRANDED Brand OBJECT
base : AbsPathn.T;
cache : AbsPathnStatTbl.Default;
dir : DirectoryTbl.Default;
msgif : MsgIF.T;
OVERRIDES
init := Init;
getBase := GetBase;
size := Size;
getStatus := GetStatus;
update := Update;
updateRec := UpdateRec;
updateFlat := UpdateFlat;
getDir := GetDir;
dirList := DirList;
fingerprint := ComputeFingerprint;
dump := Dump;
END;
--------------------------------------------------------------------------
PROCEDURE Init (sf : T; sizeHint : CARDINAL; base : AbsPathn.T;
msgif : MsgIF.T := NIL) : T =
BEGIN
sf.msgif := msgif;
sf.base := base;
sf.cache := NEW(AbsPathnStatTbl.Default).init(sizeHint);
sf.dir := NEW(DirectoryTbl.Default).init();
RETURN sf
END Init;
--------------------------------------------------------------------------
PROCEDURE GetBase (sf : T) : AbsPathn.T =
BEGIN
RETURN sf.base
END GetBase;
--------------------------------------------------------------------------
PROCEDURE Size (sf : T) : CARDINAL =
BEGIN
RETURN sf.cache.size()
END Size;
--------------------------------------------------------------------------
PROCEDURE GetStatus (sf : T; p : AbsPathn.T) : FileStatus.T =
VAR res : FileStatus.T;
BEGIN
IF NOT sf.cache.get(p, res) THEN
res := NEW(FileStatus.T);
res.exists := FALSE
END;
RETURN res
END GetStatus;
--------------------------------------------------------------------------
PROCEDURE Update (sf : T; READONLY p : AbsPathn.T) : FileStatus.T =
VAR res : FileStatus.T;
BEGIN
IF sf.base = NIL OR Text.Empty(sf.base.denotation()) OR
p.isAbsolute() THEN
res := FileSystem.Lookup(p);
ELSE
res := FileSystem.Lookup(AbsPathn.Join(sf.base, p, NIL));
END;
EVAL sf.cache.put(p, res);
RETURN res
END Update;
--------------------------------------------------------------------------
PROCEDURE TextSeqToTextHashTbl(s : TextSeq.T) : TextHashTbl.T =
VAR l : CARDINAL;
res : TextHashTbl.T;
BEGIN
l := s.size();
res := NEW(TextHashTbl.Default).init(l);
FOR i := 0 TO l - 1 DO
EVAL res.put(s.get(i), Dummy.dummyConst)
END;
RETURN res
END TextSeqToTextHashTbl;
PROCEDURE AbsPathnSeqToAbsPathnHashTbl(s : AbsPathnSeq.T) : AbsPathnHashTbl.T =
VAR l : CARDINAL;
res : AbsPathnHashTbl.T;
BEGIN
l := s.size();
res := NEW(AbsPathnHashTbl.Default).init(l);
FOR i := 0 TO l - 1 DO
EVAL res.put(s.get(i), Dummy.dummyConst)
END;
RETURN res
END AbsPathnSeqToAbsPathnHashTbl;
--------------------------------------------------------------------------
PROCEDURE UpdateRec
(sf : T; p : AbsPathn.T; ext : TextSeq.T; ignoreDirs : AbsPathnSeq.T;
ignoreDirExpr : FindExpr.T := NIL; ignoreFileExpr : FindExpr.T := NIL) =
VAR
e : TextHashTbl.T;
iD : AbsPathnHashTbl.T;
pn : AbsPathn.T;
dummy : Dummy.T;
PROCEDURE UR (READONLY p : AbsPathn.T; READONLY stat : FileStatus.T) =
VAR i : FileSystem.Iterator;
n : AbsPathn.T;
s : FileStatus.T;
q : AbsPathn.T;
l : AbsPathnSeq.T;
b : TEXT;
pn : AbsPathn.T;
ignoreDir : BOOLEAN;
ignoreFile : BOOLEAN;
BEGIN
(* OutFileStatus(sf, p, stat); *)
IF sf.base = NIL OR Text.Empty(AbsPathn.Denotation(sf.base)) OR
p.isAbsolute() THEN
pn := p;
ELSE
pn := AbsPathn.Join(sf.base, p, NIL);
END;
i := FileSystem.GetIterator(pn);
l := NEW(AbsPathnSeq.T).init();
WHILE FileSystem.NextWithStatus(i, n, s) DO
q := AbsPathn.Join(p, n, NIL);
b := AbsPathn.Denotation(n);
IF s.isDir THEN
TRY
ignoreDir := ignoreDirExpr # NIL AND ignoreDirExpr.test(b);
EXCEPT ELSE
ignoreDir := FALSE;
END;
ELSE
ignoreDir := FALSE;
END;
IF s.isFile THEN
TRY
ignoreFile := ignoreFileExpr # NIL AND ignoreFileExpr.test(b);
EXCEPT ELSE
ignoreFile := FALSE;
END;
ELSE
ignoreFile := FALSE;
END;
IF e = NIL OR e.size() = 0 OR e.get(AbsPathn.LastExt(n), dummy) OR
s.isDir THEN
IF s.isDir AND NOT ignoreDir OR NOT ignoreFile THEN
(* OutFileStatus(sf, q, s); *)
EVAL sf.cache.put(q, s);
l.addhi(q);
(*
IF sf.msgif = NIL OR sf.msgif.debugLevel > 2 THEN
MsgX.D(sf.msgif, "FileInfo: updating " & q.denotation(),
level :=3);
END;
*)
END;
END;
IF s.exists (*C*)AND s.isDir (*C*)AND NOT ignoreDir (*C*)AND
(iD = NIL OR NOT iD.get (n, dummy)) THEN
UR(q, s)
END
END;
EVAL sf.cache.put(p, stat);
EVAL sf.dir.put(p, l);
i.close()
END UR;
BEGIN
(* Msg.T2("UpdateRec", p.denotation()); *)
IF ext = NIL THEN
e := NIL;
ELSE
IF extLast # NIL AND ext = extLast THEN
e := eLast;
ELSE
e := TextSeqToTextHashTbl(ext);
eLast := e;
extLast := ext;
END;
END;
IF ignoreDirs = NIL THEN
iD := NIL;
ELSE
IF ignoreDirsLast # NIL AND ignoreDirs = ignoreDirsLast THEN
iD := iDLast;
ELSE
iD := AbsPathnSeqToAbsPathnHashTbl(ignoreDirs);
iDLast := iD;
ignoreDirsLast := ignoreDirs;
END;
END;
IF sf.base = NIL OR Text.Empty(AbsPathn.Denotation(sf.base)) OR
p.isAbsolute() THEN
pn := p;
ELSE
pn := AbsPathn.Join(sf.base, p, NIL);
END;
UR(p, FileSystem.Lookup(pn));
END UpdateRec;
--------------------------------------------------------------------------
PROCEDURE UpdateFlat (sf : T; p : AbsPathn.T; ext : TextSeq.T;
ignoreDirExpr : FindExpr.T := NIL;
ignoreFileExpr : FindExpr.T := NIL) : AbsPathnSeq.T =
VAR
e : TextHashTbl.T;
i : FileSystem.Iterator;
n : AbsPathn.T;
dummy : Dummy.T;
s : FileStatus.T;
q : AbsPathn.T;
res : AbsPathnSeq.T := NEW(AbsPathnSeq.T).init(10);
ignoreFile : BOOLEAN;
BEGIN
IF extLast # NIL AND ext = extLast THEN
e := eLast;
ELSE
IF ext = NIL THEN
e := NIL;
ELSE
e := TextSeqToTextHashTbl(ext);
extLast := ext;
eLast := e;
END;
END;
IF sf.base = NIL OR Text.Empty(sf.base.denotation()) OR
p.isAbsolute() THEN
i := FileSystem.GetIterator(p);
ELSE
i := FileSystem.GetIterator(AbsPathn.Join(sf.base, p, NIL));
END;
WHILE i.nextWithStatus(n, s) DO
q := AbsPathn.Join(p, n, NIL);
TRY
IF s.isFile THEN
ignoreFile := ignoreFileExpr # NIL AND
ignoreFileExpr.test(n.denotation());
ELSIF s.isDir THEN
ignoreFile := ignoreDirExpr # NIL AND
ignoreDirExpr.test(n.denotation());
ELSE
ignoreFile := FALSE;
END;
EXCEPT ELSE
ignoreFile := FALSE;
END;
IF (e = NIL OR e.size() = 0 OR e.get(AbsPathn.LastExt(n), dummy) OR
s.isDir) AND NOT ignoreFile THEN
EVAL sf.cache.put(q, s);
res.addhi(q);
(*
IF sf.msgif = NIL OR sf.msgif.debugLevel > 2 THEN
MsgX.D(sf.msgif, "FileInfo: updating " & q.denotation(),
level := 3);
END;
*)
END;
END;
EVAL sf.cache.put(p, FileSystem.Lookup(p));
EVAL sf.dir.put(p, res);
i.close();
RETURN res
END UpdateFlat;
--------------------------------------------------------------------------
PROCEDURE GetDir(self : T; READONLY
p : AbsPathn.T) : AbsPathnSeq.T =
VAR res : AbsPathnSeq.T;
BEGIN
IF self.dir.get(p, res) THEN
RETURN res;
ELSE
RETURN NIL;
END;
END GetDir;
--------------------------------------------------------------------------
PROCEDURE DirList(self : T; p : AbsPathn.T; long := TRUE) : TEXT =
VAR
dir := GetDir(self, p);
fn : AbsPathn.T;
s : FileStatus.T;
type : TEXT;
time : TEXT;
size : TEXT;
line : TEXT;
res := "";
BEGIN
IF dir = NIL THEN RETURN NIL END;
IF long THEN
FOR i := 0 TO dir.size() - 1 DO
fn := dir.get(i);
s := GetStatus(self, fn);
IF s.exists THEN
IF s.isDir THEN
type := "d";
ELSIF s.isFile THEN
type := "f";
ELSE
type := "?";
END;
time := FmtTime.Long(s.modified);
size := Fmt.LongInt(s.size);
line := Fmt.F("%-32s %10s %1s %s\n", fn.denotation(),
size, type, time);
res := res & line;
END;
END;
ELSE
FOR i := 0 TO dir.size() - 1 DO
fn := dir.get(i);
res := res & fn.denotation() & "\n";
END;
END;
RETURN res;
END DirList;
--------------------------------------------------------------------------
<*NOWARN*> PROCEDURE OutFileStatus(self : T; p : AbsPathn.T;
stat : FileStatus.T) =
BEGIN
IF NOT Msg.dFlag THEN RETURN END;
VAR
pn := p.denotation();
s, t, k : TEXT;
BEGIN
IF stat = NIL THEN
MsgX.D(self.msgif, pn & ": NIL");
ELSE
t := FmtTime.Long(stat.modified);
s := Fmt.LongInt(stat.size);
IF stat.isDir THEN
k := " d ";
ELSIF stat.isFile THEN
k := " f ";
ELSE
k := " ? ";
END;
MsgX.D(self.msgif, pn & k & s & " " & t);
END;
END;
END OutFileStatus;
--------------------------------------------------------------------------
PROCEDURE ComputeFingerprint(
self : T; READONLY p : AbsPathn.T;
READONLY ignoreDirExpr : FindExpr.T := NIL;
READONLY ignoreFileExpr : FindExpr.T := NIL) : REF Fingerprint.T =
VAR
dir : AbsPathnSeq.T;
fn : AbsPathn.T;
s : FileStatus.T;
fp := NEW(REF Fingerprint.T);
res : REF Fingerprint.T := NIL;
size : TEXT;
time : TEXT;
base : TEXT;
ignoreDir : BOOLEAN;
ignoreFile : BOOLEAN;
todo := NEW(AbsPathnSeq.T).init();
BEGIN
(* Msg.T2("ComputeFingerprint", p.denotation()); *)
todo.addhi(p);
WHILE todo.size() > 0 DO
fn := todo.remlo();
s := GetStatus(self, fn);
(* OutFileStatus(self, fn, s); *)
IF s.exists THEN
base := AbsPathn.Last(fn).denotation();
IF s.isDir THEN
TRY
ignoreDir := ignoreDirExpr # NIL AND ignoreDirExpr.test(base);
EXCEPT ELSE
ignoreDir := FALSE;
END;
ELSIF s.isFile THEN
TRY
ignoreFile := ignoreFileExpr # NIL AND ignoreFileExpr.test(base);
EXCEPT ELSE
ignoreFile := FALSE;
END;
END;
IF s.isFile AND NOT ignoreFile THEN
(* combine the fingerprints of the name, the size, and the
modification time *)
time := Fmt.LongReal(s.modified, Fmt.Style.Fix, 2);
(* Msg.D(time); *)
size := Fmt.LongInt(s.size);
fp^ := Fingerprint.Combine(
fp^,
Fingerprint.Combine(
Fingerprint.FromText(time),
Fingerprint.FromText(size)));
(* fp^ := Fingerprint.FromText(fn.denotation() & time & size); *)
ELSIF s.isDir AND NOT ignoreDir THEN
(* combine the fingerprints of all directory entries *)
fp^ := Fingerprint.FromText(fn.denotation());
dir := GetDir(self, fn);
FOR i := 0 TO dir.size() - 1 DO
fn := dir.get(i);
todo.addhi(fn);
END;
ELSE
(* use only the name *)
fp^ := Fingerprint.FromText(fn.denotation());
END;
IF res = NIL THEN
res := fp;
fp := NEW(REF Fingerprint.T);
ELSE
res^ := Fingerprint.Combine(res^, fp^)
END;
END;
END;
RETURN res;
END ComputeFingerprint;
--------------------------------------------------------------------------
PROCEDURE ComputeFingerprintRec(
self : T; READONLY p : AbsPathn.T;
READONLY ignoreDirExpr : FindExpr.T := NIL;
READONLY ignoreFileExpr : FindExpr.T := NIL) : REF Fingerprint.T =
VAR
dir : AbsPathnSeq.T;
fn : AbsPathn.T;
s : FileStatus.T;
fp := NEW(REF Fingerprint.T);
time : TEXT;
size : TEXT;
base : TEXT;
ignoreDir : BOOLEAN;
ignoreFile : BOOLEAN;
BEGIN
s := GetStatus(self, p);
IF NOT s.exists THEN RETURN NIL END;
fp^ := Fingerprint.FromText(p.denotation());
base := AbsPathn.Last(p).denotation();
IF s.isDir THEN
TRY
ignoreDir := ignoreDirExpr # NIL AND ignoreDirExpr.test(base);
EXCEPT ELSE
ignoreDir := FALSE;
END;
END;
IF s.isFile THEN
TRY
ignoreFile := ignoreFileExpr # NIL AND ignoreFileExpr.test(base);
EXCEPT ELSE
ignoreFile := FALSE;
END;
END;
IF s.isFile AND NOT ignoreFile THEN
(* combine the fingerprints of the name, the size, and the
modification time *)
time := Fmt.LongReal(s.modified, Fmt.Style.Fix, 2);
size := Fmt.LongInt(s.size);
fp^ := Fingerprint.Combine(
fp^,
Fingerprint.Combine(
Fingerprint.FromText(time),
Fingerprint.FromText(size)));
ELSIF s.isDir AND NOT ignoreDir THEN
(* combine the fingerprints of all directory entries *)
dir := GetDir(self, p);
FOR i := 0 TO dir.size() - 1 DO
fn := dir.get(i);
WITH fpi = ComputeFingerprint(self, fn,
ignoreDirExpr, ignoreFileExpr) DO
IF fpi # NIL AND NOT Fingerprint.Equal(fpi^, Fingerprint.Zero) THEN
fp^ := Fingerprint.Combine(fp^, fpi^);
END;
END;
END;
ELSE
(* use only the name *)
END;
RETURN fp;
END ComputeFingerprintRec;
--------------------------------------------------------------------------
PROCEDURE Dump (sf : T; wr : Wr.T) =
VAR
iter := sf.cache.iterate();
pn : AbsPathn.T;
s : FileStatus.T;
BEGIN
WHILE iter.next(pn, s) DO
TRY
Wr.PutText(wr, pn.denotation() & ": " & s.repr() & "\n");
EXCEPT ELSE END;
END;
END Dump;
--------------------------------------------------------------------------
VAR
extLast : TextSeq.T := NIL;
ignoreDirsLast : AbsPathnSeq.T := NIL;
eLast : TextHashTbl.T := NIL;
iDLast : AbsPathnHashTbl.T := NIL;
BEGIN
END FileInfo.