Pagina1 van 1

Lazarus Pascal – Items naar Trash voor Windows, Linux and macOS

Lazarus Pascal – Items naar Trash voor Windows, Linux and macOS
   0

In dit korte artikel laat ik je zien hoe ik een functie voor Lazarus Pascal heb gemaakt, waarmee je cross platform (Windows, Linux, macOS) bestanden of directories naar de TrashCan of Recycle Bin kunt verplaatsen.

Omdat het me toch wat moeite kostte om alles voor elk platform te vinden, leek het me een goed idee om de gevonden informatie en de unit die ik gemaakt heb, hier te plaatsen. Hopelijk heeft iemand er wat aan, of heeft iemand suggestie ter verbetering.
Voor het gemak heb ik de unit ook downloadbaar gemaakt waarin de functie voor macOS, Linux en macOS is te vinden.




Hoe werkt de computer TrashCan?

Even een korte uitleg hoed een TrashCan op de computer werkt (andere namen: Trash, Recycle Bin, BitBucket, etc):

De betreffende objecten (bestanden/directories) die je wilt verwijderen worden simpel weg verplaatst naar een specifieke directory op jouw computer.

Dit kan een standaard of veel gebruikte directory zijn, welke wel of niet door het besturingssysteem beheert wordt.

Afhankelijk van het besturingssysteem, wordt er ook nog bijgehouden waar het betreffende item oorspronkelijk vandaan komt, zodat de gebruiker het eenvoudiger terug kan zetten.

 

Security – Een TrashCan moet prive zijn … 

Voor veiligheid en privacy moet een TrashCan uiteraard beschermd worden en dus alleen toegankelijk zijn voor de betreffende gebruiker.
Je zou niet willen dat iemand door jouw afval emmer snuffelt en ineens tegen prive zaken aanloopt die je eigenlijk niet publiek wilde hebben.

TrashCan wordt niet gebruik in Shells, Terminals, etc … 

De TrashCan wordt vaak alleen gebruikt in de grafische gebruikers interface.
Het verwijderen van bestanden in een Shell of Terminal zal zelden gebruik maken van de TrashCan.

 

Items in macOS naar de Trash brengen

Met recente macOS versies, kun je de TrashCan in de home directory van de gebruiker terug vinden, en wel hier:  ~/.Trash

We zouden bestanden en directories natuurlijk handmatig kunnen verplaatsen maar dat hoeft niet. macOS heeft hiervoor een mooie functie: recycleURLs:completionHandler:.

Om hier mee te kunnen werken moeten we wel de benodigde units toevoegen en de Free Pascal compiler laten weten dat we naar de ObjectiveC mode moeten omschakelen.


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;

Let op: Deze functie levert altijd een TRUE als retourwaarde … 

Omdat de recycleURLs functie geen TRUE of FALSE terug geeft, en in plaats daarvan een speciale callback functie zou willen gebruiken, hebben we dat voor eenvoudigheid redenen hier gewoon overgeslagen.
Het achteraf controleren of bestand of directory inderdaad weg is, is geen goede optie omdat de recycleURLs functie asynchroon werkt, en het bestand dus mogelijk pas later weg haalt.

 

Items naar de Windows Recycle Bin

Windows, omdat het een Microsoft product is, heeft de locatie van de Recycle Bin directory de jaren al vaker veranderd (dit CCleaner artikel geeft dat duidelijk weer).

Als voorbeeld:
 C:\$Recycle.Bin  voor Windows Vista, en niewer,
 C:\recycler  voor Windows 2000, NT, en XP, of
 C:\recycled  voor Windows 95 en 98.

Ik heb gemerkt dat  C:\$Recycle.Bin onder Windows 10 ook niet handig werkt, dus daar hebben we ook niks aan.

Gelukkig bestaat er onder Windows een API die dat voor ons kan regelen, en een mooie functie die ik in het Lazarus Forum vond, van de gebruiker aSerge vult dit helemaal voor ons in.
Het gebruikt de functie SHFileOperation van de Windows ShellAPI en ondersteund bestanden en directories. We moeten dus we de “ShellApi” unit aan onze uses clausule toevoegen.


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;   

 

