home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1998 April A / Pcwk4a98.iso / PROGRAM / DELPHI16 / Calmira / Src / SRC / FILEMAN.PAS < prev    next >
Pascal/Delphi Source File  |  1997-02-15  |  18KB  |  584 lines

  1. {**************************************************************************}
  2. {                                                                          }
  3. {    Calmira shell for Microsoft« Windows(TM) 3.1                          }
  4. {    Source Release 1.0                                                    }
  5. {    Copyright (C) 1997  Li-Hsin Huang                                     }
  6. {                                                                          }
  7. {    This program is free software; you can redistribute it and/or modify  }
  8. {    it under the terms of the GNU General Public License as published by  }
  9. {    the Free Software Foundation; either version 2 of the License, or     }
  10. {    (at your option) any later version.                                   }
  11. {                                                                          }
  12. {    This program is distributed in the hope that it will be useful,       }
  13. {    but WITHOUT ANY WARRANTY; without even the implied warranty of        }
  14. {    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         }
  15. {    GNU General Public License for more details.                          }
  16. {                                                                          }
  17. {    You should have received a copy of the GNU General Public License     }
  18. {    along with this program; if not, write to the Free Software           }
  19. {    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             }
  20. {                                                                          }
  21. {**************************************************************************}
  22.  
  23. unit FileMan;
  24.  
  25. { FileMan contains the main file management engine used for processing
  26.   files and directories.  It provides high-level operations which can
  27.   be used easily from other units, while allowing full user interaction
  28.   and error handling.
  29.  
  30.   Application.ProcessMessages is called frequently so that the progress
  31.   bar can be updated and the user can press the Cancel button.
  32.  
  33.   Almost all of Calmira's filename strings are stored and processed
  34.   as lower case to be consistent, especially useful because there is no
  35.   case insensitive Pos() function.  Also, all filenames should be fully
  36.   qualified to avoid ambiguities.
  37. }
  38.  
  39. interface
  40.  
  41. uses Classes, SysUtils, Dialogs;
  42.  
  43. type
  44.   EFileOpError = class(Exception);
  45.  
  46. const
  47.   faProtected = faReadOnly or faHidden or faSysFile;
  48.   faFileDir   = faAnyFile and not faVolumeID;
  49.  
  50. function CopyFile(const Filename, Destname : TFilename): Boolean;
  51. function MoveFile(const Filename, Destname : TFilename; attr: Integer): Boolean;
  52. function CopyDirectory(const Dirname, Destname : TFilename): Boolean;
  53. function MoveDirectory(const Dirname, Destname : TFilename): Boolean;
  54. function DeleteDirectory(const Dirname: TFilename): Boolean;
  55. function EraseFile(const Filename: string; attr: Integer): Boolean;
  56. procedure CreateDirectory(const Dirname: TFilename);
  57. procedure RemoveDirectory(const Dirname : TFilename);
  58. procedure ProcessFiles(files: TStrings; const dest: TFilename);
  59. procedure ExitDirectory(const Dirname : TFilename);
  60.  
  61. function DefaultExec(FileName, Params, DefaultDir: string;
  62.   ShowCmd: Word): Integer;
  63. { Encapsulates ShellExecute.  If a filename with no associated
  64.   program is encountered, the default viewer is used to open the file.  Also,
  65.   DOS environment strings are inserted into each string before they are
  66.   passed to Windows }
  67.  
  68.  
  69. function ExtensionIn(const ext : TFileExt; const list: string): Boolean;
  70. { Searches a string containing file extensions separated by
  71.   spaces.  It is case sensitive. }
  72.  
  73. procedure YesToAll;
  74. procedure NoToAll;
  75. procedure BackgroundProcess;
  76.  
  77. { ExtensionIn searches a string containing file extensions separated by
  78.   spaces.  It is case sensitive. }
  79.  
  80. var BytesTransferred : Longint;
  81.  
  82.  
  83.  
  84. implementation
  85.  
  86. uses Replace, Controls, FileCtrl, Progress, WinProcs, Settings, Debug,
  87.  Forms, Desk, AskDrop, Files, Strings, MiscUtil, Drives, WinTypes, Environs;
  88.  
  89. var
  90.   CopyAllFiles  : Boolean;
  91.   MoveAllFiles  : Boolean;
  92.   DelAllFiles   : Boolean;
  93.   RepAllFiles   : Boolean;
  94.   MoveAllProt   : Boolean;
  95.   DelAllProt    : Boolean;
  96.   CopyAllFolders: Boolean;
  97.   MoveAllFolders: Boolean;
  98.   DelAllFolders : Boolean;
  99.  
  100.  
  101. procedure NoToAll;
  102. begin
  103.   CopyAllFiles  := False;
  104.   MoveAllFiles  := False;
  105.   DelAllFiles   := False;
  106.   RepAllFiles   := False;
  107.   MoveAllProt   := False;
  108.   DelAllProt    := False;
  109.   CopyAllFolders:= False;
  110.   MoveAllFolders:= False;
  111.   DelAllFolders := False;
  112. end;
  113.  
  114. procedure YesToAll;
  115. begin
  116.   CopyAllFiles  := True;
  117.   MoveAllFiles  := True;
  118.   DelAllFiles   := True;
  119.   RepAllFiles   := True;
  120.   MoveAllProt   := True;
  121.   DelAllProt    := True;
  122.   CopyAllFolders:= True;
  123.   MoveAllFolders:= True;
  124.   DelAllFolders := True;
  125. end;
  126.  
  127.  
  128. function CanReplace(Filename, Destname: TFilename): Boolean;
  129. begin
  130.   { Returns True if the user specifies that the destination file
  131.     (which must exist) can be replaced. }
  132.  
  133.   if ReplaceBox = nil then ReplaceBox := TReplaceBox.Create(Application);
  134.  
  135.   case ReplaceBox.Query(Filename, Destname) of
  136.     mrYes   : Result := True;
  137.     mrNo    : begin ProgressBox.UpdateGauge; Result := False; end;
  138.     mrAll   : begin Result := True; RepAllFiles := True; end;
  139.     mrCancel: Abort;
  140.   end;
  141. end;
  142.  
  143.  
  144.  
  145. function ProtectBox(const name, op: string): Word;
  146. begin
  147.   { Asks the user for confirmation before deleting or moving
  148.     a protected file }
  149.  
  150.   Desktop.SetCursor(crDefault);
  151.   try
  152.     Result := MsgDialog(
  153.      Format('%s is Read Only, Hidden or System.'#13'%s this file?', [name, op]),
  154.        mtConfirmation, mbYesNoCancel + [mbAll], 0);
  155.   finally
  156.     Desktop.ReleaseCursor;
  157.   end;
  158. end;
  159.  
  160.  
  161. function ConfirmSingleOperation(Ask: Boolean; var All: Boolean;
  162.   const prompt, filename: string): Boolean;
  163. begin
  164.   Result := True;
  165.   if Ask and not All then begin
  166.     Desktop.SetCursor(crDefault);
  167.     try
  168.       case MsgDialog(Format('%s %s ?', [prompt, filename]),
  169.        mtConfirmation, [mbYes, mbNo, mbAll, mbCancel], 0) of
  170.         mrNo    : Result := False;
  171.         mrAll   : All := True;
  172.         mrCancel: Abort;
  173.       end;
  174.     finally
  175.       Desktop.ReleaseCursor;
  176.     end;
  177.   end;
  178. end;
  179.  
  180.  
  181. function CopyFile(const Filename, Destname : TFilename): Boolean;
  182. begin
  183.   Result := False;
  184.   ProgressBox.CheckForAbort;
  185.   ProgressBox.Updatelabel(Filename, Destname);
  186.  
  187.   if not ConfirmSingleOperation(ConfirmCopyFile, CopyAllFiles,
  188.     'Copy file', Filename) then Exit;
  189.  
  190.   if Filename = Destname then
  191.     raise EFileOpError.CreateFmt('Cannot copy %s to itself', [Filename]);
  192.  
  193.   if ConfirmReplace and not RepAllFiles and FFileExists(Destname)
  194.     and not CanReplace(Filename, Destname) then Exit;
  195.  
  196.   Application.ProcessMessages;
  197.   try
  198.     FCopyFile(Filename, Destname); { low-level copy in Files.pas }
  199.     ProgressBox.UpdateGauge;
  200.   except on EWriteAccessDenied do
  201.     if FileSetAttr(Destname, 0) < 0 then { try removing protection bits }
  202.       raise
  203.     else begin
  204.       FCopyFile(Filename, Destname);     { attempt the copy again }
  205.       ProgressBox.UpdateGauge;
  206.     end;
  207.   end;
  208.   Result := True;
  209. end;
  210.  
  211.  
  212. procedure CreateDirectory(const Dirname: TFilename);
  213. begin
  214.   try
  215.     MkDir(Dirname);
  216.   except on EInOutError do
  217.     raise EFileOpError.CreateFmt('Cannot create folder %s', [Dirname])
  218.   end;
  219. end;
  220.  
  221.  
  222. procedure CreateDirectoryMerge(const Dirname: TFilename);
  223. begin
  224.   { Similar to CreateDirectory, but used when copying or moving
  225.     whole directory structures.  If the destination already exists,
  226.     then the contents will be merged, in which case any window showing
  227.     the destination must be refreshed afterwards }
  228.  
  229.   if not FDirectoryExists(Dirname) then CreateDirectory(Dirname)
  230.   else Desktop.RefreshList.Add(Dirname);
  231. end;
  232.  
  233.  
  234. procedure ExitDirectory(const Dirname : TFilename);
  235. const
  236.   NewDir : string[3] = 'c:\';
  237. var
  238.   current : TFilename;
  239. begin
  240.   { If the current logged directory is somewhere inside Dirname,
  241.     the directory is changed to the Windows directory.  This is required
  242.     because directories cannot be deleted or renamed while they are logged }
  243.  
  244.   GetDir(DriveNumber(Dirname[1]), current);
  245.   current := Lowercase(current);
  246.   if (current = Dirname) or IsAncestorDir(Dirname, current) then begin
  247.     NewDir[1] := Dirname[1];
  248.     ChDir(NewDir);
  249.     ChDir(MakeDirname(WinPath));
  250.   end;
  251. end;
  252.  
  253.  
  254. procedure RemoveDirectory(const Dirname : TFilename);
  255. begin
  256.   { EInOutError is thrown away because the user may choose not to
  257.     delete a specific file during a directory-delete, in which case
  258.     the parent dir can't be removed.  We want to prevent the entire
  259.     operation from being aborted due to this. }
  260.  
  261.   try
  262.     ExitDirectory(Dirname);
  263.     RmDir(Dirname);
  264.   except
  265.     on EInOutError do;
  266.   end;
  267. end;
  268.  
  269.  
  270.  
  271. function MoveFile(const Filename, Destname : TFilename; attr: Integer): Boolean;
  272. begin
  273.   Result := False;
  274.   ProgressBox.CheckForAbort;
  275.   ProgressBox.UpdateLabel(Filename, Destname);
  276.  
  277.   if not ConfirmSingleOperation(ConfirmMoveFile, MoveAllFiles,
  278.     'Move file', Filename) then Exit;
  279.  
  280.   if Filename = Destname then
  281.     raise EFileOpError.CreateFmt('Cannot move %s to itself', [Filename]);
  282.  
  283.   if attr < 0 then attr := FileGetAttr(Filename);
  284.  
  285.   { Check for read-only, hidden or system file }
  286.  
  287.   if (attr and faProtected > 0) and ConfirmProtect and not MoveAllProt then
  288.     case ProtectBox(Filename, 'Move') of
  289.       mrNo    : begin ProgressBox.UpdateGauge; exit; end;
  290.       mrCancel: Abort;
  291.       mrAll   : MoveAllProt := True;
  292.     end;
  293.  
  294.   { If destination already exists, ask before replacing it.  If the
  295.     user says "yes", try deleting it so that the move can be performed
  296.     by a rename operation.  If the first delete fails, reset the attributes
  297.     and try again }
  298.  
  299.   if FFileExists(Destname) then
  300.     if not ConfirmReplace or RepAllFiles or CanReplace(Filename, Destname) then begin
  301.       if not DeleteFile(Destname) then
  302.         if (FileSetAttr(Destname, 0) < 0) or not DeleteFile(Destname) then
  303.           raise EFileOpError.CreateFmt('Cannot replace %s', [Destname])
  304.     end
  305.     else Exit;
  306.  
  307.   Application.ProcessMessages;
  308.  
  309.   { Files on the same drive are moved using a rename.  Those on
  310.     different drives are copied, and the original is deleted afterwards. }
  311.  
  312.   if (UpCase(Filename[1]) <> UpCase(Destname[1])) or
  313.     not RenameFile(Filename, Destname) then begin
  314.  
  315.     FCopyFile(Filename, Destname);
  316.     if (not DeleteFile(Filename)) and (attr and faReadOnly > 0) and
  317.       ((FileSetAttr(Filename, 0) < 0) or not DeleteFile(Filename)) then
  318.         raise EFileOpError.CreateFmt('Cannot move %s', [Filename]);
  319.   end;
  320.  
  321.   ProgressBox.UpdateGauge;
  322.   Result := True;
  323. end;
  324.  
  325.  
  326.  
  327. function CopyDirectory(const Dirname, Destname : TFilename):Boolean;
  328. var
  329.   source, target: TFileName;
  330.   code : Integer;
  331.   rec : TSearchRec;
  332. begin
  333.   { CopyDirectory recursively scans a directory structure and recreates
  334.     the contents elsewhere.  Both CreateDirectoryMerge and CopyFile will
  335.     raise exceptions on error, which terminates this procedure.
  336.  
  337.     We must check that Destname is not the same as, or a subdirectory of
  338.     Dirname, otherwise you will cause an infinite recursion, which XCOPY
  339.     calls a cyclic copy :
  340.  
  341.     e.g. CopyDirectory('c:\windows', 'c:\windows\temp') }
  342.  
  343.   Result:= False;
  344.   ProgressBox.CheckForAbort;
  345.  
  346.   if (Dirname = Destname) or IsAncestorDir(Dirname, Destname) then
  347.     raise EFileOpError.Create('Cannot perform a cyclic copy');
  348.  
  349.   if not ConfirmSingleOperation(ConfirmCopyFolder, CopyAllFolders,
  350.     'Copy folder', Dirname) then Exit;
  351.  
  352.   CreateDirectoryMerge(Destname);
  353.  
  354.   code := FindFirst(Dirname + '\*.*', faFileDir, rec);
  355.   while code = 0 do begin
  356.     if rec.name[1] <> '.' then begin
  357.       rec.name := Lowercase(rec.name);
  358.       source := Dirname + '\' + Lowercase(rec.name);
  359.       target := Destname + '\' + Lowercase(rec.name);
  360.  
  361.       if rec.attr and faDirectory <> 0 then
  362.         Result := CopyDirectory(source, target)
  363.       else begin
  364.         CopyFile(source, target);
  365.         Inc(BytesTransferred, rec.size);
  366.       end;
  367.     end;
  368.     code := FindNext(rec);
  369.   end;
  370.   Result := True;
  371. end;
  372.  
  373.  
  374. function MoveDirectory(const Dirname, Destname : TFilename): Boolean;
  375. var
  376.   source, target: TFilename;
  377.   code : Integer;
  378.   rec : TSearchRec;
  379. begin
  380.   { The structure of this is very similar to CopyDirectory, and the
  381.     same rules about cyclic copying applies }
  382.  
  383.   Result := False;
  384.   ProgressBox.CheckForAbort;
  385.  
  386.   if (Dirname = Destname) or IsAncestorDir(Dirname, Destname) then
  387.     raise EFileOpError.Create('Cannot perform a cyclic move');
  388.  
  389.   if not ConfirmSingleOperation(ConfirmMoveFolder, MoveAllFolders,
  390.     'Move folder', Dirname) then Exit;
  391.  
  392.   CreateDirectoryMerge(Destname);
  393.  
  394.   code := FindFirst(Dirname + '\*.*', faFileDir, rec);
  395.   while code = 0 do begin
  396.     if rec.name[1] <> '.' then begin
  397.       source := Dirname + '\' + Lowercase(rec.name);
  398.       target := Destname + '\' + Lowercase(rec.name);
  399.  
  400.       if rec.attr and faDirectory <> 0 then
  401.         Result := MoveDirectory(source, target)
  402.       else begin
  403.         Result := MoveFile(source, target, rec.attr);
  404.         Inc(BytesTransferred, rec.size);
  405.       end;
  406.     end;
  407.     code := FindNext(rec);
  408.   end;
  409.  
  410.   RemoveDirectory(Dirname);
  411.   Result := True;
  412. end;
  413.  
  414.  
  415. function DeleteDirectory(const Dirname: TFilename): Boolean;
  416. var
  417.   target: TFilename;
  418.   code  : Integer;
  419.   rec   : TSearchRec;
  420. begin
  421.   Result := False;
  422.   ProgressBox.CheckForAbort;
  423.  
  424.   if not ConfirmSingleOperation(ConfirmDelFolder, DelAllFolders,
  425.     'Delete folder', Dirname) then Exit;
  426.  
  427.   code := FindFirst(Dirname + '\*.*', faFileDir, rec);
  428.   while code = 0 do begin
  429.     if rec.name[1] <> '.' then begin
  430.       target := Dirname + '\' + Lowercase(rec.name);
  431.       if rec.attr and faDirectory <> 0 then Result := DeleteDirectory(target)
  432.       else EraseFile(target, rec.attr);
  433.     end;
  434.     code := FindNext(rec);
  435.   end;
  436.  
  437.   RemoveDirectory(Dirname);
  438.   Result := True;
  439. end;
  440.  
  441.  
  442.  
  443. function EraseFile(const Filename: string; attr: Integer): Boolean;
  444. begin
  445.   Result := False;
  446.   ProgressBox.CheckForAbort;
  447.  
  448.   if not ConfirmSingleOperation(ConfirmDelFile, DelAllFiles,
  449.     'Delete file', Filename) then Exit;
  450.  
  451.   if attr = -1 then attr := FileGetAttr(Filename);
  452.  
  453.   if attr and faProtected <> 0 then
  454.     if ConfirmProtect and not DelAllProt then
  455.       case ProtectBox(Filename, 'Delete') of
  456.         mrYes    : FileSetAttr(Filename, 0);
  457.         mrNo     : begin
  458.                      ProgressBox.UpdateGauge;
  459.                      Exit;
  460.                    end;
  461.         mrCancel : Abort;
  462.         mrAll    : begin
  463.                      DelAllProt := True;
  464.                      FileSetAttr(Filename, 0);
  465.                    end;
  466.       end
  467.     else FileSetAttr(Filename, 0);
  468.  
  469.   if not DeleteFile(Filename) then
  470.     raise EFileOpError.CreateFmt('Cannot delete %s', [Filename]);
  471.  
  472.   ProgressBox.UpdateGauge;
  473.   Result := True;
  474. end;
  475.  
  476.  
  477. procedure ProcessFiles(files: TStrings; const dest: TFilename);
  478. var
  479.   i : Integer;
  480.   CopyDroppedFiles: Boolean;
  481.   destpath : TFilename;
  482. begin
  483.   { Mainly used to handle file drops from other programs.  A list of
  484.     filenames will be copied or moved after asking the user, and all
  485.     affected windows are refreshed.
  486.  
  487.     Note that file descriptions are NOT preserved. }
  488.  
  489.   i := 0;
  490.   while i < files.Count do
  491.     if not FileExists(files[i]) then files.Delete(i)
  492.     else inc(i);
  493.  
  494.   if files.Count = 0 then
  495.     raise EFileOpError.Create('No files found');
  496.  
  497.   destpath := MakePath(dest);
  498.  
  499.   try
  500.     AskDropBox := TAskDropBox.Create(Application);
  501.     case AskDropBox.ShowModal of
  502.       mrOK : CopyDroppedFiles := True;
  503.       mrYes: CopyDroppedFiles := False;
  504.       mrCancel: Abort;
  505.     end
  506.   finally
  507.     AskDropBox.Free;
  508.     AskDropBox := nil;
  509.   end;
  510.  
  511.   if CopyDroppedFiles then ProgressBox.Init(foCopy, files.Count)
  512.   else ProgressBox.Init(foMove, files.Count);
  513.  
  514.   try
  515.     NoToAll;
  516.     for i := 0 to files.Count-1 do begin
  517.       if CopyDroppedFiles then
  518.         CopyFile(files[i], destpath + ExtractFilename(files[i]))
  519.       else begin
  520.         MoveFile(files[i], destpath + ExtractFilename(files[i]), -1);
  521.         Desktop.RefreshList.Add(ExtractFileDir(files[i]));
  522.       end;
  523.     end;
  524.     Desktop.RefreshList.Add(dest);
  525.   finally
  526.     ProgressBox.Hide;
  527.     Desktop.RefreshNow;
  528.     PlaySound(Sounds.Values['NotifyCompletion']);
  529.   end;
  530. end;
  531.  
  532.  
  533. function DefaultExec(Filename, Params, DefaultDir: string;
  534.   ShowCmd: Word): Integer;
  535. begin
  536.   { Substitute environment variables }
  537.   Filename := EnvironSubst(Filename);
  538.   Params := EnvironSubst(Params);
  539.   DefaultDir := EnvironSubst(DefaultDir);
  540.  
  541.   Result := ExecuteFile(Filename, Params, DefaultDir, 'Open', ShowCmd);
  542.  
  543.   { ShellExecute sometimes return error code 2 (file not fount), for a
  544.     file with no extension.  Code 31 means that no associated program
  545.     exists. }
  546.  
  547.   if (Result = 31) or ((Result = 2) and FileExists(Filename)) then begin
  548.     if DefaultProg > ''  then begin
  549.       Result := ExecuteFile(EnvironSubst(DefaultProg), QualifiedFilename(Filename),
  550.         DefaultDir, 'Open', SW_SHOWNORMAL);
  551.       if Result <= 32 then ErrorMsg('Unable to run default viewer.')
  552.     end
  553.     else
  554.       ErrorMsg('This file is not assocated with a program.  Use File Manager to '+
  555.         'make an association, or specify a default file viewer to use.');
  556.   end
  557.   else if Result <= 32 then
  558.     ErrorMsg('Unable to run program or view file.');
  559. end;
  560.  
  561.  
  562.  
  563. function ExtensionIn(const ext : TFileExt; const list: string): Boolean;
  564. var temp: string[5];
  565. begin
  566.   temp[0] := ext[0];
  567.   Inc(temp[0], 2);
  568.   temp[1] := ' ';
  569.   temp[2] := ext[1];
  570.   temp[3] := ext[2];
  571.   temp[4] := ext[3];
  572.   temp[Length(temp)] := ' ';
  573.   Result := Pos(temp, list) > 0;
  574. end;
  575.  
  576.  
  577. procedure BackgroundProcess;
  578. begin
  579.   Application.ProcessMessages;
  580. end;
  581.  
  582.  
  583. end.
  584.