[Last Call] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

A program that will search for ONLY empty directories and delete all of them but NOT dirs with anything in it?

Posted on 2004-11-17
17
Medium Priority
?
351 Views
Last Modified: 2010-04-05
What is the code to make a program with one button that searches my drives and deletes all the empty directories? It must not delete anything else, only those who are empty.

Fdehell
0
Comment
Question by:fdehell
  • 6
  • 6
  • 3
  • +1
17 Comments
 
LVL 17

Expert Comment

by:geobul
ID: 12602263
Hi,

The function below will remove all empty folders from a subtree:

procedure DelEmptyFolders(dir: string);
var
 Search : TSearchRec;
 ok: integer;
begin
 ok := FindFirst(dir + '\*.*',faDirectory,Search);
 while ok = 0 do  begin
   if ((Search.Name <> '.' ) and (Search.Name <> '..')) then begin
     DelEmptyFolders(dir + '\' + Search.Name);
   end;
   ok := FindNext(Search);
 end;
 FindClose(Search);
 try
   RmDir(dir);
 except
   // do nothing - dir is not empty
 end;
end;

// use it for all of your drives like:
procedure TForm1.Button1Click(Sender: TObject);
begin
  DelEmptyFolders('c:\');
end;

Regards, Geo
0
 
LVL 17

Expert Comment

by:geobul
ID: 12602392
The example above should be:

// use it for all of your drives like:
procedure TForm1.Button1Click(Sender: TObject);
begin
  DelEmptyFolders('c:');
end;

For deleting all empty folders from all your logical volumes (including floppy and mapped drives) try this:

function GetDrives: string;
var
  buf: array [0..1023] of char;
  len, i: integer;
begin
  result := '';
  len := GetLogicalDriveStrings(1024, buf);
  if len > 0 then begin
    for i := 0 to len - 1 do begin
      if buf[i] = #0 then buf[i] := #13;
      result := result + buf[i];
    end;
  end;
end;

function DiskInDrive(Drive: Char): Boolean;
var
  ErrorMode: word;
begin
  Drive := UpCase(Drive);
  if not (Drive in ['A'..'Z']) then
    raise EConvertError.Create('Not a valid drive ID');
  ErrorMode := SetErrorMode(SEM_FailCriticalErrors);
  try
    if DiskSize(Ord(Drive) - $40) = -1 then
      DiskInDrive := False
    else
      DiskInDrive := True;
  finally
    SetErrorMode(ErrorMode);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
  i: integer;
begin
  sl := TStringList.Create;
  try
    sl.Text := GetDrives;
    for i := 0 to sl.Count - 1 do begin
      if DiskInDrive(sl[i][1]) then begin
        DelEmptyFolders(Copy(sl[i],1,2));
      end;
    end;
  finally
    sl.Free;
  end;
end;

Regards, Geo
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12602727
Deleting empty folders might not be a very good idea, since some programs might depend on those empty folders. And not all applications will re-create them if they cannot be found.

The following code fills a stringlist with all empty files within a certain folder:
procedure EnumerateEmptyFolder( const Folder: string; EmptyFolders: TStringList );
var
  I: Integer;
  SearchRec: TSearchRec;
  Folders: TStringList;
begin
  EmptyFolders.Clear;
  Folders := TStringList.Create;
  Folders.Add( IncludeTrailingBackslash( Folder ) );
  I := 0;
  while ( I < Folders.Count ) do begin
    if ( FindFirst( Folders[ I ] + '*.*', faDirectory, SearchRec ) = 0 ) then begin
      repeat
        if ( ( SearchRec.Attr and faDirectory ) = faDirectory ) then begin
          if not ( ( SearchRec.Name = '.' ) or ( SearchRec.Name = '..' ) ) then begin
            Folders.Add( IncludeTrailingBackslash( Folders[ I ] + SearchRec.Name ) );
          end;
        end;
      until ( FindNext( SearchRec ) <> 0 );
      FindClose( SearchRec );
      if ( FindFirst( Folders[ I ] + '*.*', faAnyFile - faDirectory, SearchRec ) = 0 ) then begin
        FindClose( SearchRec );
      end
      else begin
        EmptyFolders.Add( Folders[ I ] );
      end;
    end;
    Inc( I );
  end;
  Folders.Free;
  EmptyFolders.Sort;
end;

But with empty, I mean they don't contain any files but they could still contain any folders. But consider that you have the path
C:\Empty\Folders\going|several\levels\deep
and none of these folders contain any data, would you want to delete them or not? Would you like to just delete the Deep folder or also the folders, levels, several. going, folders and Empty? Well, above code will put all these folders in the list, and if you start deleting them from the bottom upwards (for I := pred(List.count) downto 0 do rmdir(List[I]);) then you would delete them all. If you only want to delete the outer level empty folders (thus "deep" only) then use this code to create the list:

procedure EnumerateOuterEmptyFolder( const Folder: string; EmptyFolders: TStringList );
var
  I: Integer;
  SearchRec: TSearchRec;
  Folders: TStringList;
  HasContents: Boolean;
begin
  EmptyFolders.Clear;
  Folders := TStringList.Create;
  Folders.Add( IncludeTrailingBackslash( Folder ) );
  I := 0;
  while ( I < Folders.Count ) do begin
    HasContents := False;
    if ( FindFirst( Folders[ I ] + '*.*', faDirectory, SearchRec ) = 0 ) then begin
      repeat
        if not ( ( SearchRec.Name = '.' ) or ( SearchRec.Name = '..' ) ) then begin
          HasContents := True;
          if ( ( SearchRec.Attr and faDirectory ) = faDirectory ) then begin
            Folders.Add( IncludeTrailingBackslash( Folders[ I ] + SearchRec.Name ) );
          end;
        end;
      until ( FindNext( SearchRec ) <> 0 );
      FindClose( SearchRec );
    end;
    if not HasContents then EmptyFolders.Add( Folders[ I ] );
    Inc( I );
  end;
  Folders.Free;
  EmptyFolders.Sort;
end;

The list it returns is sorted by foldername which is just useful. Be aware that you have to create the list you want to be filled yourself.

I would suggest that you use this list of foldernames just to display them to the user so the user can check or unchech which files he wants to keep and which ones must be deleted. That way, you could avoid deleting critical folders.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12602739
And before I forget, on NT/W2K/XP, the user might have limited access and might not even see all files in a folder. Thus, for this user a folder might appear empty while it actually contains data that the user isn't allowed to see. (No, these are not system files or hidden files but files with specific security settings denying the user access even to view the files.)
0
 
LVL 14

Accepted Solution

by:
Pierre Cornelius earned 2000 total points
ID: 12604359
Here's another version also using Findfirst, etc. It basically searches all directories from a specified path and drills down into all sub-levels counting the number of files and directories in a specific directory. The deletion part simply runs through the enumeration and removes directories with a count of 0 (i.e. those with no files or folders). I've built in some prompts for the user to confirm deletion of a directory before doing so.


Create a new app, drop a button on it and call it "btnDelDirs";
Then copy the follow into the unit.

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btnDelDirs: TButton;
    procedure btnDelDirsClick(Sender: TObject);
  end;

  TFileCount = record
    Filename: string;
    count: integer;
  end;
  TDirFileCount = array of TFileCount;
var
  Form1: TForm1;

implementation

{$R *.dfm}

function DirFileCountIndexOf(dfc: TDirFileCount; fn: string): integer;
var i: integer;
begin
  result:= -1;
  for i:= low(dfc) to High(dfc) do
    if dfc[i].Filename = fn then
    begin
      result:= i;
      break;
    end;
  if result = -1 then
    raise exception.Create('Filename index not found in directory list.');
end;

function BuildDirList(path: string; var DirFileCount: TDirFileCount): string;
var sr: TSearchRec;
    s: string;
begin
  result:= '';
  if path = '' then path:= 'c:\';
  if path[length(path)] <> '\' then path:= path + '\';
  if (length(DirFileCount) = 0) then
  begin
    SetLength(DirFileCount, 1);
    DirFileCount[0].Filename:= path;
    DirFileCount[0].count:= 0;
  end;
  try
    if FindFirst(path+'*.*', faDirectory, sr) = 0 then
      //then raise exception.Create('Error building directory list.');

    repeat
      if (sr.Name <> '.') AND (sr.Name <> '..') then
        if (sr.Attr = faDirectory)
        then begin
          result:= result+path+sr.Name + #13#10;

            SetLength(DirFileCount, length(DirFileCount)+1);
            DirFileCount[Length(DirFileCount)-1].Filename:= path+sr.Name+'\';
            //directories must also be counted
            s:= path+ExtractFilePath(sr.Name);
            Inc(DirFileCount[DirFileCountIndexOf(DirFileCount, s)].count);

          result:= result + BuildDirList(path+sr.Name+'\', DirFileCount);
        end
        else try
          s:= path+ExtractFilePath(sr.Name);
          Inc(DirFileCount[DirFileCountIndexOf(DirFileCount, s)].count);
        except end;

    until FindNext(sr) <> 0
  finally
    FindClose(sr);
  end;
end;

procedure DeleteEmptyDirs(path: string);
var dfc: TDirFileCount;
    i: integer;
begin
  SetLength(dfc,0);
  BuildDirList(path, dfc);
  for i:= low(dfc) to High(dfc) do
  begin
    if dfc[i].count = 0 then
    case MessageDlg('You are attempting to delete the following directory:'#13+
                  dfc[i].Filename+#13+' Confirm delete?', mtConfirmation,
                  [mbYes, mbNo, mbCancel],0) of
      mrYes: if not RemoveDir(dfc[i].Filename)
               then MessageDlg('Error removing directory: '+
                               dfc[i].Filename+#13+
                               'Most likely cause is that the directory '+
                               'is not empty.',
                               mtWarning, [mbOK],0);
      mrNo: ;//do nothing
      mrCancel: exit;
    end;
  end;
end;

procedure TForm1.btnDelDirsClick(Sender: TObject);
begin
  DeleteEmptyDirs('C:\');
end;

end.

Regards
Pierre
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 12604370
You could also do

procedure TForm1.btnDelDirsClick(Sender: TObject);
begin
  DeleteEmptyDirs('C:\SomeDir\SomeOtherDir');
end;
0
 
LVL 17

Expert Comment

by:geobul
ID: 12604687
I don't understand the need for counting the files in a folder before deleting it while RmDir or RemoveDir won't let you to delete a non-empty folder. As Workshop_Alex already said there could be files you can't see at all in a NTFS file system. You just need to enumerate all folders and try to delete them in a reverse order. That's all.
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 12604902
I know RemoveDir won't let you delete a non empty folder. I'm using FindNext anyway so why not count the number of files/folders in them while we're at it. This way I only attempt deleting dirs which I know are empty instead of relying on RemoveDir not to delete them, a strategy I prefer. Also, it is code from code I wrote before where I needed the number of files in each directory (I just adapted it for this example).

Regarding the "files you can't see", would this not ensure you see all files?

FindFirst(path+'*.*', faAnyFile, sr)

0
 
LVL 17

Expert Comment

by:geobul
ID: 12605093
On a NTFS I'm able to make a folder and all its contents invisible for anybody (including the administrators which is strange :-).
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12606147
> there could be files you can't see at all in a NTFS file system.
On the NTFS system you would have to use a try-except block if you're trying to DELETE the folder. The folder might appear to be empty but if it isn't (or if the folder is protected in some other way) then an exception will be generated.

And as Geo and I said, you just have to enumerate all folders and remove them in reverse order. When a folder generates an exception, catch it.

If you look at my first code, you will see that I do check if there are files in the folders I'm checking. But instead of enumerating all files, I just stop counting after I found one file. If there's a file, I don't add it to the list for deletion. One flaw in the procedure EnumerateOuterEmptyFolder is that the line
    if ( FindFirst( Folders[ I ] + '*.*', faDirectory, SearchRec ) = 0 ) then begin
should be replaced with
    if ( FindFirst( Folders[ I ] + '*.*', faAnyfile, SearchRec ) = 0 ) then begin
Otherwise it might consider some folders as empty, while they are not... For some weird reason it still seemed to work, though.

Of course, mine is also non-recursive. :-) Apparantly I'm the only one who enumerates folders without using recursion...
0
 
LVL 1

Author Comment

by:fdehell
ID: 12608360
Geo thx a lot im busy with your examples now.
Workshop alex, they are for my own private use since i have a huge number of folders, files [over 1,5 miliion lol] just so you know:)
Everyone already thx heaps im working on your answers!

Fdehell

[but im not fast when it comes to delphi ]
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12609190
Well, if it is for personal use then there aren't much problems but there's still a risk that some program will stop working if you delete certain empty files. The better programs will just create those folders again but I have seen software crashing just because I deleted an empty folder... Thus the warning. For example, on my system there's a folder called C:\WinNT\PIF which is empty. But I assume it might have some critical role within my OS somehow... Delete it or not, that's a difficult question.
If you're an administrator on your system then it's unlikely that some folders will have an invisible contents. But there are some folders that are empty but still cannot be deleted. The folder "c:\System Volume Information" for example is one that you can't even get in to. It's empty if you enumerate it. But deletion will fail too. And, as another example, I have webserver/FTP server software on my system. Especially the FTP server has some empty folders where people can upload data. Removing those folders would block people from uploading data to my system. And of course the temporary files folder, which might or might not have any data. You can delete it's contents to clean it up, but if you remove the folder, some programs will fail to write to the temporary storage since it's location is gone.

A folder can be empty but it might serve a special purpose.

Thus, this is why I suggest to build a list of empty folders first. You could save this list to file just as backup information so you know what has been deleted, allowing you to restore them if need be. Or follow my suggestion and display the list of empty folders onscreen, so you can select which ones you want to be deleted and which ones you're not sure about. You wouldn't be the first who'se system crashes after deleting one thing too many... ;-)
0
 
LVL 17

Expert Comment

by:geobul
ID: 12612332
I must say that I agree with Workshop_Alex's statement above completely. Apparently I just wrote my first harmful code ;-)
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12613220
@Geo  ;-)

We've all written something harmful in our lives before, Geo. Don't worry about it. :-P The worst I did was creating something that would print a simple report to the printer. It seemed to work so I decided to do a final testrun. But for some weird reason it got in an eternal loop and I didn't realise this because I ran it and went away to pick a cup of coffee. When I got back, about a hundred pages were already printed, each partially printed. And hundreds more pages were stored in the queue...
Real waste of paper...

Btw, Geo... Your code for enumerating the folders have a flaw. If FindFirst fails for any reason, you're NOT supposed to call FindClose. (FindFirst will call FindClose internally upon failure.) Therefore, I consider my repeat-until option a nicer solution. Cleaner too. ;-) Pierre makes the same mistake in his code too. However, FindFirst will almost always succeed thus you would not easily spot this error.
0
 
LVL 17

Expert Comment

by:geobul
ID: 12613710
One morning one of our supervisors emerged behind a big printer with a box filled with used paper in his hands saying "We have been printing exes again" :-)