Items naar de Linux TrashCan

Linux heeft helaas geen standaard locatie of API functie die ons snel even helpt. Er bestaat echter wel een officiële TrashCan specificatie welke de meeste moderne Linux distros wel volgen.
Linux biedt echter veel vrijheid, dus niet iedere Linux versie doet het op dezelfde manier … helaas is dat de achillespees van Linux.

Linux TrashCan directory locatie

De eerder genoemde specificaties stellen dat men de environment variable “$XDG_DATA_HOME” moet uitlezen, maar ik al gemerkt dat die bij een aantal Linux versie gewoon niet is gedefinieerd.

Bij de verschillende Linux en FreeBSD varianten gebruikt men echter wel vaak  ~/.local/share/Trash/ .
Als deze directory gebruikt wordt, dan zie je daar vaak twee sub-directories in:
 ~/.local/share/Trash/files/ waar het verwijderde bestand of directory geplaatst wordt.
 ~/.local/share/Trash/info/ waar een referentie document wordt gezet zodat het systeem kan zien waar het bestand oorspronkelijk vandaan komt.

Maar helaas, zoals Linux eigen, niet iedereen houdt zich daaraan, en andere locaties kunnen ook zijn:
 ~/.local/share/Trash/ ,
 ~/Desktop/Trash/ ,
 ~/trash ,
 ~/Trash ,
 ~/.trash ,
 ~/.Trash

Als iemand er nog meer weer: plaats hieronder een opmerking zodat ik ze aan de lijst kan toevoegen.

Voor de Linux versie van de MoveToTrash functie, heb ik daarom de volgende aanpak gekozen:

  1. Als  ~/.local/share/Trash/  bestaat, gebruik deze dan voor het verwijderde bestand/directory en het referentie document.
  2. Mocht deze directory niet bestaan, kijk dan of de alternatieve directories bestaat, en als dat het geval is verplaatsen we alleen bestand/directory en maken we geen referentie document.
  3. Mochten beide stappen falen, dan maken we  ~/.local/share/Trash/ aan en gaan we de standaard volgen (dus optie 1).

Ik kan me voorstellen dat iemand het helemaal niet eens is met deze aanpak: ook hier weer – post een opmerking hieronder. Ik leer ook graag nieuwe dingen .

Dus als de standaard directory  ~/.local/share/Trash/ bestaat, of als de alternatieve directories niet bestaan, dan verplaatsen we bestanden  ~/.local/share/Trash/files/ en maken we een referentie document in  ~/.local/share/Trash/info/.
Als de standaard directory niet bestaat, maar we hebben we een alternatieve directory gevonden, dan verplaatsen we betreffende bestand of directory daarheen, maar maken we geen referentie bestand.

Linux TrashCan Filename nummering

Omdat het kan zijn dat een bestand met dezelfde naam al in de Trash staat, moeten we soms een bestand een nummer geven, zodat het niet een eerder verwijderd bestand overschrijft.
Dit geldt voor de bestanden/directories in de “files” directory en de “info” directory.

De nummering volgt dit patroon: voorbeeld.doc, voorbeeld.2.doc, voorbeeld.3.doc, etc.
Je ziet dat “.1” wordt weggelaten.

Linux TrashCan Referentie Document (.trashinfo)

In mijn functie, indien we de standaard volgen ( ~/.local/share/Trash/), maken we een referentie document aan.
Voor de alternatieve (niet-standaard) directories slaan we dat over.

Het referentie bestand wordt aangemaakt in “~/.local/share/Trash/info/” waarin originele bestandsnaam en verwijder datum staan opgeslagen.
De naam van het referentie bestand is hetzelfde als die van de verwijderde directory of bestand en wordt opgeslagen in “~/.local/share/Trash/files/” met “.trashinfo” achter de naam (inclusief nummer indien van toepassing).

