How to Delete a Folder

| | Comments (29)

This is a question I get over and over again -- how do you properly delete a folder in REALbasic? The problem stems from the fact that you're not allowed to delete a folder if it's not empty. When you try to delete one, the OS doesn't let you, and you get an error back from the FolderItem. So how do you delete the folder?

The key concept is that you need to remove all the files from the folder first. The problem is, everyone tries to do it the wrong way first because it's the most obvious way. They say:
[rbcode]
for i = 0 to folder.Count - 1
folder.Item( i ).Delete
next
[/rbcode]
This has four problems with it. 1) FolderItems are 1-based lists, so the looping construct doesn't have the proper bounds. 2) Item resolves immediately, so once you've removed a file, the count changes and Item( i ) will start skipping files. 3) What if there are other folders to delete? 4) You're resolving aliases and deleting things that don't reside in the folder.

So to counteract #1, #2 and #4, the code starts to look like this:
[rbcode]
dim i, count as Integer

count = folder.Count
for i = count downto 1
if folder.TrueItem( i ) <> nil then
folder.TrueItem( i ).Delete
end if
next i
[/rbcode]
This is a little closer to being correct, but the problem is -- you're still not accounting for running into other folders while you're looping over the items. And it's when you have folders inside of folders that things get more tricky. Then you have to see that you have a folder, dive into that folder and delete all its children. But don't worry, that's not too hard -- leverage the code you've already written!
[rbcode]
Sub DeleteFilesFromFolder( folder as FolderItem )
dim i, count as Integer

count = folder.Count
for i = count downto 1
if folder.TrueItem( i ) <> nil then
// Check to see if the item is a directory
if folder.TrueItem( i ).Directory then
// It is, so remove all it's children
DeleteFilesFromFolder( folder.TrueItem( i ) )
end if
// Delete the item (whether it's a file or a folder)
folder.TrueItem( i ).Delete
end if
next i
End Sub
[/rbcode]
Now you've deleted all the files from the folder (leaving the folder itself), and so you're ready to delete the folder. The code isn't too hard, it's the concept that trips most people up.

29 Comments

So you'll post this which can lead to mischevious doings... but you won't post your RAW socket stuff?

:P

hehe..

Because:

1) This only affects your local machine, nothing remote
2) This only affects machines where you have the privs to remove files

Apples and Oranges Jake. :-)

well.. not entirely...

1) You could send it remotely to an individual. Sometimes against their will...

2) Yes but on the same token, you can prevent those bad inbound packets as easier than you can prevent someone from deleting your files (average user of course). Most people have ZoneAlarm or other "router/firewall" software that is automatically turned on to deny inbound packets like that...

1) In that case, the machine already needs to be insecure, in which case, nothing matters. Hell, giving the user access to the Shell is a security risk at that point. This code is no more malicious than Shell.Execute( "format c:" ). Local access means you can screw with the local machine only -- sending an executable to another user just means your local access changed to another machine. It does not mean you've got remote access (remote access would be if I was able to write code that I launch from my machine but executes on your machine. This code would be *launched* on the remote machine, so it doesn't count).

2) If by "most users" you mean "only the users with SP2 and up, of which there aren't that many out of the whole", then sure.

But that still doesn't negate the fact that you need the proper privs for the code to work locally.

The entire point to raw sockets (from a hacking perspective) is that you can pwn the target box. Packet filtering doesn't much matter -- you crash the packet filter if you work hard enough. Hell, some packet filters (:: coughs :: OS X :: coughs :: ) don't even catch anything aside from TCP traffic.

Raw sockets are MUCH more dangerous to release into the wild than a snippet of code that does the same thing a user can do by right clicking and say "delete" locally.

:: plugs ears :: lalalalalala :)

j/k

Lol, I see you've taken your MCADs to heart. :-P

Yup! :) Speaking of exams... What was your grade in spelling during school?

:: puts on the boxing gloves ::
It's on like Donkey Kong!

we haven't had it out like this since the old DingoStick days...

:)

What spelling mistake? O:-)

Yeah, it has been a while since we've done this. :-P

HAHAH Mr. "I'm Ub3R bl06 @dmin that can change my comments"

pwn3d

Sub DeleteFilesFromFolder( folder as FolderItem )
dim i, count as Integer
count = folder.Count
for i = 1 to count // Check to see if the item is a directory
if folder.Item( 1 ).Directory then
DeleteFilesFromFolder( folder.Item(1) )
end if
folder.Item( 1 ).Delete
next i
End Sub