About that FindClose thing you're right. But if FindFirst fails (means returning nonzero) then my subsequent call of FindClose will also fail (i.e. windows.FindClose will return false - not checked in SysUtils.FindClose at all !)  without raising any exception or doing something harmful. It will just set LastError. Test code:

procedure TForm1.Button1Click(Sender: TObject);
var
  Search : TSearchRec;
  ok: integer;
begin
  ok := FindFirst('c:\test\*.*',faAnyFile,Search);
  while ok = 0 do  begin
    ok := FindNext(Search);
 end;
 FindClose(Search);
 FindClose(Search);
end;

I don't even have such a folder.

Regards, Geo
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12615345
I know it returns false. I know the error is just ignored. :-)
It's just my opinion that my solution of enumerating

if (FindFirst=0) then begin
  repeat
    bla;
  until (FindNext<>0)
  FindClose;
end;

is just as effective and doesn't even need that temporary variable. ;-)

And printing executables again? You too? :-)
Still, the worst thing I've seen was actually written by a collegue of mine. He created a tool that would delete a certain folder and all subfolders. This foldername was provided by ParamStr(1). He also had a folder named "C:\Windows Examples" and he showed how his application worked, so he opened a command prompt and typed:

  DeleteFolder C:\Windows Examples

Now, if he had used quotes around the folder name, he would not have had the trouble that he had now... :-)


















