Link to home
Start Free TrialLog in
Avatar of rincewind666
rincewind666

asked on

Listing files & sub-directories (everything) in a directory of a web server.

At a click of a button, I want to give the user the option of deleting a directory on a web server.  The path to this directory is always the same (home/username/http-docs/list) where a directory called "list" can be deleted.  Unfortunately a directory cannot be deleted when it has other directories & files in it.  I already have the code to log in/out of the server. All I need is  some code to do EITHER of the following:

1. List all the directories and files in a directory and ALSO their sub-directories. Everything has to be listed so I can set up a delete routine. I already have the path to the top directory.

OR....

2. Code to delete a directory even if it is not empty.

OR....

3. Code to rename a directory even if not empty.

I am using Delphi 6.  This is URGENT so I am giving the maximum 500 points for this. Many thanks for your help.

Avatar of LRHGuy
LRHGuy

You set up a recursive delete:

Here's what I use:

{ Recursive! } {LRH 02-12-12}
{ Returns true if it thinks it's successful }
function Std_ClearPath(aPath:Std_String):boolean;
const
  cAny=faAnyFile;
  cDir=faDirectory;
var
  SR:TSearchRec;
  DosError:integer;
  DP:boolean;
begin
  if aPath='' then begin
    { Failsafe to prevent ROOT DIRECTORY delete! }
    Result:=False;
    exit;
  end;

  Result:=True;
  aPath:=Std_AddBackSlash(aPath);
  DosError:=
    FindFirst(aPath+'*.*',cAny,SR);
  while DosError=0 do begin
    if (SR.Name[1]<>'.') then begin
      if (SR.Attr and cDir)=cDir then begin
        { A directory }
        DP:=Std_ClearPath(aPath+SR.Name);
        Result:=Result and DP;
        if DP then begin
          { Kill directory }
          Try
            RMDir(aPath+SR.Name);
          Except
            Result:=False;
          end;
        end;
      end
      else begin
        { A file }
        Std_EraseFile(aPath+SR.name);
        Result:=Result and (STD_IOResult=0);
      end;
    end;
    DosError:=
      FindNext(SR);
  end;
  FindClose(SR);
end;

Oops...you'll need to make a few changes:

Std_AddBackSlash()
 - Adds a backslash to the path if it does not end in one..
  Replace with IncludeTrailingBackslash()

Std_EraseFile()
  -Erases the given file...
  Replace with DeleteFile()
Oops again...

Std_String can be either "String" or "ShortString".

(Just pulling a routine out of my library...I didn't realize how dependant it was on other items!)
Avatar of rincewind666

ASKER

That was quick!

I am getting the following errors:

[Error] Unit1.pas(4821): Undeclared identifier: 'Std_String'
[Error] Unit1.pas(4837): Undeclared identifier: 'Std_AddBackSlash'
[Error] Unit1.pas(4849): There is no overloaded version of 'RmDir' that can be called with these arguments
[Error] Unit1.pas(4857): Undeclared identifier: 'Std_EraseFile'
[Error] Unit1.pas(4858): Undeclared identifier: 'STD_IOResult'
[Warning] Unit1.pas(4858): Comparing signed and unsigned types - widened both operands
[Fatal Error] CGI_Installer.dpr(27): Could not compile used unit 'Unit1.pas'

Maybe I should have said that I am a beginner in Delphi so be gentle! Many thanks.
Here it is updated as I suggested. I use Delphi7 .. the RmDir seems OK. What version do you use?

function Std_ClearPath(aPath:String):boolean;
const
  cAny=faAnyFile;
  cDir=faDirectory;
var
  SR:TSearchRec;
  DosError:integer;
  DP:boolean;
begin
  if aPath='' then begin
    { Failsafe to prevent ROOT DIRECTORY delete! }
    Result:=False;
    exit;
  end;

  Result:=True;
  aPath:=IncludeTrailingBackSlash(aPath);
  DosError:=
    FindFirst(aPath+'*.*',cAny,SR);
  while DosError=0 do begin
    if (SR.Name[1]<>'.') then begin
      if (SR.Attr and cDir)=cDir then begin
        { A directory }
        DP:=Std_ClearPath(aPath+SR.Name);
        Result:=Result and DP;
        if DP then begin
          { Kill directory }
          Try
            RemoveDir(aPath+SR.Name);
          Except
            Result:=False;
          end;
        end;
      end
      else begin
        { A file }
        DP:=DeleteFile(aPath+SR.name);
        Result:=Result and DP;
      end;
    end;
    DosError:=
      FindNext(SR);
  end;
  FindClose(SR);
end;
procedure _SearchDirectory(List: TStrings; Directory: String;
  const Recursive: Boolean);
var
  bFoundFile: Boolean;
  mySearchRec: TSearchRec;
  sFileName: String;
begin
  Directory := IncludeTrailingPathDelimiter(Directory);
  bFoundFile := FindFirst(Directory + '*.*', faAnyFile, mySearchRec) = 0;
  while bFoundFile do
  begin
    // skip "." and ".."
    if (mySearchRec.Name[1] <> '.') then
    begin
      sFileName := Directory + mySearchRec.Name;
      if ((mySearchRec.Attr and faDirectory) = 0) then
      begin // found a file
        List.Add(sFileName);
      end
      else
      begin // found a directory
        sFileName := IncludeTrailingPathDelimiter(sFileName);
        List.Add(sFileName);
        // list the subdirectory
        if Recursive then
          _SearchDirectory(List, sFileName, Recursive);
      end;
    end;
    // find next file
    bFoundFile := FindNext(mySearchRec) = 0;
  end;
  FindClose(mySearchRec);
end;

an example to use it, drop a TMemo component on hte form:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;
  _SearchDirectory(Memo1.Lines, 'D:\Work\', True);
end;
I now see you're using D6, so what I posted should work A-OK!

You can use IncludeTrailingPathDelimiter instead of IncludeTrailingBackslash if you wish. The former if platform independant.

Oh, I really hate it when people use a recursive function to enumerate folders...

uses
  SysUtils, Classes;

procedure EnumFolders(Root: string; List: TStringList);
var
  SearchRec: TSearchRec;
  Index: Integer;
begin
  List := TStringList.Create;
  List.Add(IncludeTrailingPathDelimiter(Root));
  Index := 0;
  while (Index < List.Count) do begin
    if (FindFirst(IncludeTrailingPathDelimiter(List[Index]) + '*.*', faAnyFile, SearchRec) = 0) then begin
      repeat
        if ((SearchRec.Attr and faDirectory) <> 0) and (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then List.Add(IncludeTrailingPathDelimiter(List[Index]) + SearchRec.Name);
      until (FindNext(SearchRec) <> 0);
    end;
    Inc(Index);
  end;
end;

Now guys, please stop using that stupid recursive version because IT STINKS!!! ;-)

My version doesn't add files to the list, though. Only folders. But adding files would not be a real problem. If need be, use two lists. One for folders only and one for files only. Then walk through the files list first to delete all these files, then walk through the folders list, bottom to top, to remove all folders.
Oh, and remove the line: List := TStringList.Create;
I copied the code from a small console application I've once written. The code created the list, would save it to file, then free it. Now you have to create and free the list outside this function and call the function like:

begin
  List := TStringList.Create;
  EnumFolders('C:\WinNT', List);
  List.SaveToFile('Or whatever you like');
  List.Free;
end;
maybe your solution is better looking to you :-)
i personally prefere the order the items are added to the list using recursion
Well, bpana, since it's a stringlist you can always execute List.Sort to get it in your order. :-P

Then again, if you want performance, the non-recursive method will probably perform a little faster, although it's difficult to measure since a lot of action is done by disk access. And actually, all I'm saying is that enumerating folders doesn't require any recursive functionality. I just showed an easy, non-recursive technique. I wished more people were aware of this much simpler solution.
I am trying to use the following from LRHGuy (as it deletes which is the nearest to my requirements).  Unfortunately it doesn't do anything - I'm not sure that I'm using the correct path for aPath. I try it and connect using another FTP program and the folder & files are still there.  It is a Unix server. I am using using a FTP component from argosoft.com


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Mssocket, msFTP;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    FTPClient1: TmsFTPClient;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function Std_ClearPath(aPath:String):boolean;
const
  cAny=faAnyFile;
  cDir=faDirectory;
var
  SR:TSearchRec;
  DosError:integer;
  DP:boolean;
begin
  if aPath='' then begin
    { Failsafe to prevent ROOT DIRECTORY delete! }
    Result:=False;
    exit;
  end;

  Result:=True;
  aPath:=IncludeTrailingPathDelimiter(aPath);
  DosError:=
    FindFirst(aPath+'*.*',cAny,SR);
  while DosError=0 do begin
    if (SR.Name[1]<>'.') then begin
      if (SR.Attr and cDir)=cDir then begin
        { A directory }
        DP:=Std_ClearPath(aPath+SR.Name);
        Result:=Result and DP;
        if DP then begin
          { Kill directory }
          Try
            RemoveDir(aPath+SR.Name);
          Except
            Result:=False;
          end;
        end;
      end
      else begin
        { A file }
        DP:=DeleteFile(aPath+SR.name);
        Result:=Result and DP;
      end;
    end;
    DosError:=
      FindNext(SR);
  end;
  FindClose(SR);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin

Screen.Cursor := crHourGlass;

FTPClient1.Host := 'ftp.something.com';
FTPClient1.UserName := 'something';
FTPClient1.Password := 'password';
FTPClient1.Login;

Std_ClearPath('/home/something/http-docs/list');

FTPClient1.Logout;

Label1.Caption := 'TEST COMPLETE.......';
Screen.Cursor := crDefault;


end;

end.
If we are talking about performance, the sort thing is an extra step :)

But anyway, your non-recursive method it's also a good example ...
Oops! That's a slightly different problem! I didn't know you were deleting a remote directory. And through FTP it's even harder!

You'd have to send the commands to read the directory and delete the files through the ftp client.

I don't know anything about the argosfot ftp, but the "removedir" command for ftp is "rmdir", and the "deletefile" command is "delete". Plus, you'll have to read each directory from the remote system with the correct ftp command (dir).

In general, it's the same procedure, but you have to use the proper ftp commands in the clearpath procedure.
Oops... Deleting through FTP. Hadn't noticed that either. Oh, well... Still, same technique. Enumerate all the files and folders, then Delete() and RemoveDir() them with the TidFTP Indy component. Using List() you get a list of files and directories in a certain folder. By using *.* as specifier you should get all files while if you use just one * you should get all folders in the current folder. Make sure to NOT ask details. Unfortunately I don't have time right now to be more specific... Maybe later, if no one else beats me to it. :-)
listening
You could work with the Indy components... it is free and easy. And Indy has a FTP component and you can find in Delphi Demos... exactly what you are looking for... except for the deletion of the non-empty directory. For the deletion you could modify the procedures above... and using the demo it would be an easy job.
Cheers
ASKER CERTIFIED SOLUTION
Avatar of bpana
bpana

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
bpana, Where can I get TIdFTP?  Thanks.
Also, where can I get the Indy component?  I've made a search of Delphi Superpage, Torrys etc but can find nothing.  Thanks for your help.
you said you use Delphi 6, so in the components pallete, on the Indy Clients tab you'll find the TIdFTP component.
but you should upgrade to a newer version:
http://www.indyproject.org/download/Borland.html
i made the previous example in delphi 7 which has a newer version of Indy than delphi 6, and now i've seen that in delphi 6 is missing the idFTP.DirectoryListing property.

you should try to upgrade to a newer version

let me know if you have any problem
Many thanks for your help.