Ich habe mir jetzt selber eine Lösung gebastelt. Der Schlauheitsgrad ist ungewiss, aber jedenfalls funktioniert sie. Bei ca. 1.400 Dateien (SSD, ältere CPU) benötigt sie zwischen 10 und 350 Millisekunden. Die besondere Herausforderung war, dass ich sie für ein Bildprogramm brauche, bei dem die Bilder auf einer Übersicht auf neue Positionen gezogen werden können, wobei die Dateinamen automatisch angepasst werden.
Der Algorithmus funktioniert so, dass die Dateien des Verzeichnisses zunächst getrennt werden nach denen, die umbenannt werden sollen, und den anderen (so vorhanden). Dabei wird für die umzubenennenden Dateien gleich der zukünftige Name ermittelt und gespeichert. Es wird geprüft, ob es Kollisionen gibt; bei solchen mit "äußeren" Dateien ist sogleich Schluss.
Danach wird in einer Schleife versucht umzubenennen. Erfolgreich umbenannte Dateien werden aus der Liste entfernt, die somit immer kleiner wird. Schluss ist hier, wenn entweder alle Dateien umbenannt werden konnten oder bei einem abgeschlossenen Durchlauf keine erfolgreichen Umbenennungen mehr möglich waren.
Danach wird mit temporären Umbenennungen gearbeitet bis zum hoffentlich guten Ende.
Der unten stehende Code ist herausoperiert und daher unvollständig und zum Teil simplifiziert; er dient nur zur Verdeutlichung des Algorithmus. Unter Umständen habe ich auch ein paar Anpassungen vergessen.
Die "Contains"-Lösung von Sir Rufo iteriert über alle Elemente der Liste; da ist eine BinarySearch sicher schneller. Ich habe auch eine Interface-Lösung von
hier verwendet ("Replacing SHFileOperation With IFileOperation To Process Multiple File Operations In One Call"), aber mit dieser Lösung lief eine simple Umbenennung 50 - 300 Mal langsamer. Der Hinweis von Namenloser ist nichts für meine Preisklasse.
Delphi-Quellcode:
type
TSortArt = (soNachDatname,soNachUmbName);
TDateiInfo = class(TObject)
Datname : string;
Daterw : string;
UmbName : string;
end;
TDateiListe = class(TObjectList<TDateiInfo>)
private
function VergleicheDatname(const L,R:TDateiInfo):Integer;
function VergleicheUmbName(const L,R:TDateiInfo):Integer;
protected
function getItem(Index: Integer): TDateiInfo; virtual;
procedure setItem(Index: Integer; Objekt: TDateiInfo); virtual;
public
procedure Sort(SortArt:TSortArt);
function SucheDatName(const DatName:string;var P:Integer):Boolean;
function SucheUmbName(const UmbName:string):Boolean;
end;
const Verz = 'C:\temp\';
function BenenneBilderUm:Boolean;
var BInfo,UmbInfo:TDateiInfo;
InDatUmbListe,InDatVerzListe:Boolean;
DatUmbListe,DatVerzListe:TDateiListe;
const Zusatz = '$$$$';
//------------------------------------------------------------------------------------------------------------------------------------------
procedure PrüfeDateinamenKollisionen;
var DatNr,AnzUmb,p:integer; DatnameNeu:string;
begin
// Erstelle jeweils eine Liste der umzubenennenden Dateien und der im Zielverz vorhandenen (ggfs. übrigen) Dateien
AnzUmb := 0;
// Ordne alle Dateien der einen oder der anderen Liste zu
For DatNr := 1 to Dateiliste.Count do begin
BInfo := Dateiliste[DatNr - 1];
If SollUmbenanntWerden then begin
Inc(AnzUmb);
DatnameNeu := ErmittleDatnameNeu(AnzUmb);
BInfo.UmbName := DatnameNeu;
DatUmbListe.Add(BInfo);
end else begin
BInfo.UmbName := '';
DatVerzListe.Add(BInfo);
end;
end;
DatUmbListe.Sort(soNachUmbName);
// Doppelte Dateinamen durch Umbenennen innerhalb der umzubenennenden Dateien kann gehandhabt werden, bei den anderen nicht
For DatNr := 1 to DatUmbListe.Count do begin
If DatUmbListe.SucheDatName(Dateiliste[DatNr - 1].UmbName,p)
then InDatUmbListe := True;
InDatVerzListe := DatVerzListe.SucheDatName(Dateiliste[DatNr - 1].UmbName,p);
If InDatVerzListe
then break;
end;
If InDatVerzListe
then Showmessage('Die Benennung der Dateien in der gewählten Form kollidiert mit vorhandenen Dateinamen im Zielverzeichnis.');
end;
//------------------------------------------------------------------------------------------------------------------------------------------
function BenenneDateiUm(DatnameAlt,DatnameNeu:string):Boolean; overload;
begin
Result := SameText(DatnameAlt,DatnameNeu) or (not FileExists(DatnameNeu) and RenameFile(DatnameAlt,DatnameNeu));
end;
//------------------------------------------------------------------------------------------------------------------------------------------
function BenenneDateiUm(Info:TDateiInfo;MitZusatz:Boolean = False):Boolean; overload;
var DatnameAlt,DatnameNeu:string;
begin
DatnameAlt := Verz + Info.Datname + Info.Daterw;
If MitZusatz
then DatnameNeu := VUmbn.ZielVerz + Info.Datname + Zusatz + Info.Daterw
else DatnameNeu := VUmbn.ZielVerz + Info.Umbname + Info.Daterw;
Result := BenenneDateiUm(DatnameAlt,DatnameNeu);
If Result and not MitZusatz then begin
Info.DatName := Info.UmbName;
Info.UmbName := '';
DatUmbListe.Remove(Info);
end;
end;
//------------------------------------------------------------------------------------------------------------------------------------------
function VergebeNeueDateinamen:Boolean;
var DatNr,AnzUmb,p:integer;
begin
DatUmbListe.Sort(soNachDatName);
Try
// Erster Angriff von unten - alles umbenennen, was geht
Repeat
AnzUmb := DatUmbListe.Count;
For DatNr := DatUmbListe.Count downto 1 do
Result := BenenneDateiUm(DatUmbListe[DatNr - 1]);
Until (DatUmbListe.Count = 0) or (AnzUmb = DatUmbListe.Count);
// Im Idealfall ist hier schon Schluss - wenn nicht, liegen Dateikollisionen vor
If DatUmbListe.Count > 0 then begin
Repeat
// Ausgangswert; "Repeat" wird solange wiederholt, wie sich hier etwas tut (DatUmbListe.Count sinkt)
AnzUmb := DatUmbListe.Count;
For DatNr := DatUmbListe.Count downto 1 do begin
BInfo := DatUmbListe[DatNr - 1];
// Handelt es sich um eine temporär umbenannte Kollisionsdatei?
If EndsText(Zusatz,BInfo.Datname) then begin
If DatUmbListe.SucheDatName(BInfo.UmbName,p) then begin
UmbInfo := DatUmbListe[p];
BenenneDateiUm(UmbInfo);
end else begin
BenenneDateiUm(BInfo);
end;
// der "störende" Gegenpart wird gesucht und durch Umbenennung aus dem Weg geräumt
end else if DatUmbListe.SucheDatName(BInfo.UmbName,p) then begin
UmbInfo := DatUmbListe[p];
If BenenneDateiUm(UmbInfo,True) then begin
UmbInfo.Datname := UmbInfo.Datname + Zusatz;
// jetzt ist der Weg frei
BenenneDateiUm(BInfo);
end;
end else begin
BenenneDateiUm(BInfo);
end;
end;
Until (DatUmbListe.Count = 0) or (AnzUmb = DatUmbListe.Count);
end;
Except
Result := False;
Fehlerbehandlung;
exit;
End;
Result := (DatUmbListe.Count = 0);
If not Result
then Showmessage('Von ' + IntToStr(Dateiliste.Count) + ' Dateien konnten ' + IntToStr(DatUmbListe.Count) + ' nicht umbenannt werden.');
end;
//------------------------------------------------------------------------------------------------------------------------------------------
begin
PrüfeDateinamenKollisionen;
VergebeNeueDateinamen;
end;