(If you don't get it, ParamStr(1) would be "C:\Windows" with above command. So he cleaned up his Windows folder instead.)
0
 
LVL 1

Author Comment

by:fdehell
ID: 12624603
Ok i have tried to and tried to get the examples offered to work in order of appearance, but did not get your example to work Geobull :( and  i just read why i guess, the same counted for your example workshopalex :( i really tried to and tried to but it did not succeed either, then i got to the e xample of Pierre and it worked at once.

I really really appreciated all the help i have gotten, and learned a lot from the advise, but since Pierre provided the code i could get to work instantly he will receive the points. It is really awesome that you experts all have assisted me, and i will show my gratitude in the only way i know, and that is read your feedback pls.

Thx very much all of you!

Fdehell

[ps. both geobull and workshopalex's advise about deleting empty folders were taken into account, again thx for the extra info]
0

Featured Post

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
In a question here at Experts Exchange (https://www.experts-exchange.com/questions/29062564/Adobe-acrobat-reader-DC.html), a member asked how to create a signature in Adobe Acrobat Reader DC (the free Reader product, not the paid, full Acrobat produ…
Is your OST file inaccessible, Need to transfer OST file from one computer to another? Want to convert OST file to PST? If the answer to any of the above question is yes, then look no further. With the help of Stellar OST to PST Converter, you can e…
Suggested Courses

830 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