Damon

That certainly works.

Personally, I find it to be slightly more confusing though. My mind can handle thinking "I need to pull plates from the top of the stack so I don't miss any" much easier than it can handle "I'm pulling plates out from the bottom, so the entire stack will fall down neatly in place". But obviously the code is correct either way. Gotta love programming! :-)

you are of course correct, but I find that this runs about 15% faster, dont know why but if you try to delete a dir with lots of dir's within it , it just seems to do it faster.

D

I would believe that. When you try to get item N, we have to count our way thru N-1 items to get there. So if you're always removing from the back, you're always doing the most work.

The hope is that a future version of RB which shall remain nameless ;-) will have FolderItem.Items() as FolderItem() that kills two birds with one stone. It's a snapshot of all the items currently in the directory so you can delete them in any order you'd like, and it's faster than trying to loop over all the items with Item.

thats a great idea, if i know that a dir has no internal dirs I load the absolutepaths into an array , then process and then delete, I remember the first time I did for i=1 to f.count, it took me about an hour to figure out why it deleted half the items. :)
But then again I just figured out DIM arr(-1) then .append will give a 0 based array and DIM arr(0) then .append will give a 1 based array, so I guess there is more to learn....


TrueItem... not Item. Otherwise, you'll resolve aliases and delete things you aren't meaning to.

Great googly moogly! Thanks for pointing that out Jon. The code is now corrected to reflect this.

Hey Aaron,
How to join two tutes into one project, never used extends but now I think they are great (my first extend).

two extends on folderitem
deletedirectorycontents and trash in a module (my first module as well)

http://www.dlpedi.com/realbasic

and I figured out how to use a variable with a default value (first as well).
Instructions in Notes (first time used notes)

I feel like a virgin (4 times over)
TA
Damon

Congrats! The source looks nice (though I would use a more descriptive parameter than "s" so that I'm not forced to read the notes unless I want more in-depth descriptions), you may want to submit this to www.rbgarage.com so others can use it as well.

Hi,
Have changed s to DeleteFolderToo

so it reads

if DeleteFolderToo then f.delete

Also listed with RBgarage (another first)

Do modules run faster than normal code. I know its a stupid question, but the code does seem to run very fast...

Ever-so-slightly faster because you don't have to hit the vtable to find which subclass to call the method on. But nothing enough to really notice unless you're doing a *lot* of iterations.

You should also check folder.TrueItem(i) Nil. The user executing the code may lack the permissions to view folder.TrueIrem(i), for instance.

That's another very good point. I suppose it is possible for Folder A to have permissions to delete, but Folder B (inside of A) may not.

Is there any reason why you can't just have a command that does this in RB instead of forcing everyone to write the same code over and over again? I mean neither Explorer nor Konqueror nor Finder balk at you when you trash a folder. They just delete stuff in them themselves.

This comes up over and over again, and it all boils down to the danger factor.

When deleting things from the file manager (like explorer or finder), they typically aren't deleted -- they're moved into another area where you have to go and manually say "erase this file." However, with this code, you're erasing the file forever.

I don't know about you, but I've had bugs in my code before. ;-) Heck, even with this example -- before it was using Item instead of TrueItem. That means I would have been resolving aliases and deleting things I didn't mean to delete. Now imagine if I was able to delete entire folders by accident -- it's pretty scary to make that easy.

I've thought about implementing this as an internal API, and the only way I would feel comfortable doing it would be to make it slightly harder. Like this:

FolderItem.Delete( recursive as Boolean = false )

This way the programmer has to explicitly say "no really, I want to delete everything here" before it starts deleting folders and subtrees.

If this *is* made a framework function (which I don't think it should) then it should return a boolean as to whether it got deleted or not.

-- SirG3

I'd much prefer there being a built in function for this! However, there's no difference between having this built in, or writing my own version and having a bug make it delete something I didn't intend to delete. Whether I delete it, or RB deletes, it's still gone :^)

I see what you're saying though, and having a recursive flag isn't all that crazy of an idea. rf needs it.

rm rather.

One more thing to speed this operation up is to always delete the first item instead of deleting the 'ith' item. This is faster because the call to TrueItem needs to loop over the preceeding i - 1 items in order to reach i. So if you are always deleting the first item, then that loop is removed.

Benchmark data (Windows XP, 3408 items):
Looping backwards: 1 minute, 15 seconds
Deleting first: 16 seconds

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.