How a computer TrashCan works
Just a quick and short explanation how a TrashCan (Trash, Recycle Bin, BitBucket, etc) works:
The objects (files/directories) you’d like to delete are simply moved to a specific directory on your computer.
Sometimes this is a standard or commonly used directory which may or may not be by the Operating System.
Besides just dumping the object in that directory, certain systems maintain additional data so a restore from the TrashCan can be done.
After all: just moving an object into a TrashCan doesn’t tell the system where it originally came from – in case the user asks to restore the object to its original location.
Security – A TrashCan should be private …
For security and privacy reasons, the TrashCan or Recycle Bin should be accessible only by the specific user.
You would never want other users to have access to your Trashcan and have them dig through your Trash, and make private matters public.
TrashCan not used in Shells, Terminals, etc …
The TrashCan is often only used in the graphical interface of your system.
Deleting files or directories in a Shell or Terminal window will usually not be using the Trashcan.
Ad Blocking Detected Please consider disabling your ad blocker for our website.
We rely on these ads to be able to run our website.
You can of course support us in other ways (see Support Us on the left).
Move objects to the macOS Trash
With recent macOS versions, the Trashcan can be found in the user’s home directory: ~/.Trash
So we could do a move manually, but we don’t have to … the macOS API has a function for that: I have used the recycleURLs:completionHandler:.
Obviously we will need de macOS units, but we also need to tell the Free Pascal compiler to switch to ObjectiveC mode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| {$modeswitch ObjectiveC1}
{$linkframework CoreFoundation}
interface
uses
... MacOSAll, CocoaAll ...
implementation
function MoveToTrash(FileOrDirName:AnsiString): boolean;
var
aNSArray : NSMutableArray;
aNSURL : NSURL;
begin
aNSArray := NSMutableArray(NSMutableArray.array_).init; // How can I create a new array?
aNSURL := NSURL.fileURLWithPath(NSSTR(pchar(FileOrDirName)));
aNSArray.addObject(aNSURL);
NSWorkspace.sharedWorkspace.recycleURLs_completionHandler(aNSArray,nil);
Result:=true; // recycleURLs doesn't return a result unless we make a function for it.
end; |
Please note: Result will always be TRUE …
Since the recycleURLs function does not return a result, unless we create a callback function for that (replacing the nil parameter), making this too complicated for this article. We will just assume it worked.
Checking if file or directory has been removed is not very reliable either, since the recycleURLs function works async, so the file or directory may be removed at a slightly later time. So a check wouldn’t work.
Move objects to the Windows Recycle Bin
Windows, since it is a Microsoft product, has changed the location of the Recycle Bin directory over the years (this CCleaner article is a good read).
For example:
C:\$Recycle.Bin
for Windows Vista, and newer,
C:\recycler
for Windows 2000, NT, and XP, or
C:\recycled
for Windows 95 and 98.
You’d expect C:\$Recycle.Bin
to work for Windows 10 as well, but … it doesn’t.
This honestly makes sense, since a Trashcan should be based on the user and not on a single disk drive.
The good thing I found though is that Windows does have an API call for that and in the Lazarus Forum I found a super helpful post by a user aSerge handling this just fine for me.
It uses the function SHFileOperation Windows ShellAPI and supports both Files and Directories, so we’d need to include the “ShellApi” unit in our uses clause:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| uses ... ShellApi ...
...
function MoveToTrash(const FileOrDirName: UnicodeString): Boolean;
var
R: TSHFILEOPSTRUCTW;
begin
FillChar(R, SizeOf(R), 0);
R.wFunc := FO_DELETE;
R.pFrom := PWideChar(FileOrDirName + #0);
R.fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION;
Result := SHFileOperationW(@R) = 0;
end; |
Ad Blocking Detected Please consider disabling your ad blocker for our website.
We rely on these ads to be able to run our website.
You can of course support us in other ways (see Support Us on the left).
Move objects to the Linux TrashCan
Linux doesn’t seem to have a standard location or API call for using the Trashcan, although there is an “official” TrashCan specification which most distros actually follow.
With the freedom we can find in Linux, this may or may not be implemented, in part or completely, based to these specifications. I suppose this is the strength and achilles heel of Linux.
Linux TrashCan directory location
The specifications suggest reading the value of the environment variable “$XDG_DATA_HOME“, but from experience I found this variable to be missing too often.
I did find however that most of the Linux and FreeBSD variants use ~/.local/share/Trash/
.
Now with the use of that directory, you will typically see two subdirectories:
– ~/.local/share/Trash/files/
where we store the actual file or directory
– ~/.local/share/Trash/info/
where a reference document is created, which hold restore information.
If this would be all, then I’d be happy, but unfortunately, I have seen quite a few variants as well:
– ~/.local/share/Trash/
,
– ~/Desktop/Trash/
,
– ~/trash
,
– ~/Trash
,
– ~/.trash
,
– ~/.Trash
If anyone is aware of other directories, then please let me know by posting a comment below, so I can add it to the list.
So to create a function for Linux as well, I followed this approach:
- If
~/.local/share/Trash/
exists, then use this directory and add the restore file as explained below.
- If the directory does not exist, check if one of the other directories exist and just move the file there and do not add the restore information file.
- If the alternative directories do not exists then we will create the
~/.local/share/Trash/
and work as defined in the standard (option 1).
I’m pretty sure someone will blow up over this approach, but please feel free to let me know if you have a better idea or approach. I’d like to learn as well
.
So … we will follow the standard if the standard directory ~/.local/share/Trash/
was found, or if the alternatives were not found. We will move the file or directory into ~/.local/share/Trash/files/
and a reference document into ~/.local/share/Trash/info/
.
If the standard directory was not found, but one of the alternatives was found, then we move file or directory to that alternative directory. We will however not create a reference document.
If the file already exists (since you deleted a file with the same name before), a digit will be added to distinguish them and not overwrite them.
This applies to the “files” directory and the “info” directory.
The number is basically following this pattern: example.doc, example.2.doc, example.3.doc, etc.
You may have noticed that the first one does not have a “.1” and this is intentional.
Linux TrashCan Reference file (.trashinfo)
In my function, I will create such a file when using the standard TrashCan definition ( ~/.local/share/Trash/
).
For the alternative (non-standard) directories we will not create such a file.
The additionally reference file will be created in “~/.local/share/Trash/info/” which includes the original filename and location, and deletion date.
The file has the same filename as the one in “~/.local/share/Trash/files/” with the additional “.trashinfo” extension, and does follow the same numbering as well.
Note: the original name is an escaped URL formatter string, so for example a space is replaced by a %20, like we see with URLs.
The content of this reference file will looks something like this:
1 2 3
| [Trash Info]
Path=/path/to/original/location/deletefile.extension
DeletionDate=2020-01-09T17:05:41 |
Not all Linux Distros observe the .trashinfo file!
Even though the .trashinfo files are supposed to be observed to help restore files in the TrashCan, not every Linux distro observes this.
For example, LinuxMint 19 (Cinamon) totally ignores it and may rely on a custom file found in “gvfs-metadata”.
My unit will ignore oddballs like that and the user will have to manually drag the file or directory out of the TrashCan.
Linux TrashCan Function in Pascal
Now since we do not have a clean method/API call, I’ve created this approach:
- First we will try to rename the file which (depending on the OS) will “move” the file to the destination directory (the TrashCan directory).
- If we see that step 1 failed, we will try to copy the file or directory to the TrashCan directory.
- If step 2 failed, then we will undo the copying and report back that the move to the TrashCan failed (FALSE).
- If step 2 succeeded, then we will delete the original files and we’re done.
So all these steps make the function a lot bigger than the ones we have seen for Windows or macOS as you can see below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
| uses
... DateUtils, LCLProc, LazFileUtils, FileUtil ...
...
function MoveToTrash(FileOrDirName:AnsiString): boolean;
var
TrashCanBase : string = '~/.local/share/Trash/';
TrashCanFiles : string = '~/.local/share/Trash/files/';
TrashCanInfo : string = '~/.local/share/Trash/info/';
TrashCanAlt : string;
InfoFile : TStringList;
isDirectory : boolean;
newFileOrDirName : string;
NamePart1, NamePart2 : string;
FilesInDirectory : TStringList;
counter : integer;
function EscapeURLString(s: string): string;
var
i: integer;
source: PAnsiChar;
begin
Result := '';
source := pansichar(s);
for i := 1 to length(source) do
if not (source[i - 1] in ['A'..'Z', 'a'..'z', '0'..'9', '-', '_', '~', '.', ':', '/']) then
Result := Result + '%' + IntToHex(Ord(source[i - 1]), 2)
else
Result := Result + source[i - 1];
end;
begin
if RightStr(FileOrDirName,1)=DirectorySeparator then // remove directory separator if this is the last char
FileOrDirName := LeftStr(FileOrDirName,Length(FileOrDirName)-1);
// Are we moving a directory into the trashcan?
isDirectory := DirectoryExists(FileOrDirName);
TrashCanFiles := ExpandFileName(TrashCanFiles);
TrashCanInfo := ExpandFileName(TrashCanInfo);
TrashCanBase := ExpandFileName(TrashCanBase);
if not DirectoryExists(TrashCanBase) then // We didn't find the standard dir, so now we are guessing alternatives
begin
TrashCanAlt := '';
if DirectoryExists(ExpandFileName('~/Desktop/Trash/')) then
TrashCanAlt:=ExpandFileName('~/Desktop/Trash/')
else if DirectoryExists(ExpandFileName('~/trash/')) then
TrashCanAlt:=ExpandFileName('~/trash/')
else if DirectoryExists(ExpandFileName('~/Trash/')) then
TrashCanAlt:=ExpandFileName('~/Trash/')
else if DirectoryExists(ExpandFileName('~/.trash/')) then
TrashCanAlt:=ExpandFileName('~/.trash/')
else if DirectoryExists(ExpandFileName('~/.Trash/')) then
TrashCanAlt:=ExpandFileName('~/.Trash/');
if TrashCanAlt='' then // we didn't find an alternative: create standard dirs
begin
ForceDirectories(TrashCanFiles);
ForceDirectories(TrashCanInfo);
end
else // we did find an alternative, make sure we use it for files (and not for trashinfo)
begin
TrashCanFiles := TrashCanAlt;
TrashCanInfo := '';
end;
end;
newFileOrDirName := ExtractFileName(FileOrDirName);
NamePart1 := LazFileUtils.ExtractFileNameWithoutExt(newFileOrDirName);
NamePart2 := ExtractFileExt(newFileOrDirName);
counter := 0;
// if file already in Trashcan, we add a number (eg. example.doc, example.2.doc, expample.3.doc etc.)
while (isDirectory and DirectoryExists(TrashCanFiles+newFileOrDirName)) or
(not(isDirectory) and FileExists(TrashCanFiles+newFileOrDirName)) do
begin
inc(Counter);
newFileOrDirName := NamePart1 + BoolToStr(Counter>1,'.'+IntToStr(Counter),'') + NamePart2;
end;
// Create a trashinfo file, if the standard dir did exist!
if TrashCanInfo<>'' then
begin
InfoFile := TStringList.Create;
InfoFile.Text := '[Trash Info]'+LineEnding+
'Path='+EscapeURLString(FileOrDirName)+LineEnding+
'DeletionDate='+FormatDateTime('YYYY-MM-DD',Now)+'T'+FormatDateTime('hh:nn:ss',Now)+LineEnding; //'2020-01-09T17:05:41'+
InfoFile.SaveToFile(TrashCanInfo+newFileOrDirName+'.trashinfo');
InfoFile.Free;
end;
// make new filename now full path
newFileOrDirName := TrashCanFiles+newFileOrDirName;
// Move File or Directory - try rename first, if that fails try copying files, and if copying worked delete originals
Result := RenameFile(FileOrDirName,newFileOrDirName); // try moving file or dir
if not Result then // if rename failed then try copy and delete (aka move)
begin
if isDirectory then
begin
FilesInDirectory := FindAllFiles(FileOrDirName, '*', true);
for Counter:=0 to FilesInDirectory.Count-1 do // Copy the file structure to the trashcan
begin
if ForceDirectories( ExtractFilePath( StringReplace(FilesInDirectory.Strings[Counter],FileOrDirName,newFileOrDirName,[] ) ) ) then
begin
if not CopyFile( FilesInDirectory.Strings[Counter], StringReplace(FilesInDirectory.Strings[Counter],FileOrDirName,newFileOrDirName,[] ) ) then
begin // failed copying file - cleanup and bail
Result := false;
if DeleteDirectory(newFileOrDirName,true) then // delete what we have copied so far - things are going side ways
RemoveDir(newFileOrDirName);
if FileExists(TrashCanInfo+newFileOrDirName+'.trashinfo') then
DeleteFile(TrashCanInfo+newFileOrDirName+'.trashinfo');
Exit;
end;
end
else // failed creating dir - cleanup and bail
begin
Result := false;
if DeleteDirectory(newFileOrDirName,true) then // delete what we have copied so far - things are going sideways
RemoveDir(newFileOrDirName);
if FileExists(TrashCanInfo+newFileOrDirName+'.trashinfo') then
DeleteFile(TrashCanInfo+newFileOrDirName+'.trashinfo');
Exit;
end;
end;
// all went well, remove old dir
if DeleteDirectory(FileOrDirName,true) then
Result := RemoveDir(FileOrDirName);
FilesInDirectory.Free;
end
else
begin
if CopyFile(FileOrDirName,newFileOrDirName,true,false) then // If rename fails: copy file
Result := DeleteFile(FileOrDirName) // If copy succeeded: remove original
else
Result := false; // Copy/Delete failed d
end;
end;
end; |
I’d honestly would expect this to already exist somewhere, somehow, … but I for sure could not find it.
So here you can download my unit to covers Windows, Linux and macOS, for moving files or directories to the TrashCan, Recycle Bin, Bit Bucket, or whatever it is called.
I’m sure there is plenty of room for improvements; please feel free to post them in the comments below.
Here you can download the entire unit:
Download - Lazarus-Pascal-TrashCanUnit.zip
Comments
There are 6 comments. You can read them below.
You can post your own comments by using the form below, or reply to existing comments by using the "Reply" button.
Thanks for with this writeup!
It saved me a lot of work.
Johnny7
Thanks Johnny7!
Glad to hear it has been of use for you, and I very much appreciate you taking the time to post a Thank You
Hans
Thank you for this article it really helped me
peniel
Thanks Peniel for taking the time to post a Thank You – it is much appreciated
Hans
In Linux you can also use cli command ‘gio’
Alex
Awesome find! Thanks for sharing!
Since I’m not a Linux expert; is “gio” available on most common distro’s?
Hans