Solved

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

Posted on 2004-09-02
24
598 Views
Last Modified: 2010-04-05
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.

0
Comment
Question by:rincewind666
  • 7
  • 6
  • 5
  • +2
24 Comments
 
LVL 7

Expert Comment

by:LRHGuy
ID: 11966196
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;

0
 
LVL 7

Expert Comment

by:LRHGuy
ID: 11966248
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()
0
 
LVL 7

Expert Comment

by:LRHGuy
ID: 11966288
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!)
0
 

Author Comment

by:rincewind666
ID: 11966394
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.
0
 
LVL 7

Expert Comment

by:LRHGuy
ID: 11966552
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;
0
 
LVL 6

Expert Comment

by:bpana
ID: 11966561
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;
0
 
LVL 7

Expert Comment

by:LRHGuy
ID: 11966603
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.

0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11966980
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.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11967023
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;
0
 
LVL 6

Expert Comment

by:bpana
ID: 11967586
maybe your solution is better looking to you :-)
i personally prefere the order the items are added to the list using recursion
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11968684
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.
0
 

Author Comment

by:rincewind666
ID: 11968823
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.
0
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
LVL 6

Expert Comment

by:bpana
ID: 11968848
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 ...
0
 
LVL 7

Expert Comment

by:LRHGuy
ID: 11969011
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.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11970969
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. :-)
0
 
LVL 11

Expert Comment

by:calinutz
ID: 11971095
listening
0
 
LVL 11

Expert Comment

by:calinutz
ID: 11971111
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
0
 
LVL 6

Accepted Solution

by:
bpana earned 500 total points
ID: 11973750
use TIdFTP.

// recursive procedure - delete all the files and folders
procedure RecDelete(AIdFtp: TIdFTP; ADir: string);
var
  sl: TStringList;
  i, n: integer;
  NewDir: string;
begin
  sl := TStringList.Create;

  if Trim(ADir) <> '' then
  begin
    try
      AIdFtp.ChangeDir(ADir);
    except
      AIdFtp.Delete(ADir);
      AIdFtp.MakeDir(ADir);
      AIdFtp.ChangeDir(ADir);
    end;
  end;

  AIdFtp.List(sl, '*.*', True);
  for i := 0 to AIdFtp.DirectoryListing.Count - 1 do
  begin
    AIdFtp.Delete(AIdFtp.DirectoryListing.Items[i].FileName);
  end;

  AIdFtp.List(sl, '*', True);
  if AIdFtp.DirectoryListing.Count = 0 then
    AIdFtp.ChangeDirUp
  else
  begin
    n := 0;
    n := AIdFtp.DirectoryListing.Count - 1;
    i := n;
    while i >= 0 do
    begin
      NewDir := AIdFtp.DirectoryListing.Items[i].FileName;
      RecDelete(AIdFtp, NewDir);
      AIdFtp.RemoveDir(NewDir);
      AIdFtp.List(sl, '*', True);
      i := i - 1;
    end;
    AIdFtp.ChangeDirUp;
  end;
  sl.Free;
end;

Example of use:
procedure TForm1.Button1Click(Sender: TObject);
begin
  IdFTP1.Connect(True, 60);
  RecDelete(IdFTP1, 'YourDirectory');
  IdFTP1.Disconnect;
  IdFTP1.Quit;
  ShowMessage('End');
end;
0
 

Author Comment

by:rincewind666
ID: 11975173
bpana, Where can I get TIdFTP?  Thanks.
0
 

Author Comment

by:rincewind666
ID: 11975551
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.
0
 
LVL 6

Expert Comment

by:bpana
ID: 11975793
you said you use Delphi 6, so in the components pallete, on the Indy Clients tab you'll find the TIdFTP component.
0
 
LVL 6

Expert Comment

by:bpana
ID: 11975860
but you should upgrade to a newer version:
http://www.indyproject.org/download/Borland.html
0
 
LVL 6

Expert Comment

by:bpana
ID: 11976077
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
0
 

Author Comment

by:rincewind666
ID: 12167647
Many thanks for your help.
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Suggested Solutions

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now