FSTools.Mod 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. (* Aos, Copyright 2001, Pieter Muller, ETH Zurich *)
  2. MODULE FSTools; (** AUTHOR "be"; PURPOSE "Files Tools"; *)
  3. (**
  4. * Usage:
  5. *
  6. * FSTools.Mount prefix alias [volpar] ["|" fspar] ~ Mount the specified volume.
  7. * FSTools.Unmount prefix [\f] ~ Unmount the specified volume. Use /f to force unmounting.
  8. *
  9. * FSTools.SetDefault prefix ~ Set the specified volume as default volume.
  10. * FSTools.Watch ~ Diplays a list of all mounted file systems
  11. *
  12. * FSTools.CopyFiles [-ioq] {sourcefile " => " destfile} ~ Copy the specified files to
  13. * FSTools.RenameFiles [-i] {oldname " => " newname} ~ Rename files
  14. * FSTools.DeleteFiles [-i] {file} ~ Delete the specified files
  15. * FSTools.Directory [-ts] ~ Show Directory (t: show creation times, s: show file sizes)
  16. *
  17. * FSTools.Safe ~ disallow pattern matching
  18. * FSTools.Unsafe ~ allow pattern matching
  19. *
  20. * Options i, o and q:
  21. *
  22. * i: ignore errors, e.g. continue with deletion of files if a file could not be deleted
  23. * o: force overwriting existing files
  24. * q: quiet mode
  25. *
  26. * Examples:
  27. *
  28. * FSTools.Mount FAT FatFS IDE0#4~
  29. * FSTools.Unmount FAT~
  30. *
  31. * FSTools.CopyFiles AOS:Configuration.XML => FAT:Configuration.XML AOS:Test.Mod => FAT:Test.Mod ~
  32. * FSTools.RenameFiles Configuration.XML => Configuration.Bak ~
  33. * FSTools.DeleteFiles Test.Mod Bimbo.Mod ~
  34. * FSTools.Directory -s ~
  35. *
  36. * Pattern matching:
  37. *
  38. * Supported by: CopyFiles, RenameFiles, DeleteFiles and Directory
  39. *
  40. * WARNING: If no prefix is specified, the source mask if checked against all files on all mounted volumes, i.e. the command
  41. * FSTools.DeleteFiles * ~ would DELETE ALL FILES ON ALL MOUNTED partitions.
  42. *
  43. * The source mask may contain an arbitrary number of '*' (matches any string) and '?' (matches any character) characters.
  44. * For operations that have a target, the target mask semantics is the following:
  45. *
  46. * - '?' characters are not allowed in the target mask
  47. * - '*' characters are not allowed in the prefix and path
  48. * - every occurence of the character '*' is replaced by ...
  49. * ... the source file name if there is no '.' character on the left side of the '*' character
  50. * ... the source file extension if there is at least one '.' character on the left side of the '*' character
  51. *
  52. * Notes:
  53. * - Files treats the right-most '.*' as file extension, e.g. the file extension of 'AosBimbo.Test.00.Bak.Mod' is '.Mod'
  54. *
  55. *)
  56. IMPORT Modules, Commands, Options, Streams, Files, Configuration, Dates, Strings;
  57. CONST
  58. MaxNameLen = 512; (* Maximum file name length including path and 0X-termination *)
  59. InitialFilelistSize = 1024;
  60. (* Layout for Directory operation *)
  61. Column1 = 30;
  62. FormatDateTime = "dd.mm.yyyy hh:nn:ss";
  63. Error = -1;
  64. CR = 0DX; LF = 0AX;
  65. TYPE
  66. String = Strings.String;
  67. FileList = POINTER TO ARRAY OF String;
  68. EnumProc = PROCEDURE(context : Commands.Context);
  69. VAR
  70. unsafeMode : BOOLEAN;
  71. PROCEDURE ExpandAlias(CONST alias : ARRAY OF CHAR; VAR genvol, genfs: ARRAY OF CHAR);
  72. VAR t: ARRAY 64 OF CHAR; i, j, res: LONGINT;
  73. BEGIN
  74. genvol[0] := 0X; genfs[0] := 0X;
  75. t := "Files.Alias.";
  76. i := 0; WHILE t[i] # 0X DO INC(i) END;
  77. j := 0; WHILE alias[j] # 0X DO t[i] := alias[j]; INC(i); INC(j) END;
  78. t[i] := 0X;
  79. Configuration.Get(t, t, res);
  80. i := 0;
  81. WHILE (t[i] # 0X) & (t[i] # ";") DO genvol[i] := t[i]; INC(i) END;
  82. genvol[i] := 0X;
  83. IF (t[i] = ";") THEN
  84. j := 0; INC(i);
  85. WHILE (t[i] # 0X) DO genfs[j] := t[i]; INC(j); INC(i) END;
  86. genfs[j] := 0X
  87. END
  88. END ExpandAlias;
  89. PROCEDURE GetFileSystemFactory(CONST name : ARRAY OF CHAR; error : Streams.Writer) : Files.FileSystemFactory;
  90. VAR
  91. factory : Files.FileSystemFactory;
  92. moduleName, procedureName : Modules.Name; msg : ARRAY 128 OF CHAR; res : WORD;
  93. BEGIN
  94. factory := NIL;
  95. Commands.Split(name, moduleName, procedureName, res, msg);
  96. IF (res = Commands.Ok) THEN
  97. GETPROCEDURE(moduleName, procedureName, factory);
  98. IF factory = NIL THEN
  99. error.String('failed to get file system factory with name "'); error.String(name); error.String('"!'); error.Ln;
  100. END;
  101. ELSE
  102. error.String(msg); error.Ln;
  103. END;
  104. RETURN factory;
  105. END GetFileSystemFactory;
  106. PROCEDURE Mount*(context : Commands.Context); (** prefix alias [volpar] ["|" fspar] ~ *)
  107. VAR
  108. factory : Files.FileSystemFactory;
  109. parvol, parfs: Files.Parameters; i, res: LONGINT;
  110. alias, genvol, genfs : ARRAY 64 OF CHAR; prefix: Files.Prefix;
  111. BEGIN
  112. IF context.arg.GetString(prefix) & context.arg.GetString(alias) THEN
  113. ExpandAlias(alias, genvol, genfs);
  114. IF (Files.This(prefix) # NIL) THEN
  115. context.error.String(prefix); context.error.String("; already used"); context.error.Ln;
  116. ELSIF (genvol = "") OR (genfs = "") THEN
  117. context.error.String(prefix); context.error.String(": unknown alias "); context.error.String(alias); context.error.Ln;
  118. ELSE
  119. IF genvol # "NIL" THEN
  120. NEW(parvol, context.in, context.arg, context.out, context.error, context.caller);
  121. parvol.vol := NIL; res := 0;
  122. COPY(prefix, parvol.prefix);
  123. factory := GetFileSystemFactory(genvol, context.error);
  124. IF (factory # NIL) THEN
  125. factory(parvol);
  126. END;
  127. IF (factory = NIL) OR (parvol.vol = NIL) THEN res := 1; END;
  128. ELSE
  129. i := 0
  130. END;
  131. IF (res = Commands.Ok) THEN
  132. NEW(parfs, context.in, context.arg, context.out, context.error, context.caller);
  133. IF (parvol # NIL) THEN parfs.vol := parvol.vol; ELSE parfs.vol := NIL; END;
  134. COPY(prefix, parfs.prefix);
  135. factory := GetFileSystemFactory(genfs, context.error);
  136. IF (factory # NIL) THEN
  137. factory(parfs);
  138. IF (Files.This(prefix) = NIL) THEN
  139. res := 1
  140. ELSE
  141. context.out.String(prefix); context.out.String(": mounted"); context.out.Ln;
  142. END;
  143. ELSE
  144. res := 1;
  145. END;
  146. IF (res # 0) & (parvol # NIL) & (parvol.vol # NIL) THEN
  147. parvol.vol.Finalize() (* unmount volume *)
  148. END
  149. ELSE
  150. (* skip *)
  151. END
  152. END;
  153. ELSE
  154. context.error.String('Expected parameters: prefix alias ([volpar] ["|" fspar]'); context.error.Ln;
  155. END;
  156. END Mount;
  157. PROCEDURE Unmount*(context : Commands.Context); (** prefix[\f] *)
  158. VAR prefix: Files.Prefix; fs: Files.FileSystem; i: LONGINT; force: BOOLEAN; option : ARRAY 8 OF CHAR; ch : CHAR;
  159. BEGIN
  160. context.arg.SkipWhitespace;
  161. i := 0; ch := context.arg.Peek();
  162. WHILE (i < LEN(prefix)-1) & (ch # ":") & (ch # "\") & (ch > " ") & (context.arg.res = Streams.Ok) DO
  163. context.arg.Char(ch); (* consume ch *)
  164. prefix[i] := ch;
  165. INC(i);
  166. ch := context.arg.Peek();
  167. END;
  168. prefix[i] := 0X;
  169. IF (ch = ":") THEN context.arg.Char(ch); (* consume ":" *) END;
  170. context.arg.SkipWhitespace; context.arg.String(option);
  171. force := option = "\F";
  172. fs := Files.This(prefix);
  173. IF fs # NIL THEN
  174. IF (fs.vol = NIL) OR force OR ~(Files.Boot IN fs.vol.flags) THEN
  175. Files.Remove(fs);
  176. context.out.String(prefix); context.out.Char(":");
  177. context.out.String(" unmounted"); context.out.Ln;
  178. ELSE
  179. context.error.String(prefix); context.error.Char(":");
  180. context.error.String(" can't unmount boot volume. Use \f parameter to force unmounting."); context.error.Ln;
  181. END
  182. ELSE
  183. context.error.String(prefix); context.error.Char(":"); context.error.String(" not found"); context.error.Ln;
  184. END
  185. END Unmount;
  186. PROCEDURE SetDefault*(context : Commands.Context); (** prefix *)
  187. VAR prefix: Files.Prefix; fs: Files.FileSystem; i: LONGINT; ft: Files.FileSystemTable;
  188. BEGIN
  189. context.arg.SkipWhitespace; context.arg.String(prefix);
  190. fs := Files.This(prefix);
  191. IF fs # NIL THEN
  192. Files.Promote(fs);
  193. Files.GetList(ft);
  194. IF ft # NIL THEN
  195. context.out.String("Path: ");
  196. FOR i := 0 TO LEN(ft)-1 DO
  197. context.out.String(ft[i].prefix); context.out.String(" "); context.out.Ln;
  198. END
  199. END
  200. ELSE
  201. context.error.String(prefix); context.error.String(": not found"); context.error.Ln;
  202. END;
  203. END SetDefault;
  204. (* using the NIST standard for Kibi, Mebi & Gibi: http://physics.nist.gov/cuu/Units/binary.html *)
  205. PROCEDURE WriteK( k: LONGINT; out : Streams.Writer);
  206. VAR suffix: ARRAY 3 OF CHAR;
  207. BEGIN
  208. IF k < 10*1024 THEN COPY("Ki", suffix)
  209. ELSIF k < 10*1024*1024 THEN COPY("Mi", suffix); k := k DIV 1024
  210. ELSE COPY("Gi", suffix); k := k DIV (1024*1024)
  211. END;
  212. out.Int(k, 1); out.String(suffix); out.String("B");
  213. END WriteK;
  214. PROCEDURE Watch*(context : Commands.Context); (** ~ *)
  215. VAR prefix : Files.Prefix; free, total, i: LONGINT; fs: Files.FileSystem; ft: Files.FileSystemTable; found : BOOLEAN;
  216. BEGIN
  217. prefix := "";
  218. context.arg.SkipWhitespace; context.arg.String(prefix);
  219. found := FALSE;
  220. Files.GetList(ft);
  221. IF ft # NIL THEN
  222. FOR i := 0 TO LEN(ft)-1 DO
  223. fs := ft[i];
  224. IF (prefix = "") OR (prefix = fs.prefix) THEN
  225. found := TRUE;
  226. context.out.String(fs.prefix); context.out.String(": "); context.out.String(fs.desc);
  227. IF fs.vol # NIL THEN
  228. context.out.String(" on "); context.out.String(fs.vol.name);
  229. IF Files.ReadOnly IN fs.vol.flags THEN context.out.String(" (read-only)") END;
  230. IF Files.Removable IN fs.vol.flags THEN context.out.String(" (removable)") END;
  231. IF Files.Boot IN fs.vol.flags THEN context.out.String(" (boot)") END;
  232. context.out.Ln; context.out.String(" ");
  233. free := ENTIER(fs.vol.Available()/1024.0D0 * fs.vol.blockSize);
  234. total := ENTIER(fs.vol.size/1024.0D0 * fs.vol.blockSize);
  235. WriteK(free, context.out); context.out.String(" of ");
  236. WriteK(total, context.out); context.out.String(" free")
  237. END;
  238. context.out.Ln
  239. END;
  240. END;
  241. END;
  242. IF ~found THEN
  243. IF (prefix = "") THEN
  244. context.out.String("No file systems found."); context.out.Ln;
  245. ELSE
  246. context.out.String("File system "); context.out.String(prefix); context.out.String(" not found.");
  247. context.out.Ln;
  248. END;
  249. END;
  250. END Watch;
  251. (** File operations *)
  252. (* Simple text formatting (assuming the use of fixed fonts) *)
  253. PROCEDURE Align(out : Streams.Writer; CONST string : ARRAY OF CHAR);
  254. VAR spaces, i : LONGINT;
  255. BEGIN
  256. spaces := Column1 - Strings.Length(string); IF spaces < 0 THEN spaces := 0; END;
  257. FOR i := 0 TO spaces-1 DO out.Char(" "); END;
  258. END Align;
  259. PROCEDURE Directory*(context : Commands.Context); (** [Options] [pattern] *)
  260. VAR
  261. options : Options.Options;
  262. string, pattern : ARRAY 256 OF CHAR;
  263. enum : Files.Enumerator;
  264. flags, fileflags : SET;
  265. count, total : LONGINT;
  266. time, date, size : LONGINT;
  267. name : ARRAY MaxNameLen OF CHAR;
  268. dt : Dates.DateTime;
  269. BEGIN
  270. NEW(options);
  271. options.Add("s", "size", Options.Flag);
  272. options.Add("t", "time", Options.Flag);
  273. IF options.Parse(context.arg, context.error) THEN
  274. flags := {};
  275. IF options.GetFlag("size") THEN INCL(flags, Files.EnumSize); END;
  276. IF options.GetFlag("time") THEN INCL(flags, Files.EnumTime); END;
  277. IF ~context.arg.GetString(pattern) THEN
  278. pattern := "";
  279. END;
  280. NEW(enum); enum.Open(pattern, flags);
  281. count := 0; total := 0;
  282. WHILE enum.GetEntry(name, fileflags, time, date, size) DO
  283. INC(count);
  284. context.out.String(name);
  285. IF Files.EnumSize IN flags THEN
  286. Align(context.out, name); context.out.Int(size, 10); context.out.Char("B");
  287. INC(total, size)
  288. END;
  289. IF Files.EnumTime IN flags THEN
  290. IF Files.EnumSize IN flags THEN context.out.String(" "); ELSE Align(context.out, name); END;
  291. dt := Dates.OberonToDateTime(date, time);
  292. Strings.FormatDateTime(FormatDateTime, dt, string);
  293. context.out.String(string);
  294. END;
  295. context.out.Ln;
  296. END;
  297. enum.Close;
  298. IF count > 1 THEN
  299. context.out.Int(count, 0); context.out.String(" files ");
  300. IF Files.EnumSize IN flags THEN
  301. context.out.String("use "); WriteK((total+1023) DIV 1024, context.out);
  302. END
  303. END;
  304. context.out.Ln;
  305. END;
  306. END Directory;
  307. PROCEDURE EnumerateDirectory(
  308. enum : Files.Enumerator;
  309. enumProc : EnumProc;
  310. options : Options.Options;
  311. context : Commands.Context;
  312. CONST filemask : ARRAY OF CHAR;
  313. CONST arguments : ARRAY OF CHAR);
  314. VAR
  315. name : Files.FileName;
  316. flags : SET; time, date, size : LONGINT;
  317. subDirEnum : Files.Enumerator;
  318. PROCEDURE PrepareContext(context : Commands.Context; CONST currentFile, arguments : ARRAY OF CHAR);
  319. CONST PlaceHolder = "<#filename#>";
  320. VAR thisArguments : Strings.String; position : LONGINT;
  321. BEGIN
  322. NEW(thisArguments, Strings.Length(arguments) + 1024);
  323. COPY(arguments, thisArguments^);
  324. (* replace PlaceHolder string by current file's name *)
  325. position := Strings.Pos(PlaceHolder, arguments);
  326. WHILE (position >= 0) DO
  327. Strings.Delete(thisArguments^, position, Strings.Length(PlaceHolder));
  328. Strings.Insert(name, thisArguments^, position);
  329. position := Strings.Pos(PlaceHolder, thisArguments^);
  330. END;
  331. context.arg(Streams.StringReader).InitStringReader(Strings.Length(thisArguments^));
  332. context.arg(Streams.StringReader).Set(thisArguments^);
  333. END PrepareContext;
  334. BEGIN
  335. ASSERT((enum # NIL) & (enumProc # NIL) & (options # NIL) & (context # NIL));
  336. WHILE enum.GetEntry(name, flags, time, date, size) DO
  337. IF ~(Files.Directory IN flags) & Strings.Match(filemask, name) THEN
  338. PrepareContext(context, name, arguments);
  339. enumProc(context);
  340. context.out.Update;
  341. context.error.Update;
  342. ELSIF options.GetFlag("subdirectories") THEN
  343. IF options.GetFlag("directories") THEN
  344. Strings.Append(name, Files.PathDelimiter);
  345. PrepareContext(context, name, arguments);
  346. enumProc(context);
  347. Strings.Append(name, filemask);
  348. END;
  349. NEW(subDirEnum);
  350. subDirEnum.Open(name, {});
  351. EnumerateDirectory(subDirEnum, enumProc, options, context, filemask, arguments);
  352. subDirEnum.Close;
  353. END;
  354. END;
  355. enum.Close;
  356. END EnumerateDirectory;
  357. PROCEDURE Enumerate*(context : Commands.Context); (** [Options] pattern commandProc ~ *)
  358. VAR
  359. options : Options.Options;
  360. pattern, path, filemask : Files.FileName;
  361. commandProcStr, msg : ARRAY 128 OF CHAR;
  362. arguments : Strings.String;
  363. enumProc : EnumProc;
  364. moduleName, procedureName : Modules.Name;
  365. enum : Files.Enumerator;
  366. enumContext : Commands.Context;
  367. arg : Streams.StringReader;
  368. res : WORD;
  369. BEGIN
  370. NEW(options);
  371. options.Add("s", "subdirectories", Options.Flag);
  372. options.Add("d", "directories", Options.Flag);
  373. IF options.Parse(context.arg, context.out) THEN
  374. IF context.arg.GetString(pattern) & context.arg.GetString(commandProcStr) THEN
  375. Commands.Split(commandProcStr, moduleName, procedureName, res, msg);
  376. IF (res = Commands.Ok) THEN
  377. GETPROCEDURE(moduleName, procedureName, enumProc);
  378. IF (enumProc # NIL) THEN
  379. Files.SplitPath(pattern, path, filemask);
  380. NEW(enum);
  381. enum.Open(path, {});
  382. NEW(arg, 4096);
  383. NEW(arguments, context.arg.Available()); Strings.Truncate(arguments^, 0);
  384. context.arg.Bytes(arguments^, 0, context.arg.Available(), res); (* ignore res *)
  385. NEW(enumContext, context.in, arg, context.out, context.error, context.caller);
  386. EnumerateDirectory(enum, enumProc, options, enumContext, filemask, arguments^);
  387. enum.Close;
  388. ELSE
  389. context.out.String("Procedure "); context.out.String(commandProcStr); context.out.String(" not found");
  390. context.out.Ln;
  391. END;
  392. ELSE
  393. context.out.String("Command procedure error, res: "); context.out.Int(res, 0);
  394. context.out.String(" ("); context.out.String(msg); context.out.String(")");
  395. context.out.Ln;
  396. END;
  397. ELSE
  398. context.out.String("FSTools.Enumerate [Options] pattern ~"); context.out.Ln;
  399. END;
  400. END;
  401. END Enumerate;
  402. (** Create a new file and optionally fill it with content
  403. Option c: Transform <LF> into <CR><LF>
  404. Option r: Remove whitespace at beginning of line
  405. Option a: Append to file instead of creating new file
  406. *)
  407. PROCEDURE CreateFile*(context : Commands.Context); (** [Options] filename [content] ~ *)
  408. VAR
  409. options : Options.Options; cr, removeWhitespace : BOOLEAN;
  410. file : Files.File; filename : Files.FileName; writer : Files.Writer; ch : CHAR; pos: LONGINT;
  411. BEGIN
  412. NEW(options);
  413. options.Add("c", "cr", Options.Flag);
  414. options.Add("r", "remove", Options.Flag);
  415. options.Add("a", "append", Options.Flag);
  416. IF options.Parse(context.arg, context.out) THEN
  417. IF context.arg.GetString(filename) THEN
  418. cr := options.GetFlag("cr");
  419. removeWhitespace := options.GetFlag("remove");
  420. file := NIL;
  421. IF options.GetFlag("append") THEN
  422. file := Files.Old(filename);
  423. END;
  424. IF file = NIL THEN
  425. file := Files.New(filename);
  426. pos := 0;
  427. ELSE
  428. pos := file.Length();
  429. END;
  430. Files.OpenWriter(writer, file, pos);
  431. IF removeWhitespace THEN context.arg.SkipWhitespace; END;
  432. WHILE (context.arg.res = Streams.Ok) DO
  433. ch := context.arg.Get();
  434. IF (ch = LF) THEN
  435. IF cr THEN writer.Char(CR); END;
  436. IF removeWhitespace THEN context.arg.SkipWhitespace; END;
  437. END;
  438. IF ch # 0X THEN
  439. writer.Char(ch);
  440. END;
  441. END;
  442. writer.Update;
  443. Files.Register(file);
  444. context.out.String("Created file "); context.out.String(filename); context.out.Ln;
  445. ELSE
  446. context.out.String("FSTools.CreateFile filename [content] ~"); context.out.Ln;
  447. END;
  448. END;
  449. END CreateFile;
  450. PROCEDURE CopyTo*(context : Commands.Context); (** targetpath sourcepath {filename} ~ *)
  451. VAR targetPath, sourcePath, targetFullname, sourceFullname, filename : Files.FileName; overwrite : BOOLEAN; nofFilesCopied, nofErrors, res : LONGINT;
  452. BEGIN
  453. context.arg.SkipWhitespace; context.arg.String(targetPath);
  454. context.arg.SkipWhitespace; context.arg.String(sourcePath);
  455. nofFilesCopied := 0; nofErrors := 0;
  456. WHILE context.arg.GetString(filename) DO
  457. COPY(targetPath, targetFullname); Strings.Append(targetFullname, filename);
  458. COPY(sourcePath, sourceFullname); Strings.Append(sourceFullname, filename);
  459. overwrite := TRUE;
  460. Files.CopyFile(sourceFullname, targetFullname, overwrite, res);
  461. IF (res = Files.Ok) THEN
  462. INC(nofFilesCopied);
  463. ELSE
  464. INC(nofErrors);
  465. context.error.String("Error: Could not copy file "); context.error.String(sourceFullname);
  466. context.error.String(" to "); context.error.String(targetFullname); context.error.String(", res: ");
  467. context.error.Int(res, 0); context.error.Ln;
  468. RETURN;
  469. END;
  470. END;
  471. context.out.Int(nofFilesCopied, 0); context.out.String(" files copied");
  472. IF (nofErrors > 0) THEN
  473. context.out.String(" ("); context.out.Int(nofErrors, 0); context.out.String(" errors)");
  474. END;
  475. context.out.Ln;
  476. END CopyTo;
  477. (** Copy files *)
  478. PROCEDURE CopyFiles*(context : Commands.Context); (** [Options] {source => destination} ~ *)
  479. VAR
  480. source, destination : FileList;
  481. overwritten, error, ignoreErrors, quiet : BOOLEAN;
  482. nofFiles, res, n : LONGINT;
  483. options: Options.Options;
  484. BEGIN
  485. NEW(options);
  486. options.Add("o", "overwrite", Options.Flag); (* overwrite target file if it exists *)
  487. options.Add("i", "ignore", Options.Flag); (* continue on errors *)
  488. options.Add("n", "nolist", Options.Flag); (* only allow two arguments *)
  489. options.Add("q", "quiet", Options.Flag); (* do not print copied file names *)
  490. IF options.Parse(context.arg, context.error) THEN
  491. ignoreErrors := options.GetFlag("ignore");
  492. IF options.GetFlag("nolist") THEN (* source target *)
  493. nofFiles := GetSimpleFileLists(context, source, destination);
  494. ELSE (* {source => target} *)
  495. nofFiles := GetFileLists(context, source, destination);
  496. END;
  497. IF nofFiles # Error THEN
  498. quiet := options.GetFlag("quiet");
  499. IF ~quiet THEN context.out.String("Copying files..."); context.out.Ln; context.out.Update END;
  500. n := 0;
  501. WHILE(n < LEN(source)) & (source[n] # NIL) & (n < LEN(destination)) & (destination[n] # NIL) & (ignoreErrors OR ~error) DO
  502. IF ~quiet THEN
  503. context.out.String(" Copy "); context.out.String(source[n]^); context.out.String(" => ");
  504. context.out.String(destination[n]^); context.out.String(" ... ");
  505. context.out.Update;
  506. END;
  507. overwritten := options.GetFlag("overwrite");
  508. Files.CopyFile(source[n]^, destination[n]^, overwritten, res);
  509. IF res = Files.Ok THEN
  510. IF ~quiet THEN
  511. context.out.String("done");
  512. IF overwritten THEN context.out.String(" (overwritten)"); END;
  513. context.out.Char("."); context.out.Ln;
  514. context.out.Update;
  515. END;
  516. INC(n);
  517. ELSE
  518. IF quiet THEN
  519. context.out.String(" Copy "); context.out.String(source[n]^); context.out.String(" => ");
  520. context.out.String(destination[n]^);
  521. END;
  522. context.error.String("failed "); ShowRes(context.error, res); context.error.Ln;
  523. context.error.Update;
  524. error := TRUE;
  525. END;
  526. END;
  527. END;
  528. IF nofFiles # Error THEN
  529. context.out.Int(n, 0); context.out.String(" of "); context.out.Int(nofFiles, 0); context.out.String(" files copied."); context.out.Ln;
  530. ELSE
  531. context.out.String("No files copied."); context.out.Ln;
  532. END;
  533. END;
  534. END CopyFiles;
  535. PROCEDURE GenerateName(CONST prefix: ARRAY OF CHAR; index: LONGINT; VAR str: ARRAY OF CHAR);
  536. VAR startTime: Dates.DateTime; num: ARRAY 32 OF CHAR;
  537. BEGIN
  538. startTime := Dates.Now();
  539. Strings.FormatDateTime("_yyyymmdd__hhnnss",startTime,str);
  540. Strings.Concat(prefix,str,str);
  541. IF index # 0 THEN
  542. Strings.IntToStr(index,num);
  543. Strings.Append(str,"_");
  544. Strings.Concat(str,num,str);
  545. END;
  546. Strings.Concat(str,".bak",str);
  547. END GenerateName;
  548. PROCEDURE Backup*(context: Commands.Context);
  549. VAR index: LONGINT; fileList: FileList; nofFiles, n, res: LONGINT; str: Files.FileName; overwritten: BOOLEAN;
  550. BEGIN
  551. overwritten := FALSE;
  552. nofFiles := GetFileList(context, fileList);
  553. n := 0;
  554. WHILE (fileList[n] # NIL) DO
  555. index := -1;
  556. REPEAT
  557. INC(index);
  558. GenerateName(fileList[n]^, index, str);
  559. UNTIL Files.Old(str) = NIL;
  560. Files.CopyFile(fileList[n]^, str, overwritten, res);
  561. context.out.String("backed up "); context.out.String(fileList[n]^); context.out.String(" in "); context.out.String(str); context.out.Ln;
  562. ASSERT(~overwritten);
  563. INC(n);
  564. END;
  565. END Backup;
  566. (** Delete files *)
  567. PROCEDURE DeleteFiles*(context : Commands.Context); (** [Options] {file} ~ *)
  568. VAR
  569. filelist : FileList;
  570. error, ignoreErrors, silent : BOOLEAN;
  571. nofFiles, res, n, ndone : LONGINT;
  572. options : Options.Options;
  573. BEGIN
  574. NEW(options);
  575. options.Add("i", "ignore", Options.Flag);
  576. options.Add("s", "silent", Options.Flag);
  577. IF options.Parse(context.arg, context.error) THEN
  578. ignoreErrors := options.GetFlag("ignore");
  579. silent := options.GetFlag("silent");
  580. nofFiles := GetFileList(context, filelist);
  581. IF (nofFiles > 0) THEN
  582. context.out.String("Deleting files..."); context.out.Ln;
  583. n := 0; ndone := 0;
  584. WHILE(filelist[n] # NIL) & (ignoreErrors OR ~error) DO
  585. res := 0;
  586. IF ~silent THEN context.out.String(" Delete "); context.out.String(filelist[n]^); context.out.String(" ... "); context.out.Update; END;
  587. Files.Delete(filelist[n]^, res);
  588. IF res = Files.Ok THEN
  589. IF ~silent THEN context.out.String("done."); context.out.Ln; END;
  590. INC(ndone);
  591. ELSE
  592. IF silent THEN
  593. context.out.String(" Delete "); context.out.String(filelist[n]^); context.out.String(" ... "); context.out.Update;
  594. END;
  595. context.out.String("failed "); ShowRes(context.out, res); context.out.Ln;
  596. error := TRUE;
  597. END;
  598. INC(n);
  599. context.out.Update;
  600. END;
  601. context.out.Int(ndone, 0); context.out.String(" of "); context.out.Int(nofFiles, 0); context.out.String(" files deleted."); context.out.Ln;
  602. ELSIF (nofFiles = 0) THEN
  603. context.out.String("No files matching the mask found."); context.out.Ln;
  604. ELSE
  605. context.error.String("Syntax Error: No files deleted"); context.error.Ln;
  606. END;
  607. END;
  608. END DeleteFiles;
  609. (** Rename files. *)
  610. PROCEDURE RenameFiles*(context : Commands.Context); (** [Options] {source => destination} ~ *)
  611. VAR
  612. source, target : FileList;
  613. error, ignoreErrors : BOOLEAN;
  614. nofFiles, res, n : LONGINT;
  615. options : Options.Options;
  616. BEGIN
  617. NEW(options);
  618. options.Add("i", "ignore", Options.Flag); (* continue on errors *)
  619. options.Add("n", "nolist", Options.Flag);
  620. IF options.Parse(context.arg, context.error) THEN
  621. ignoreErrors := options.GetFlag("ignore");
  622. IF options.GetFlag("nolist") THEN
  623. nofFiles := GetSimpleFileLists(context, source, target);
  624. ELSE
  625. nofFiles := GetFileLists(context, source, target);
  626. END;
  627. IF nofFiles # Error THEN
  628. context.out.String("Renaming files..."); context.out.Ln;
  629. n := 0;
  630. WHILE(source[n] # NIL) & (target[n] # NIL) & (ignoreErrors OR ~error) DO
  631. res := 0;
  632. context.out.String(" Rename "); context.out.String(source[n]^); context.out.String(" => "); context.out.String(target[n]^); context.out.String(" ... ");
  633. Files.Rename(source[n]^, target[n]^, res);
  634. IF res # Files.Ok THEN
  635. context.error.String("failed "); ShowRes(context.error, res); context.error.Ln;
  636. error := TRUE;
  637. ELSE
  638. context.out.String("done."); context.out.Ln;
  639. INC(n);
  640. END;
  641. END;
  642. END;
  643. IF nofFiles # Error THEN
  644. context.out.Int(n, 0); context.out.String(" of "); context.out.Int(nofFiles, 0); context.out.String(" files renamed."); context.out.Ln;
  645. ELSE
  646. context.out.String("No files renamed."); context.out.Ln;
  647. END;
  648. END;
  649. END RenameFiles;
  650. PROCEDURE CreateDirectory*(context : Commands.Context); (* path ~ *)
  651. VAR path : Files.FileName; res : WORD;
  652. BEGIN
  653. IF context.arg.GetString(path) THEN
  654. Files.CreateDirectory(path, res);
  655. IF (res # Files.Ok) THEN
  656. context.out.String("Could not create directory '"); context.out.String(path); context.out.String("', res: ");
  657. ShowRes(context.out, res); context.out.Ln;
  658. END;
  659. ELSE
  660. context.out.String("Usage: FSTools.CreateDirectory <path> ~"); context.out.Ln;
  661. END;
  662. END CreateDirectory;
  663. PROCEDURE DeleteDirectory*(context : Commands.Context); (* path ~ *)
  664. VAR path : Files.FileName; res : WORD;
  665. BEGIN
  666. IF context.arg.GetString(path) THEN
  667. Files.RemoveDirectory(path, FALSE, res);
  668. IF (res # Files.Ok) THEN
  669. context.out.String("Could not delete directory '"); context.out.String(path); context.out.String("', res: ");
  670. ShowRes(context.out, res); context.out.Ln;
  671. END;
  672. ELSE
  673. context.out.String("Usage: FSTools.DeleteDirectory <path> ~"); context.out.Ln;
  674. END;
  675. END DeleteDirectory;
  676. (** Compare filenames of two directories and display files that are not present in both directories *)
  677. PROCEDURE CompareDirectories*(context : Commands.Context); (** directory1 directory2 ~ *)
  678. VAR
  679. fileList1, fileList2 : FileList;
  680. length1, length2 : LONGINT;
  681. dirname1, dirname2 : Files.FileName;
  682. index1, index2 : LONGINT;
  683. differences : LONGINT;
  684. PROCEDURE GetSortedFileList(CONST dirname : ARRAY OF CHAR; VAR index : LONGINT) : FileList;
  685. VAR mask : Files.FileName; fileList : FileList;
  686. BEGIN
  687. COPY(dirname, mask);
  688. Strings.Append(mask, Files.PathDelimiter);
  689. Strings.Append(mask, "*");
  690. NEW(fileList, 128);
  691. InsertFiles(mask, fileList, index);
  692. IF (index > 0) THEN SortFileList(fileList, index); END;
  693. ASSERT(fileList # NIL);
  694. RETURN fileList;
  695. END GetSortedFileList;
  696. PROCEDURE CompareEntries(CONST entry1, entry2 : ARRAY OF CHAR) : LONGINT;
  697. VAR result : LONGINT; prefix : Files.Prefix; filename1, filename2, pathname, path : Files.FileName;
  698. BEGIN
  699. Files.SplitName(entry1, prefix, pathname);
  700. Files.SplitPath(pathname, path, filename1);
  701. Files.SplitName(entry2, prefix, pathname);
  702. Files.SplitPath(pathname, path, filename2);
  703. IF (filename1 < filename2) THEN result := -1;
  704. ELSIF (filename1 > filename2) THEN result := 1;
  705. ELSE result := 0;
  706. END;
  707. RETURN result;
  708. END CompareEntries;
  709. BEGIN
  710. context.arg.SkipWhitespace; context.arg.String(dirname1);
  711. context.arg.SkipWhitespace; context.arg.String(dirname2);
  712. differences := 0;
  713. length1 := 0;
  714. fileList1 := GetSortedFileList(dirname1, length1);
  715. length2 := 0;
  716. fileList2 := GetSortedFileList(dirname2, length2);
  717. context.out.String(dirname1); context.out.String(": "); context.out.Int(length1, 0); context.out.String(" entries"); context.out.Ln;
  718. context.out.String(dirname2); context.out.String(": "); context.out.Int(length2, 0); context.out.String(" entries"); context.out.Ln;
  719. index1 := 0; index2 := 0;
  720. WHILE (index1 < length1) DO
  721. WHILE (index2 < length2) & (CompareEntries(fileList1[index1]^, fileList2[index2]^) > 0) DO
  722. context.out.String(fileList2[index2]^); context.out.Ln;
  723. INC(differences);
  724. INC(index2);
  725. END;
  726. IF (index2 < length2) & (CompareEntries(fileList1[index1]^, fileList2[index2]^) = 0)THEN
  727. INC(index2);
  728. ELSE
  729. INC(differences);
  730. context.out.String(fileList1[index1]^); context.out.Ln;
  731. END;
  732. INC(index1);
  733. END;
  734. WHILE (index2 < length2) DO
  735. context.out.String(fileList2[index2]^); context.out.Ln;
  736. INC(differences);
  737. INC(index2);
  738. END;
  739. IF (differences = 0) THEN
  740. context.out.String("Directories contain the same entries"); context.out.Ln;
  741. END;
  742. END CompareDirectories;
  743. (** Compare two files by byte-wise comparison of contents *)
  744. PROCEDURE CompareFiles*(context : Commands.Context); (* filename1 filename2 ~ *)
  745. VAR filename : Files.FileName; file1, file2 : Files.File; reader1, reader2 : Files.Reader; ch1, ch2 : CHAR;
  746. BEGIN
  747. context.arg.SkipWhitespace; context.arg.String(filename);
  748. file1 := Files.Old(filename);
  749. IF (file1# NIL) THEN
  750. context.arg.SkipWhitespace; context.arg.String(filename);
  751. file2 := Files.Old(filename);
  752. IF (file2 # NIL) THEN
  753. IF (file1.Length() = file2.Length()) THEN
  754. NEW(reader1, file1, 0);
  755. NEW(reader2, file2, 0);
  756. REPEAT
  757. reader1.Char(ch1);
  758. reader2.Char(ch2);
  759. UNTIL (ch1 # ch2) OR (reader1.res # Files.Ok) OR (reader2.res # Files.Ok);
  760. IF (ch1 = ch2) & (reader1.res = reader2.res) & (reader1.res = Streams.EOF) THEN
  761. context.out.String("Files are equal"); context.out.Ln;
  762. ELSE
  763. context.out.String("Content mismatch"); context.out.Ln;
  764. END;
  765. ELSE
  766. context.out.String("Length mismatch"); context.out.Ln;
  767. END;
  768. ELSE
  769. context.error.String("File "); context.error.String(filename); context.error.String(" not found");
  770. context.error.Ln;
  771. END;
  772. ELSE
  773. context.error.String("File "); context.error.String(filename); context.error.String(" not found");
  774. context.error.Ln;
  775. END;
  776. END CompareFiles;
  777. PROCEDURE SortFileList(filelist : FileList; length : LONGINT );
  778. VAR i, j : LONGINT; temp : Strings.String;
  779. BEGIN
  780. (* bubble sort *)
  781. FOR i := 0 TO length-1 DO
  782. FOR j := 0 TO length-2 DO
  783. IF filelist[j]^ > filelist[j+1]^ THEN
  784. temp := filelist[j+1];
  785. filelist[j+1] := filelist[j];
  786. filelist[j] := temp;
  787. END;
  788. END;
  789. END;
  790. END SortFileList;
  791. PROCEDURE ResizeFilelist(VAR filelist : FileList);
  792. VAR temp : FileList; i : LONGINT;
  793. BEGIN
  794. NEW(temp, 2 * LEN(filelist));
  795. FOR i := 0 TO LEN(filelist)-1 DO
  796. temp[i] := filelist[i];
  797. END;
  798. filelist := temp;
  799. END ResizeFilelist;
  800. (* Checks whether a file list entry contains mask characters and adds the corresponding files if it does *)
  801. PROCEDURE InsertFiles(CONST mask : ARRAY OF CHAR; VAR filelist : FileList; VAR index : LONGINT);
  802. VAR
  803. enum : Files.Enumerator;
  804. fileflags : SET;
  805. time, date, size : LONGINT;
  806. name : ARRAY MaxNameLen OF CHAR;
  807. BEGIN
  808. NEW(enum); enum.Open(mask, {});
  809. WHILE enum.GetEntry(name, fileflags, time, date, size) DO
  810. IF (fileflags * {Files.Directory} = {}) THEN
  811. IF index >= LEN(filelist) THEN ResizeFilelist(filelist); END;
  812. filelist[index] := Strings.NewString(name);
  813. INC(index);
  814. END;
  815. END;
  816. enum.Close;
  817. END InsertFiles;
  818. (* Count the number of occurences of the character 'ch' in the string 'string'. Case-Sensitive! *)
  819. PROCEDURE CountCharacters(CONST string : ARRAY OF CHAR; ch : CHAR) : LONGINT;
  820. VAR count, i : LONGINT;
  821. BEGIN
  822. count := 0;
  823. FOR i := 0 TO LEN(string)-1 DO
  824. IF string[i] = ch THEN INC(count); END;
  825. END;
  826. RETURN count;
  827. END CountCharacters;
  828. (* Split full name into prefix, path, filename and file extension *)
  829. PROCEDURE SplitFullName(CONST fullname : ARRAY OF CHAR; VAR prefix, path, filename, extension : ARRAY OF CHAR);
  830. VAR pathname, name : ARRAY 1024 OF CHAR;
  831. BEGIN
  832. Files.SplitName(fullname, prefix, pathname);
  833. Files.SplitPath(pathname, path, name);
  834. Files.SplitExtension(name, filename, extension);
  835. END SplitFullName;
  836. PROCEDURE IsValidTargetMask(context : Commands.Context; CONST mask : ARRAY OF CHAR) : BOOLEAN;
  837. VAR
  838. prefix : ARRAY Files.PrefixLength OF CHAR;
  839. filename, extension : ARRAY Files.NameLength OF CHAR;
  840. path : ARRAY 512 OF CHAR;
  841. BEGIN
  842. SplitFullName(mask, prefix, path, filename, extension);
  843. IF (CountCharacters(mask, "?") > 0) THEN
  844. context.error.String("Syntax Error in "); context.error.String(mask); context.error.String(": '?' matching characters not implemented for target mask"); context.error.Ln;
  845. RETURN FALSE;
  846. END;
  847. IF (CountCharacters(prefix, "*") # 0) OR (CountCharacters(path, "*") # 0) THEN
  848. context.error.String("Syntax Error in "); context.error.String(mask); context.error.String(": Target prefix/path may not contain '*' characters"); context.error.Ln;
  849. RETURN FALSE;
  850. END;
  851. RETURN TRUE;
  852. END IsValidTargetMask;
  853. (* If the user does not specify a prefix or path for a mask, the mask will include all directories and subdirectories.
  854. Since this is too dangerous for file operations as delete, we only allow pattern operations if a prefix
  855. or path is specified within the pattern or the unsafe mode is set *)
  856. PROCEDURE AllowMaskInSafeMode(CONST mask : ARRAY OF CHAR) : BOOLEAN;
  857. VAR prefix : Files.Prefix; pathname, path, filename : Files.FileName;
  858. BEGIN
  859. Files.SplitName(mask, prefix, pathname);
  860. Files.SplitPath(pathname, path, filename);
  861. RETURN (prefix # "") OR ((path # "") & (path # Files.PathDelimiter));
  862. END AllowMaskInSafeMode;
  863. PROCEDURE GetTargetName(CONST sourceMask, targetMask, sourceName : ARRAY OF CHAR) : String;
  864. VAR
  865. targetName : ARRAY 1024 OF CHAR;
  866. srcPrefix, srcPath, srcFilename, srcExtension : ARRAY 512 OF CHAR;
  867. isExtension : BOOLEAN;
  868. i, j, index : LONGINT;
  869. BEGIN
  870. SplitFullName(sourceName, srcPrefix, srcPath, srcFilename, srcExtension);
  871. index := 0;
  872. FOR i := 0 TO LEN(targetMask)-1 DO
  873. IF (targetMask[i] = ".") & (targetMask[i+1]#".") & (targetMask[i+1]#"/") THEN
  874. isExtension := TRUE;
  875. targetName[index] := targetMask[i];
  876. INC(index);
  877. ELSIF targetMask[i] = "*" THEN
  878. IF isExtension THEN
  879. j := 0; WHILE (j < LEN(srcExtension)) & (srcExtension[j] # 0X) DO targetName[index] := srcExtension[j]; INC(index); INC(j); END;
  880. ELSE
  881. j := 0; WHILE (j < LEN(srcFilename)) & (srcFilename[j] # 0X) DO targetName[index] := srcFilename[j]; INC(index); INC(j); END;
  882. END;
  883. ELSE
  884. targetName[index] := targetMask[i];
  885. INC(index);
  886. END;
  887. END;
  888. IF index < LEN(targetName) THEN targetName[index] := 0X; END;
  889. RETURN Strings.NewString(targetName);
  890. END GetTargetName;
  891. PROCEDURE InsertFilesAndFixDestination(context : Commands.Context; CONST sourceMask, targetMask : ARRAY OF CHAR; VAR source, target : FileList; VAR index : LONGINT) : BOOLEAN;
  892. VAR
  893. enum : Files.Enumerator;
  894. fileflags : SET;
  895. time, date, size : LONGINT;
  896. name : ARRAY MaxNameLen OF CHAR;
  897. BEGIN
  898. IF ~IsValidTargetMask(context, targetMask) THEN RETURN FALSE; END;
  899. NEW(enum); enum.Open(sourceMask, {});
  900. WHILE enum.GetEntry(name, fileflags, time, date, size) DO
  901. IF (fileflags * {Files.Directory} = {}) THEN
  902. IF index >= LEN(source) THEN ResizeFilelist(source); ResizeFilelist(target); END;
  903. source[index] := Strings.NewString(name);
  904. target[index] := GetTargetName(sourceMask, targetMask, name);
  905. INC(index);
  906. END;
  907. END;
  908. enum.Close;
  909. RETURN TRUE;
  910. END InsertFilesAndFixDestination;
  911. PROCEDURE IsMask(CONST string : ARRAY OF CHAR) : BOOLEAN;
  912. BEGIN
  913. RETURN Strings.ContainsChar(string, "*", FALSE) OR Strings.ContainsChar(string, "?", FALSE);
  914. END IsMask;
  915. PROCEDURE GetFileList(context : Commands.Context; VAR filelist : FileList) : LONGINT;
  916. VAR filename : ARRAY MaxNameLen OF CHAR; done, error : BOOLEAN; count : LONGINT;
  917. BEGIN
  918. NEW(filelist, InitialFilelistSize);
  919. WHILE ~done & ~error DO
  920. IF context.arg.GetString(filename) THEN
  921. IF IsMask(filename) THEN
  922. IF ~(AllowMaskInSafeMode(filename) OR unsafeMode) THEN
  923. ShowUnsafeMessage(context.out); RETURN 0;
  924. END;
  925. InsertFiles(filename, filelist, count);
  926. ELSE
  927. IF count >= LEN(filelist) THEN ResizeFilelist(filelist); END;
  928. filelist[count] := Strings.NewString(filename);
  929. INC(count);
  930. END;
  931. ELSIF context.arg.res = Streams.EOF THEN
  932. done := TRUE;
  933. ELSE
  934. context.error.String("Command parsing error (res: "); context.error.Int(context.arg.res, 0); context.error.String(")");
  935. error := TRUE;
  936. END;
  937. END;
  938. IF error THEN count := Error; END;
  939. RETURN count;
  940. END GetFileList;
  941. PROCEDURE GetSimpleFileLists(context : Commands.Context; VAR source, target : FileList) : LONGINT;
  942. VAR sourceFilename, targetFilename : Files.FileName; count : LONGINT;
  943. BEGIN
  944. IF context.arg.GetString(sourceFilename) & context.arg.GetString(targetFilename) THEN
  945. count := 1;
  946. IF IsMask(sourceFilename) OR IsMask(targetFilename) THEN
  947. IF ~(AllowMaskInSafeMode(sourceFilename) OR unsafeMode) THEN ShowUnsafeMessage(context.out); RETURN 0; END;
  948. IF ~InsertFilesAndFixDestination(context, sourceFilename, targetFilename, source, target, count) THEN END;
  949. ELSE
  950. NEW(source, 1); NEW(target, 1);
  951. source[0] := Strings.NewString(sourceFilename);
  952. target[0] := Strings.NewString(targetFilename);
  953. END;
  954. ELSE
  955. count := Error;
  956. context.error.String("Expected two filenames as arguments"); context.error.Ln;
  957. END;
  958. RETURN count;
  959. END GetSimpleFileLists;
  960. PROCEDURE GetFileLists(context : Commands.Context; VAR source, target : FileList) : LONGINT;
  961. VAR
  962. filename : ARRAY MaxNameLen OF CHAR; done, error : BOOLEAN; count : LONGINT;
  963. sourceString, targetString : String;
  964. BEGIN
  965. NEW(source, InitialFilelistSize); NEW(target, InitialFilelistSize);
  966. WHILE ~done & ~error DO
  967. IF context.arg.GetString(filename) THEN
  968. sourceString := Strings.NewString(filename);
  969. IF context.arg.GetString(filename) & Strings.Match(filename, "=>") THEN
  970. IF context.arg.GetString(filename) THEN
  971. targetString := Strings.NewString(filename);
  972. IF IsMask(sourceString^) OR IsMask(targetString^) THEN
  973. IF ~(AllowMaskInSafeMode(sourceString^) OR unsafeMode) THEN ShowUnsafeMessage(context.out); RETURN 0; END;
  974. IF ~InsertFilesAndFixDestination(context, sourceString^, targetString^, source, target, count) THEN END;
  975. ELSE
  976. IF count >= LEN(source) THEN ResizeFilelist(source); ResizeFilelist(target); END;
  977. source[count] := sourceString;
  978. target[count] := targetString;
  979. INC(count);
  980. END;
  981. ELSE
  982. context.error.String("Command parsing error (res: "); context.error.Int(context.arg.res, 0); context.error.String(")");
  983. context.error.Ln;
  984. error := TRUE;
  985. END;
  986. ELSE
  987. context.error.String("Command parsing error: Exspected => token, found: "); context.error.String(filename);
  988. context.error.Ln;
  989. error := TRUE;
  990. END;
  991. ELSIF context.arg.res = Streams.EOF THEN
  992. done := TRUE;
  993. ELSE
  994. context.error.String("Command parsing error (res: "); context.error.Int(context.arg.res, 0); context.error.String(")");
  995. context.error.Ln;
  996. error := TRUE;
  997. END;
  998. END;
  999. IF error THEN count := Error; END;
  1000. RETURN count;
  1001. END GetFileLists;
  1002. PROCEDURE Safe*(context : Commands.Context);
  1003. BEGIN
  1004. unsafeMode := FALSE;
  1005. context.out.String("FSTools: SAFE mode."); context.out.Ln;
  1006. END Safe;
  1007. PROCEDURE Unsafe*(context : Commands.Context);
  1008. BEGIN
  1009. unsafeMode := TRUE;
  1010. context.out.String("FSTools: UNSAFE mode now. BE CAREFUL!"); context.out.Ln;
  1011. END Unsafe;
  1012. PROCEDURE ShowUnsafeMessage(out : Streams.Writer);
  1013. BEGIN
  1014. out.String("FSTools: Pattern matching is disabled in SAFE mode. Press FSTools.Unsafe ~ to enable pattern matching."); out.Ln;
  1015. END ShowUnsafeMessage;
  1016. PROCEDURE ShowRes(out : Streams.Writer; res : WORD);
  1017. BEGIN
  1018. out.String("(");
  1019. CASE res OF
  1020. Files.VolumeReadOnly: out.String("Target volume is read-only");
  1021. |Files.FsNotFound: out.String("File system not found");
  1022. |Files.FileAlreadyExists: out.String("File already exists");
  1023. |Files.BadFileName: out.String("Bad file name");
  1024. |Files.FileNotFound: out.String("File not found");
  1025. ELSE
  1026. out.String("res: "); out.Int(res, 0);
  1027. END;
  1028. out.String(")");
  1029. END ShowRes;
  1030. (** Close files -- paradox: open (old) file and call Close method. Intended for systems in a host environment to explicitely release a file handle. *)
  1031. PROCEDURE CloseFiles*(context : Commands.Context); (** [Options] {file} ~ *)
  1032. VAR
  1033. filelist : FileList;
  1034. nofFiles, res, n, ndone : LONGINT;
  1035. file: Files.File;
  1036. BEGIN
  1037. nofFiles := GetFileList(context, filelist);
  1038. n := 0; ndone := 0;
  1039. WHILE (n<nofFiles) & (filelist[n] # NIL) DO
  1040. file := Files.Old(filelist[n]^);
  1041. IF file # NIL THEN file.Close END;
  1042. INC(n);
  1043. END;
  1044. END CloseFiles;
  1045. (* returns if a file or directory exists. If yes, then fullname is set to filename *)
  1046. PROCEDURE Exists*(CONST name: ARRAY OF CHAR; VAR fullName: ARRAY OF CHAR; VAR flags: SET): BOOLEAN;
  1047. BEGIN
  1048. RETURN Files.Exists(name, fullName, flags);
  1049. END Exists;
  1050. END FSTools.
  1051. SystemTools.Free FSTools ~
  1052. FSTools.DeleteFiles X:*.Bak ~
  1053. FSTools.SplitFile BootManager.Bin 0200H BootManagerMBR.Bin BootManagerTail.Bin ~
  1054. FSTools.Directory Test.Mod ~