FsRemoveDir - delete DIR without delete files and dir inside

Discuss and announce Total Commander plugins, addons and other useful tools here, both their usage and their development.

Moderators: Hacker, petermad, Stefan2, white

Post Reply
User avatar
jaco777
Junior Member
Junior Member
Posts: 19
Joined: 2015-03-23, 21:56 UTC
Location: Poland
Contact:

FsRemoveDir - delete DIR without delete files and dir inside

Post by *jaco777 »

Hello.

My first post. I'm from Poland. Sometimes I write programs for themselves. Sorry for my English.

Now I write plug WFX to support a specific website file hosting. I write plug in Delphi 7. They begin to experience problems with which I do not know what to do.

My question.
Is it possible to delete a directory (FsRemoveDir) without deleting files and directories inside?
Website file hosting needs only a one single command to delete a folder and all of its contents. But TotalCommander enter the directory and deletes every file and directory separately, which is unnecessary in this case.

I tried to add to FsRemoveDir variable inform the other functions, but the function FsRemoveDir is executed after deleting files (FsDeleteFile).

Is there any other solution to this problem?
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 50421
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Yes there is: You need to implement the function FsStatusInfo.

When you get the FS_STATUS_START, FS_STATUS_OP_DELETE notification, set a flag DoNotReadSubdirs:=TRUE;

Then when TC calls FsFindFirst, call SetLastError(ERROR_NO_MORE_FILES) and return INVALID_HANDLE_VALUE. Total Commander assumes that the directory is empty, and deletes it directly.

Don't forget to set DoNotReadSubdirs:=FALSE; when receiving FsStatusInfo with parameters FS_STATUS_END, FS_STATUS_OP_DELETE !

Btw, I use this trick when the user wants to delete a server in some plugins, so he doesn't delete all the files on that server too...
Author of Total Commander
https://www.ghisler.com
User avatar
Dalai
Power Member
Power Member
Posts: 9961
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Post by *Dalai »

Ah, that's exactly what I needed! Indeed it works. Thanks!

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
User avatar
jaco777
Junior Member
Junior Member
Posts: 19
Joined: 2015-03-23, 21:56 UTC
Location: Poland
Contact:

Post by *jaco777 »

Thank you. I confirm that works.

In FsFindFirst, only instead Result: = INVALID_HANDLE_VALUE, I just set Result: = 0. Otherwise do not want to perform the functions FsRemoveDir.

For interested :-):

Code: Select all

procedure FsStatusInfo(RemoteDir : PChar; InfoStartEnd, InfoOperation : Integer); stdcall;
begin

  if (InfoStartEnd = FS_STATUS_END) and (InfoOperation = FS_STATUS_OP_GET_MULTI) then AbortCopy := False;

  // from www.ghisler.ch/board/viewtopic.php?t=41977
  if (InfoStartEnd = FS_STATUS_START) and (InfoOperation = FS_STATUS_OP_DELETE) then DoNotReadSubdirs := True;
  if (InfoStartEnd = FS_STATUS_END) and (InfoOperation = FS_STATUS_OP_DELETE) then DoNotReadSubdirs := False;

end;
For sure I'll have more questions, but this is a new post. Problem solved, post to close.
User avatar
Dalai
Power Member
Power Member
Posts: 9961
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Post by *Dalai »

jaco777 wrote:Otherwise do not want to perform the functions FsRemoveDir.
Of course, FsRemoveDir will be called even if FsFindFirst returns INVALID_HANDLE_VALUE. I'm currently using it exactly like this. Maybe there's some other code interfering with it. And you have to make sure that FsFindFirst sets ERROR_NO_MORE_FILES, as Ghisler pointed out:

Code: Select all

function FsFindFirst [...]
[..]
    if DontReadSubdirs then begin
        SetLastError(ERROR_NO_MORE_FILES);
        Exit;
    end;
Otherwise TC will "think" the subdir is inaccessible - and it can't be deleted because of that.
For interested :-):
Just a note: You can do this with less code:

Code: Select all

[...]
    if (InfoOperation = FS_STATUS_OP_DELETE) then begin
        DontReadSubdirs:= InfoStartEnd = FS_STATUS_START;
    end;
[...]
Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
User avatar
jaco777
Junior Member
Junior Member
Posts: 19
Joined: 2015-03-23, 21:56 UTC
Location: Poland
Contact:

Post by *jaco777 »

To Dali: You're right, just so I wrote a function FsFindFirst. I am a novice programmer. I wrote the code is easier for me to understand. Thank you for the tips.
In my case, my code works:

Code: Select all

function FsFindFirst(Path :PChar; var FindData : tWIN32FINDDATA) : THandle; stdcall;
var
  ofn, Path2 : string;
begin
  If DoNotReadSubdirs = False Then
    begin
    // code for other operations
    end
  else
    begin
    // code for delete DIR
    SetLastError(ERROR_NO_MORE_FILES);
    Result := 0;
    end;
end;

Code: Select all

procedure FsStatusInfo(RemoteDir : PChar; InfoStartEnd, InfoOperation : Integer); stdcall;
begin
  if (InfoStartEnd = FS_STATUS_END) and (InfoOperation = FS_STATUS_OP_GET_MULTI) then AbortCopy := False;
  // from www.ghisler.ch/board/viewtopic.php?t=41977
  if (InfoStartEnd = FS_STATUS_START) and (InfoOperation = FS_STATUS_OP_DELETE) then DoNotReadSubdirs := True;
  if (InfoStartEnd = FS_STATUS_END) and (InfoOperation = FS_STATUS_OP_DELETE) then DoNotReadSubdirs := False;