Merk op: de naam van het originele bestand wordt met escape characters geschreven, dus b.v. een psatei wordt een “%20”, zodat we dat ook bij URLS zien.

De inhoud van het referentie bestand ziet er ongeveer zo uit:


1
2
3
[Trash Info]
Path=/path/to/original/location/deletefile.extension
DeletionDate=2020-01-09T17:05:41

Niet alle Linux versies letten op het .trashinfo bestand! 

Hoewel .trashinfo bestanden zouden moeten helpen met het herstellen van een verwijderd bestand, doet niet iedere Linux versie hier iets mee.
Als voorbeeld: LinuxMint 19 (Cinamon) negeert dit compleet en lijkt een eigen formaat te gebruiken in “gvfs-metadata”.

Helaas kan ik niet alle custom oplossing in mijn unit plaatsen, en het bestandsformaat is soms exotisch, dus de gebruikers van dergelijk Linux versie zullen bestanden zelf uit de Trash moeten slepen en geen handige “restore” functie hebben.

Linux TrashCan Functie in Pascal

Omdat we dus geen handige API call hebben, ga ik als volgt te werk:

  1. Probeer of we bestand of directory met een “rename” kunnen verplaatsen (werk niet voor elk besturingssysteem).
  2. Mocht stap 1 falen, dan proberen we bestand of directory te kopiëren we het eerst naar de TrashCan.
  3. Mocht stap 2 ook falen, dan verwijderen we de kopie uit de TrashCan en geven we aan dat het verplaatsen naar de TrashCan faalde (FALSE).
  4. Mocht stap 2 echter goed zijn gegaan, dan verwijderen we daarna het origineel.

Uiteraard maakt dit alles een beetje complexer dan de handige Windows of macOS functies:


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;

 

 

Cross Platform Unit – Mijn MoveToTrash unit

Ik kon me niet voorstellen dat zo’n functie nog niet bestond, maar ik kon er geen vinden.
Ik heb daarom mijn unit hier beschikbaar gemaakt, hopelijk is het handig voor anderen om onder Windows, Linux en macOS, bestanden of directories naar de TrashCan te verplaatsen (ookwel: Recycle Bin, Bit Bucket, etc).

Ik kan me goed voorstellen dat er ruimte voor verbetering is en suggesties zijn zeer welkom.

Hier kun je de hele unit downloaden:

Download - Lazarus-Pascal-TrashCanUnit.zip 

Bestandsnaam:  Lazarus-Pascal-TrashCanUnit.zip
Platform:  Undefined
Versie:  1.0
Omvang:  3.5 kB
Datum:  2020-01-11
 Download Nu  Stuur me Koffie    

Ondersteun ons ...


Jouw ondersteuning wordt zeer gewaardeerd, en hoeft zelfs niets te kosten. Bijvoorbeeld door links naar ons te delen op social media, of andere websites.

Andere vormen kunnen ook gratis zijn (b.v. shoppen op Amazon).
Alle opbrengsten worden gebruikt voor web-hosting kosten, project hardware en software, koffie, etc.

Hartelijk dank voor wie al heeft bijgedragen!
Het is altijd geweldig om te zien hoe men mijn artikeltjes en applicaties weet te waarderen.

Merk op dat het klikken op affiliate links een kleine commissie voor ons kunnen genereren - dit wordt zeer gewaardeerd.

Reacties


Er zijn nog geen reacties geplaatst.
Je kunt jouw eigen opmerkingen plaatsen m.b.v. dit formulier, of een reactie op een bestaande opmerking plaatsen door op de "Beantwoorden" knop te klikken.



Jouw Opmerking ...

Plaats hier geen grote bestanden (zoals source codes, log files of config files). Gebruik hiervoor het Forum.

Delen:
*
*
Laat me per email weten als er nieuwe reacties zijn.
       Je kunt jouw RSS reader gebruiken om reacties te volgen.


Tweaking4All gebruikt de gratis Gravatar dienst voor Avatar weergave.