end;
User avatar
Dalai
Power Member
Power Member
Posts: 9961
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Post by *Dalai »

jaco777 wrote:I wrote the code is easier for me to understand.
Yes, of course. It's what everbody does. OK, some people can write code they don't understand later ;).

To explain the little code I posted: This

Code: Select all

DontReadSubdirs:= InfoStartEnd = FS_STATUS_START; 
compares InfoStartEnd with FS_STATUS_START and that is True in case they're equal or False in case they're not. The result of this comparison is then assigned to the DontReadSubDirs variable. You can use parenthesis to make the code a little bit more readable:

Code: Select all

DontReadSubdirs:= (InfoStartEnd = FS_STATUS_START);
In my case, my code works:

Code: Select all

function FsFindFirst(Path :PChar; var FindData : tWIN32FINDDATA) : THandle; stdcall;
var
  ofn, Path2 : string;
begin
  If DoNotReadSubdirs = False Then
    begin
    // code for other operations
    end
  else
    begin
    // code for delete DIR
    SetLastError(ERROR_NO_MORE_FILES);
    Result := 0;
    end;
end;
Do NOT compare Boolean variables with True or False! Don't start to make that a habit or your code will sometimes fail. You may ask why. Well, True is equal to 1 if it's a Boolean type but -1 if it's a Bool, LongBool or WordBool type! So, code like this

Code: Select all

var foo: BOOL;
[...]
if foo = true then
will fail! It may not be wrong to compare directly to False (it's always 0) but it's useless code. Rather code it like this:

Code: Select all

if someboolvalue then ...
(in case you want to compare with True) or

Code: Select all

if NOT someotherboolvalue then ...
(in case you want to compare with False).

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
User avatar
jaco777
Junior Member
Junior Member
Posts: 19
Joined: 2015-03-23, 21:56 UTC
Location: Poland
Contact:

Post by *jaco777 »

Variables in Delphi is a challenge for me:-) - too many. I more programing in AutoIt, and there is easier.

That is, although I declared global variables:

Code: Select all

AbortCopy, DoNotReadSubdirs : Boolean;
and in procedure FsStatusInfo is

Code: Select all

...
DoNotReadSubdirs := False;
...
it is better to write as Your example.

Code: Select all

function FsFindFirst(Path :PChar; var FindData : tWIN32FINDDATA) : THandle; stdcall;
var
  ofn, Path2 : string;
begin
  If NOT DoNotReadSubdirs Then
    begin
    // code for other operations
    end
  else
    begin
    // code for delete DIR
    SetLastError(ERROR_NO_MORE_FILES);
    Result := 0;
    end;
end;
I changed my code and of course working properly. Once again, thank you for Your tips.

I have another question. In the function FsFindFirst, does the statement Result: = 0 is correct (compatible), what better way?
User avatar
Dalai
Power Member
Power Member
Posts: 9961
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Post by *Dalai »

jaco777 wrote:Variables in Delphi is a challenge for me:-) - too many. I more programing in AutoIt, and there is easier.
Typeless variables have their pros, but they also have some cons... I code in Delphi, AutoIt, Bash, PowerShell and some others and I admit that I prefer typesafe languages, although I don't complain about the typeless variables in AutoIt ;).
I have another question. In the function FsFindFirst, does the statement Result: = 0 is correct (compatible), what better way?
Well, it's in the TC plugin SDK. You should return INVALID_HANDLE_VALUE if an error occurs, and a value of your choice if not. So, you should not return 0 in this case because there's an error (ERROR_NO_MORE_FILES), although you "created" that error yourself. I don't know if it makes a difference for TC (I guess not), but it's better to conform to the documentation/API/SDK/whatever.

I use this approach:

Code: Select all

function FsFindFirst(Path: PChar; var FindData: TWin32FindData): THandle; stdcall;
begin
    Result:= INVALID_HANDLE_VALUE;

    if DontReadSubdirs then begin
        SetLastError(ERROR_NO_MORE_FILES);
        Exit;
    end;

    // Some more code here
    try
        // Some more code here
    except
       on E: Exception do begin
            // Handle exception here
            Exit;
       end;
    end;
    Result:= 1;
end;
So, Result is only set to 1 if all is well. In all other cases, the function will be exited and return INVALID_HANDLE_VALUE.

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
User avatar
jaco777
Junior Member
Junior Member
Posts: 19
Joined: 2015-03-23, 21:56 UTC
Location: Poland
Contact:

Post by *jaco777 »

So, Result is only set to 1 if all is well. In all other cases, the function will be exited and return INVALID_HANDLE_VALUE.
Another good tips :D. Thanks, thanks, thanks.

That's right, the function will always return a value.

I think that the topic has already been fully explained.



Added on 06.04.2015, after many tests:

With this code:

Code: Select all

function FsFindFirst(Path :PChar; var FindData : tWIN32FINDDATA) : THandle; stdcall;
var
  ofn, Path2 : string;
begin
  If DoNotReadSubdirs = False Then
    begin
    // code for other operations
    end
  else
    begin
    // code for delete DIR
    SetLastError(ERROR_NO_MORE_FILES);
    Result := 0;
    end;
end;
good deletes directories (with files inside), but unnecessary once calls the function FsDeleteFile.

When instead Result: = 0 is Result: = INVALID_HANDLE_VALUE, as suggested Ghisler, good deletes directories (with files inside), and does not call function FsDeleteFile.
Post Reply