diff options
-rw-r--r-- | BUILD/Changelog.txt | 8 | ||||
-rw-r--r-- | BUILD/FreeFileSync.chm | bin | 670693 -> 670693 bytes | |||
-rw-r--r-- | BUILD/Languages/dutch.lng | 131 | ||||
-rw-r--r-- | BUILD/Languages/finnish.lng | 9 | ||||
-rw-r--r-- | BUILD/Resources.zip | bin | 272855 -> 272946 bytes | |||
-rw-r--r-- | file_hierarchy.cpp | 10 | ||||
-rw-r--r-- | file_hierarchy.h | 4 | ||||
-rw-r--r-- | lib/folder_history_box.cpp | 7 | ||||
-rw-r--r-- | lib/folder_history_box.h | 6 | ||||
-rw-r--r-- | lib/resolve_path.cpp | 13 | ||||
-rw-r--r-- | ui/small_dlgs.cpp | 1 | ||||
-rw-r--r-- | version/version.h | 2 | ||||
-rw-r--r-- | version/version.rc | 4 | ||||
-rw-r--r-- | zen/FindFilePlus/find_file_plus.cpp | 141 | ||||
-rw-r--r-- | zen/FindFilePlus/find_file_plus.h | 2 | ||||
-rw-r--r-- | zen/FindFilePlus/load_dll.h | 1 | ||||
-rw-r--r-- | zen/file_handling.cpp | 10 | ||||
-rw-r--r-- | zen/file_id_def.h | 10 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 135 |
19 files changed, 305 insertions, 189 deletions
diff --git a/BUILD/Changelog.txt b/BUILD/Changelog.txt index 5e5d9053..7c9b654f 100644 --- a/BUILD/Changelog.txt +++ b/BUILD/Changelog.txt @@ -2,6 +2,14 @@ |FreeFileSync| -------------- +Changelog v4.5 +-------------- +Fixed "Windows Error Code 50: The request is not supported" +Fixed "Windows Error Code 124: The system call level is not correct" +Fixed config load performance problem if network drive is not reachable +Support traversing truly empty directories (no ., ..) (Windows) + + Changelog v4.4 -------------- Fixed error copying files containing alternate data streams (Windows) diff --git a/BUILD/FreeFileSync.chm b/BUILD/FreeFileSync.chm Binary files differindex 04ae52a9..89f04679 100644 --- a/BUILD/FreeFileSync.chm +++ b/BUILD/FreeFileSync.chm diff --git a/BUILD/Languages/dutch.lng b/BUILD/Languages/dutch.lng index 8c9d8a33..ad09e3f5 100644 --- a/BUILD/Languages/dutch.lng +++ b/BUILD/Languages/dutch.lng @@ -1,6 +1,6 @@ <header> <language name>Nederlands</language name> - <translator>Rogier Wijker</translator> + <translator>Edwin Dierssen</translator> <locale>nl_NL</locale> <flag file>holland.png</flag file> <plural forms>2</plural forms> @@ -8,7 +8,7 @@ </header> <source>Searching for directory %x...</source> -<target></target> +<target>Zoeken naar map %x...</target> <source>Show in Explorer</source> <target>Toon in de verkenner</target> @@ -23,7 +23,7 @@ <target>RealtimeSync - Geautomatiseerde Synchronisatie</target> <source>Select alternate comparison settings</source> -<target></target> +<target>Selecteer alternatieve vergelijkings instellingen</target> <source>Select alternate synchronization settings</source> <target>Selecteer alternatieve synchronisatie instellingen</target> @@ -80,13 +80,13 @@ <target>Vind</target> <source>Select time span</source> -<target></target> +<target>Selecteer tijdsspanne</target> <source>Show pop-up</source> -<target></target> +<target>Laat pop-up zien</target> <source>Show pop-up on errors or warnings</source> -<target></target> +<target>Laat pop-up zien bij foutmeldingen of waarschuwingen</target> <source>Ignore errors</source> <target>Negeer foutmeldingen</target> @@ -110,7 +110,7 @@ <target>Fout tijdens schrijven naar synchronisatie-database:</target> <source>Invalid command line: %x</source> -<target></target> +<target>Ongeldige opdrachtregel: %x</target> <source>Windows Error Code %x:</source> <target>Windows Fout Code %x:</target> @@ -119,7 +119,7 @@ <target>Linux Fout Code %x:</target> <source>Error resolving symbolic link:</source> -<target>Fout tijdens opzoeken van symbolische koppeling:</target> +<target>Fout tijdens opzoeken van snelkoppeling:</target> <source>%x MB</source> <target>%x MB</target> @@ -179,7 +179,7 @@ <target>Opmaak van synchronisatie-database komt niet overeen:</target> <source>Database files do not share a common synchronization session:</source> -<target></target> +<target>Database bestanden delen geen gezamelijke synchronisatie sessie:</target> <source>An exception occurred!</source> <target>Er heeft een uitzondering plaatsgevonden!</target> @@ -224,7 +224,10 @@ <pluralform>[1 Thread]</pluralform> <pluralform>[%x Threads]</pluralform> </source> -<target></target> +<target> +<pluralform>[1 Thread]</pluralform> +<pluralform>[%x Threads]</pluralform> +</target> <source>Invalid FreeFileSync config file!</source> <target>Foutief FreeFileSync configuratiebestand!</target> @@ -239,7 +242,7 @@ <target>Kon een benodigde DLL niet laden:</target> <source>Error accessing Volume Shadow Copy Service!</source> -<target></target> +<target>Fout bij toegang tot Volume Schaduwkopie Service!</target> <source>Making shadow copies on WOW64 is not supported. Please use FreeFileSync 64-bit version.</source> <target>Het aanmaken van schaduwkopieën op WOW64 wordt niet ondersteund. Gebruik alstublieft de 64-bit versie van FreeFileSync.</target> @@ -517,7 +520,7 @@ De opdrachtregel wordt telkens uitgevoerd indien: <target>Taaklijst</target> <source>Create a batch file for automated synchronization. To start in batch mode simply double-click the file or execute via command line: FreeFileSync.exe <ffs_batch file>. This can also be scheduled in your operating system's task planner.</source> -<target></target> +<target>Maak een batch bestand voor automatische synchronisatie. Om te starten in batch mode kunt u simpel dubbelklikken op het bestand of uitvoeren via de opdrachtregel: FreeFileSync.exe <ffs_batchbestand>. Dit kan ook worden gepland in de taakplanner van uw besturingssysteem.</target> <source>Help</source> <target>Help</target> @@ -541,7 +544,7 @@ De opdrachtregel wordt telkens uitgevoerd indien: <target>Status terugkoppeling</target> <source>Run minimized</source> -<target></target> +<target>Geminimaliseerd uitvoeren</target> <source>Maximum number of logfiles:</source> <target>Maximale aantal van logbestanden:</target> @@ -550,7 +553,7 @@ De opdrachtregel wordt telkens uitgevoerd indien: <target>Selecteer een map voor het logbestand:</target> <source>Batch settings</source> -<target></target> +<target>Batch instellingen</target> <source>&Save</source> <target>&Opslaan</target> @@ -568,19 +571,19 @@ De opdrachtregel wordt telkens uitgevoerd indien: <target><Automatisch></target> <source>Identify and propagate changes on both sides using a database. Deletions, renaming and conflicts are detected automatically.</source> -<target></target> +<target>Identificeer en verspreid veranderingen aan beide kanten met behulp van een database. Verwijderingen, hernoemingen en conflicten worden automatisch gedetecteerd.</target> <source>Mirror ->></source> <target>Spiegelen ->></target> <source>Mirror backup of left folder. Right folder is modified to exactly match left folder after synchronization.</source> -<target>Spiegel backup van linker map. Rechter map is bewerk om precies hetzelfde te hebben na synchronisatie.</target> +<target>Spiegel backup van linker map. Rechter map is bewerkt om na synchronisatie een exacte kopie te zijn van de linker map.</target> <source>Update -></source> <target>Bijwerken -></target> <source>Copy new or updated files to right folder.</source> -<target>Kopieer nieuwe of geupdate bestanden naar de rechter map.</target> +<target>Kopiëer nieuwe of geupdate bestanden naar de rechter map.</target> <source>Custom</source> <target>Aangepast</target> @@ -630,10 +633,15 @@ Files are found equal if - file size are the same </source> -<target></target> +<target> +Bestanden worden als gelijk bevonden indien, + - de laatste schrijf tijd en datum + - de bestandsgrootte +gelijk zijn +</target> <source>File time and size</source> -<target></target> +<target>Bestands tijd-en grootte</target> <source> Files are found equal if @@ -650,7 +658,7 @@ overeenkomt <target>Bestandsinhoud</target> <source>Symbolic Link handling</source> -<target>Symbolische Koppeling afhandeling</target> +<target>Afhandeling van snelkoppelingen</target> <source>Synchronizing...</source> <target>Synchroniseert...</target> @@ -767,10 +775,10 @@ Uitsluiten: \stuff\temp\* <target>Uitsluiten</target> <source>Minimum file size</source> -<target></target> +<target>Minimale bestandsgrootte</target> <source>Maximum file size</source> -<target></target> +<target>Maximale bestandsgrootte</target> <source>&Default</source> <target>&Standaard</target> @@ -782,19 +790,19 @@ Uitsluiten: \stuff\temp\* <target>Verplaats kolom naar beneden</target> <source>Transactional file copy</source> -<target></target> +<target>Transactionele bestands kopie</target> <source>Write to a temporary file (*.ffs_tmp) first then rename it. This guarantees a consistent state even in case of fatal error.</source> -<target></target> +<target>Schrijf eerst naar een tijdelijk bestand (*.ffs_tmp) en hernoem dan. Dit garandeert een consistente toestand, zelfs in het geval van een fatale fout.</target> <source>Copy locked files</source> -<target>Kopieer vergrendelde bestanden</target> +<target>Kopiëer vergrendelde bestanden</target> <source>Copy shared or locked files using Volume Shadow Copy Service (Requires Administrator rights)</source> -<target>Kopieer gedeelde of vergrendelde bestanden met Volume Shadow Copy Service (Vereist Administrator rechten)</target> +<target>Kopiëer gedeelde of vergrendelde bestanden met Volume Shadow Copy Service (Vereist Administrator rechten)</target> <source>Copy file access permissions</source> -<target></target> +<target>Kopiëer toegangsrechten van bestand.</target> <source>Transfer file and directory permissions (Requires Administrator rights)</source> <target>Zet bestand en map permissies over (Vereist Administrator rechten)</target> @@ -866,22 +874,22 @@ Uitsluiten: \stuff\temp\* <target>Aanpassen...</target> <source>Select time span...</source> -<target></target> +<target>Selecteer tijdsspanne...</target> <source>Auto-adjust columns</source> <target>Kolommen automatisch aanpassen</target> <source>Icon size:</source> -<target></target> +<target>Icoon grootte:</target> <source>Small</source> -<target></target> +<target>Klein</target> <source>Medium</source> -<target></target> +<target>Middel</target> <source>Large</source> -<target></target> +<target>Groot</target> <source>Include all rows</source> <target>Alle rijen opnemen</target> @@ -989,10 +997,10 @@ Uitsluiten: \stuff\temp\* <target>Toon bestanden die aan de rechterzijde overschreven zullen worden</target> <source>Hide files that won't be copied</source> -<target>Verberg bestanden die niet zullen worden gekopieerd</target> +<target>Verberg bestanden die niet zullen worden gekopiëerd</target> <source>Show files that won't be copied</source> -<target>Toon bestanden die niet gekopieerd zullen worden</target> +<target>Toon bestanden die niet gekopiëerd zullen worden</target> <source>All directories in sync!</source> <target>Alle mappen zijn gesynchroniseerd!</target> @@ -1081,20 +1089,17 @@ Uitsluiten: \stuff\temp\* <source>Inactive</source> <target>Niet actief</target> -<source>Last x hours</source> -<target></target> - <source>Today</source> -<target></target> +<target>Vandaag</target> <source>This week</source> -<target></target> +<target>Deze week</target> <source>This month</source> -<target></target> +<target>Deze maand</target> <source>This year</source> -<target></target> +<target>Dit jaar</target> <source>Byte</source> <target>Byte</target> @@ -1118,7 +1123,7 @@ Uitsluiten: \stuff\temp\* <target>Volg</target> <source>Copy NTFS permissions</source> -<target></target> +<target>Kopiëer NTFS permissies</target> <source>Integrate external applications into context menu. The following macros are available:</source> <target>Integreer externe applicaties in het context menu. De volgende macros zijn beschikbaar:</target> @@ -1256,7 +1261,7 @@ Uitsluiten: \stuff\temp\* <target>Fout tijdens het aanmaken van map:</target> <source>Error copying symbolic link:</source> -<target>Fout tijdens kopiëren van symbolische koppeling:</target> +<target>Fout tijdens kopiëren van snelkoppeling:</target> <source>Error copying file:</source> <target>Fout tijdens kopiëren van bestand:</target> @@ -1316,7 +1321,7 @@ Uitsluiten: \stuff\temp\* <target>Bestanden %x hebben dezelfde datums maar een afwijkende grootte!</target> <source>Symlinks %x have the same date but a different target!</source> -<target>Symbolische koppeling %x heeft dezelfde datum maar een ander doel!</target> +<target>Snelkoppeling %x heeft dezelfde datum maar een ander doel!</target> <source>Comparing content of files %x</source> <target>De inhoud van %x bestanden wordt vergeleken</target> @@ -1334,10 +1339,10 @@ Uitsluiten: \stuff\temp\* <target>Bestanden/Mappen verschillen alleen in attributen</target> <source>Copy new file/folder to left</source> -<target>Kopieer nieuw bestand/map naar links</target> +<target>Kopiëer nieuw bestand/map naar links</target> <source>Copy new file/folder to right</source> -<target>Kopieer nieuw bestand/map naar rechts</target> +<target>Kopiëer nieuw bestand/map naar rechts</target> <source>Delete left file/folder</source> <target>Verwijder linker bestand/map</target> @@ -1346,10 +1351,10 @@ Uitsluiten: \stuff\temp\* <target>Verwijder rechter bestand/map</target> <source>Move file on left</source> -<target></target> +<target>Verplaats bestand aan de linkerkant</target> <source>Move file on right</source> -<target></target> +<target>Verplaats bestand aan de rechterkant</target> <source>Overwrite left file/folder with right one</source> <target>Overschrijf linker bestand/map met de rechter</target> @@ -1361,10 +1366,10 @@ Uitsluiten: \stuff\temp\* <target>Geen actie ondernemen</target> <source>Copy file attributes only to left</source> -<target>Kopieer bestandsattributen alleen naar links</target> +<target>Kopiëer bestandsattributen alleen naar links</target> <source>Copy file attributes only to right</source> -<target>Kopieer bestandsattributen allen naar rechts</target> +<target>Kopiëer bestandsattributen alleen naar rechts</target> <source>Multiple...</source> <target>Meerdere...</target> @@ -1376,40 +1381,40 @@ Uitsluiten: \stuff\temp\* <target>Verwijderen van map %x</target> <source>Deleting symbolic link %x</source> -<target></target> +<target>Verwijderen van snelkoppeling %x</target> <source>Moving file %x to recycle bin</source> -<target></target> +<target>Bezig met verplaatsen van bestand %x naar prullenbak</target> <source>Moving folder %x to recycle bin</source> -<target></target> +<target>Bezig met verplaatsen van map %x naar prullenbak</target> <source>Moving symbolic link %x to recycle bin</source> -<target></target> +<target>Bezig met verplaatsen van snelkoppeling %x naar prullenbak</target> <source>Moving file %x to %y</source> -<target></target> +<target>Bezig met verplaatsen van bestand %x naar %y</target> <source>Moving folder %x to %y</source> -<target></target> +<target>Bezig met verplaatsen van map %x naar %y</target> <source>Moving symbolic link %x to %y</source> -<target></target> +<target>Bezig met verplaatsen van snelkoppeling %x naar %y</target> <source>Creating file %x</source> -<target></target> +<target>Bestand %x wordt aangemaakt</target> <source>Creating symbolic link %x</source> -<target></target> +<target>Snelkoppeling %x wordt aangemaakt</target> <source>Creating folder %x</source> <target>Map %x wordt aangemaakt</target> <source>Overwriting file %x</source> -<target></target> +<target>Bezig met overschrijven van bestand %x</target> <source>Overwriting symbolic link %x</source> -<target></target> +<target>Bezig met overschrijven van snelkoppeling %x</target> <source>Verifying file %x</source> <target>Verifieert bestand %x</target> @@ -1436,7 +1441,7 @@ Uitsluiten: \stuff\temp\* <target>Significant verschil gedetecteerd:</target> <source>More than 50% of the total number of files will be copied or deleted!</source> -<target>Meer dan 50% van alle bestanden zal gekopieerd of verwijderd worden!</target> +<target>Meer dan 50% van alle bestanden zal gekopiëerd of verwijderd worden!</target> <source>Not enough free disk space available in:</source> <target>Niet genoeg vrije schijfruimte beschikbaar op:</target> @@ -1448,7 +1453,7 @@ Uitsluiten: \stuff\temp\* <target>Beschikbare vrije schijfruimte :</target> <source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> -<target></target> +<target>Prullenbak is niet beschikbaar voor de volgende locaties! De bestanden worden permanent verwijderd:</target> <source>A directory will be modified which is part of multiple folder pairs! Please review synchronization settings!</source> <target>Een map wordt bewerkt die deel is van meerdere mappen! Controleer de synchronisatie instellingen!</target> diff --git a/BUILD/Languages/finnish.lng b/BUILD/Languages/finnish.lng index 942fbc33..44299263 100644 --- a/BUILD/Languages/finnish.lng +++ b/BUILD/Languages/finnish.lng @@ -571,7 +571,7 @@ Komento suoritetaan kun: <target><- Automaattinen -></target> <source>Identify and propagate changes on both sides using a database. Deletions, renaming and conflicts are detected automatically.</source> -<target></target> +<target>Tunnista ja monista muutokset tietokannalla molemmille puolille. Poisto/Poikkeama/Uudelleen nimeäminan tunnistetaan automaattisesti.</target> <source>Mirror ->></source> <target>Peilaava ->></target> @@ -1089,9 +1089,6 @@ Sulje pois: \stuff\temp\* <source>Inactive</source> <target>Lepotilassa</target> -<source>Last x hours</source> -<target>Viimeiset x tuntia</target> - <source>Today</source> <target>Tänään</target> @@ -1354,10 +1351,10 @@ Sulje pois: \stuff\temp\* <target>Poista tiedosto/hakemisto oikealta</target> <source>Move file on left</source> -<target></target> +<target>Siirä vasen tiedosto</target> <source>Move file on right</source> -<target></target> +<target>Siirrä oikea tiedosto</target> <source>Overwrite left file/folder with right one</source> <target>Ylikirjoita oikea tiedosto/hakemista vasemanpuolisella</target> diff --git a/BUILD/Resources.zip b/BUILD/Resources.zip Binary files differindex c4749b68..41b492ba 100644 --- a/BUILD/Resources.zip +++ b/BUILD/Resources.zip diff --git a/file_hierarchy.cpp b/file_hierarchy.cpp index f5d2d28c..5bd7c5bb 100644 --- a/file_hierarchy.cpp +++ b/file_hierarchy.cpp @@ -117,9 +117,9 @@ bool hasDirectChild(const HierarchyObject& hierObj, Predicate p) } -SyncOperation FileSystemObject::testSyncOperation(SyncDirection testSyncDir, bool enabled) const +SyncOperation FileSystemObject::testSyncOperation(SyncDirection testSyncDir, bool active) const { - return proposedSyncOperation(getCategory(), enabled, testSyncDir, syncDirConflict); + return proposedSyncOperation(getCategory(), active, testSyncDir, syncDirConflict); } @@ -205,15 +205,15 @@ SyncOperation DirMapping::getSyncOperation() const } -SyncOperation FileMapping::testSyncOperation(SyncDirection testSyncDir, bool enabled) const +SyncOperation FileMapping::testSyncOperation(SyncDirection testSyncDir, bool active) const { - SyncOperation op = FileSystemObject::testSyncOperation(testSyncDir, enabled); + SyncOperation op = FileSystemObject::testSyncOperation(testSyncDir, active); /* check whether we can optimize "create + delete" via "move": note: as long as we consider "create + delete" cases only, detection of renamed files, should be fine even for "binary" comparison variant! */ - if (const FileMapping* refFile = dynamic_cast<const FileMapping*>(FileSystemObject::retrieve(moveFileRef))) + if (const FileSystemObject* refFile = dynamic_cast<const FileMapping*>(FileSystemObject::retrieve(moveFileRef))) //we expect a "FileMapping", but only need a "FileSystemObject" { SyncOperation opRef = refFile->FileSystemObject::getSyncOperation(); //do *not* make a virtual call! diff --git a/file_hierarchy.h b/file_hierarchy.h index ac7ed87d..c0db4be9 100644 --- a/file_hierarchy.h +++ b/file_hierarchy.h @@ -348,7 +348,7 @@ public: virtual CompareFilesResult getCategory() const = 0; virtual std::wstring getCatConflict() const = 0; //only filled if getCategory() == FILE_CONFLICT //sync operation - virtual SyncOperation testSyncOperation(SyncDirection testSyncDir, bool enabled) const; + virtual SyncOperation testSyncOperation(SyncDirection testSyncDir, bool active) const; virtual SyncOperation getSyncOperation() const; std::wstring getSyncOpConflict() const; //return conflict when determining sync direction or during categorization @@ -491,7 +491,7 @@ public: virtual CompareFilesResult getCategory() const; virtual std::wstring getCatConflict() const; - virtual SyncOperation testSyncOperation(SyncDirection testSyncDir, bool enabled) const; + virtual SyncOperation testSyncOperation(SyncDirection testSyncDir, bool active) const; virtual SyncOperation getSyncOperation() const; template <SelectedSide side> void syncTo(const FileDescriptor& descrTarget, const FileDescriptor* descrSource = NULL); //copy + update file attributes (optional) diff --git a/lib/folder_history_box.cpp b/lib/folder_history_box.cpp index b395cbf4..c8bd042a 100644 --- a/lib/folder_history_box.cpp +++ b/lib/folder_history_box.cpp @@ -60,8 +60,8 @@ void FolderHistoryBox::OnHideDropDown(wxCommandEvent& event) } #endif - -void FolderHistoryBox::update(const wxString& dirname) +//set value and update list are technically entangled: see potential bug description below +void FolderHistoryBox::setValueAndUpdateList(const wxString& dirname) { //it may be a little lame to update the list on each mouse-button click, but it should be working and we dont't have to manipulate wxComboBox internals @@ -69,8 +69,7 @@ void FolderHistoryBox::update(const wxString& dirname) //add some aliases to allow user changing to volume name and back, if possible #ifdef FFS_WIN - const Zstring activePath = toZ(GetValue()); - std::vector<Zstring> aliases = getDirectoryAliases(activePath); + std::vector<Zstring> aliases = getDirectoryAliases(toZ(dirname)); dirList.insert(dirList.end(), aliases.begin(), aliases.end()); #endif diff --git a/lib/folder_history_box.h b/lib/folder_history_box.h index 28b85c90..859234cc 100644 --- a/lib/folder_history_box.h +++ b/lib/folder_history_box.h @@ -80,7 +80,7 @@ public: void setValue(const wxString& dirname) { - update(dirname); //required for setting value correctly + Linux to ensure the dropdown is shown as being populated + setValueAndUpdateList(dirname); //required for setting value correctly + Linux to ensure the dropdown is shown as being populated } // GetValue @@ -88,9 +88,9 @@ public: private: void OnKeyEvent(wxKeyEvent& event); void OnSelection(wxCommandEvent& event); - void OnUpdateList(wxEvent& event) { update(GetValue()); event.Skip(); } + void OnUpdateList(wxEvent& event) { setValueAndUpdateList(GetValue()); event.Skip(); } - void update(const wxString& dirname); + void setValueAndUpdateList(const wxString& dirname); #if wxCHECK_VERSION(2, 9, 1) void OnShowDropDown(wxCommandEvent& event); diff --git a/lib/resolve_path.cpp b/lib/resolve_path.cpp index feeb98e3..df65b98b 100644 --- a/lib/resolve_path.cpp +++ b/lib/resolve_path.cpp @@ -5,6 +5,7 @@ #include <map> #include <set> #include <zen/scope_guard.h> +#include <zen/thread.h> #ifdef FFS_WIN #include <zen/dll.h> @@ -354,6 +355,7 @@ Zstring volumenNameToPath(const Zstring& volumeName) //return empty string on er #ifdef FFS_WIN +//attention: this call may seriously block if network volume is not available!!! Zstring volumePathToName(const Zstring& volumePath) //return empty string on error { const DWORD bufferSize = MAX_PATH + 1; @@ -440,9 +442,14 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less dirname[1] == L':' && dirname[2] == L'\\') { - Zstring volname = volumePathToName(Zstring(dirname.c_str(), 3)); - if (!volname.empty()) - output.insert(L"[" + volname + L"]" + Zstring(dirname.c_str() + 2)); + //attention: "volumePathToName()" will seriously block if network volume is not available!!! + boost::unique_future<Zstring> futVolName = zen::async([=] { return volumePathToName(Zstring(dirname.c_str(), 3)); }); + if (futVolName.timed_wait(boost::posix_time::seconds(1))) + { + Zstring volname = futVolName.get(); + if (!volname.empty()) + output.insert(L"[" + volname + L"]" + Zstring(dirname.c_str() + 2)); + } } //2. replace volume name by volume path: [SYSTEM]\dirname -> c:\dirname diff --git a/ui/small_dlgs.cpp b/ui/small_dlgs.cpp index cf323279..63d3f566 100644 --- a/ui/small_dlgs.cpp +++ b/ui/small_dlgs.cpp @@ -160,7 +160,6 @@ FilterDlg::FilterDlg(wxWindow* parent, enumTimeDescr. add(UTIME_NONE, _("Inactive")). - //add(UTIME_LAST_X_HOURS, _("Last x hours")). //better: "Last %x hour" ? add(UTIME_TODAY, _("Today")). add(UTIME_THIS_WEEK, _("This week")). add(UTIME_THIS_MONTH, _("This month")). diff --git a/version/version.h b/version/version.h index 50936457..03ad1777 100644 --- a/version/version.h +++ b/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t currentVersion[] = L"4.4"; //internal linkage! +const wchar_t currentVersion[] = L"4.5"; //internal linkage! } #endif diff --git a/version/version.rc b/version/version.rc index c5f61a95..2b486f25 100644 --- a/version/version.rc +++ b/version/version.rc @@ -1,2 +1,2 @@ -#define VER_FREEFILESYNC 4,4,0,0 -#define VER_FREEFILESYNC_STR "4.4\0" +#define VER_FREEFILESYNC 4,5,0,0 +#define VER_FREEFILESYNC_STR "4.5\0" diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index becfe553..e613177b 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -10,16 +10,18 @@ #include "load_dll.h" #include <zen/scope_guard.h> +#include <cstdio> + using namespace dll; using namespace findplus; namespace { -struct FileError +struct NtFileError //exception class { - FileError(ULONG errorCode) : win32Error(errorCode) {} - ULONG win32Error; + NtFileError(NTSTATUS errorCode) : ntError(errorCode) {} + NTSTATUS ntError; }; @@ -91,8 +93,8 @@ bool findplus::initDllBinding() //evaluate in ::DllMain() when attaching process //http://msdn.microsoft.com/en-us/library/ff563638(v=VS.85).aspx //verify dynamic dll binding - return ntOpenFile && - ntClose && + return ntOpenFile && + ntClose && ntQueryDirectoryFile && rtlNtStatusToDosError && rtlFreeUnicodeString && @@ -108,9 +110,15 @@ public: FileSearcher(const wchar_t* dirname); //throw FileError ~FileSearcher(); - void readdir(FileInformation& output); //throw FileError + void readDir(FileInformation& output) { (this->*readDirFun)(output); } //throw FileError + + bool tryFallbackToDefaultQuery(); //call if readDir() throws STATUS_NOT_SUPPORTED or similar private: + template <class QueryPolicy> void readDirImpl(FileInformation& output); //throw FileError + //handle fallback, if retrieving file id is not supported by file system layer + void (FileSearcher::*readDirFun)(FileInformation& output); + UNICODE_STRING ntPathName; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! HANDLE hDir; @@ -124,9 +132,43 @@ private: }; +namespace +{ +/* +Common C-style policy handling directory traversal: + +struct QueryPolicy +{ + typedef ... RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = ...; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo); +}; +*/ + +struct DirQueryDefault +{ + typedef FILE_BOTH_DIR_INFORMATION RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = FileBothDirectoryInformation; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) { fileInfo.fileId.QuadPart = 0; } +}; + +struct DirQueryFileId +{ + typedef FILE_ID_BOTH_DIR_INFORMATION RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = FileIdBothDirectoryInformation; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) + { + fileInfo.fileId.QuadPart = rawInfo.FileId.QuadPart; //may be 0 even in this context, e.g. for mapped FTP drive! + static_assert(sizeof(fileInfo.fileId) == sizeof(rawInfo.FileId), "dang!"); + } +}; +} + + FileSearcher::FileSearcher(const wchar_t* dirname) : hDir(NULL), - nextEntryOffset(0) + nextEntryOffset(0), + readDirFun(&FileSearcher::readDirImpl<DirQueryFileId>) //start optimistically { ntPathName.Buffer = NULL; ntPathName.Length = 0; @@ -136,7 +178,8 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : //-------------------------------------------------------------------------------------------------------------- //convert dosFileName, e.g. C:\Users or \\?\C:\Users to ntFileName \??\C:\Users - //in contrast to ::FindFirstFile() we don't evaluate the relativeName, however tests indicate ntFileName is *always* filled with an absolute name, even if dosFileName is relative + //in contrast to ::FindFirstFile() implementation we don't evaluate the relativeName, + //however tests indicate ntFileName is *always* filled with an absolute name, even if dosFileName is relative //NOTE: RtlDosPathNameToNtPathName_U may be used on all XP/Win7/Win8 for compatibility // RtlDosPathNameToNtPathName_U: used by Windows XP available with OS version 3.51 (Windows NT) and higher @@ -145,7 +188,7 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : &ntPathName, //__out ntFileName, NULL, //__out_optFilePart, NULL)) //__out_opt relativeName - empty if dosFileName is absolute - throw FileError(rtlNtStatusToDosError(STATUS_OBJECT_PATH_NOT_FOUND)); //translates to ERROR_PATH_NOT_FOUND, same behavior like ::FindFirstFileEx() + throw NtFileError(STATUS_OBJECT_PATH_NOT_FOUND); //translates to ERROR_PATH_NOT_FOUND, same behavior like ::FindFirstFileEx() OBJECT_ATTRIBUTES objAttr = {}; InitializeObjectAttributes(&objAttr, //[out] POBJECT_ATTRIBUTES initializedAttributes, @@ -163,7 +206,7 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //__in ULONG shareAccess, - 7 on Win7/Win8, 3 on XP FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT); //__in ULONG openOptions - 4021 used on all XP/Win7/Win8 if (!NT_SUCCESS(rv)) - throw FileError(rtlNtStatusToDosError(rv)); + throw NtFileError(rv); } guardConstructor.dismiss(); @@ -179,16 +222,33 @@ FileSearcher::~FileSearcher() if (ntPathName.Buffer) rtlFreeUnicodeString(&ntPathName); //cleanup identical to ::FindFirstFile() - //note that most if this function seems inlined by the linker, so that its assembly looks equivalent to "RtlFreeHeap(GetProcessHeap(), 0, ntPathName.Buffer)" + //note that most of this function seems inlined by the linker, so that its assembly looks equivalent to "RtlFreeHeap(GetProcessHeap(), 0, ntPathName.Buffer)" +} + + +inline +bool FileSearcher::tryFallbackToDefaultQuery() +{ + if (readDirFun == &FileSearcher::readDirImpl<DirQueryDefault>) + return false; //already default + + //Note: NtQueryDirectoryFile() may not respect "restartScan" for some weird Win2000 file system drivers, so we won't bother + //Samba before v3.0.22 (Mar 30, 2006) seems to have a bug which sucessfully returns 128 elements via NtQueryDirectoryFile() + //and FileIdBothDirectoryInformation, then fails with STATUS_INVALID_LEVEL. + //Although traversal is NOT finished yet, it will further return STATUS_NO_MORE_FILES, even if falling back to FileBothDirectoryInformation!!! + + readDirFun = &FileSearcher::readDirImpl<DirQueryDefault>; + return true; } -void FileSearcher::readdir(FileInformation& output) +template <class QueryPolicy> +void FileSearcher::readDirImpl(FileInformation& output) //throw FileError { //although FILE_ID_FULL_DIR_INFORMATION should suffice for our purposes, there are problems on Windows XP for certain directories, e.g. "\\Vboxsvr\build" //making NtQueryDirectoryFile() return with STATUS_INVALID_PARAMETER while other directories, e.g. "C:\" work fine for some reason //FILE_ID_BOTH_DIR_INFORMATION on the other hand works on XP/Win7/Win8 - //performance: there is no noticable difference between FILE_ID_BOTH_DIR_INFORMATION and FILE_ID_FULL_DIR_INFORMATION + //performance: there is no noticeable difference between FILE_ID_BOTH_DIR_INFORMATION and FILE_ID_FULL_DIR_INFORMATION /* corresponding first access in ::FindFirstFileW() @@ -216,18 +276,26 @@ void FileSearcher::readdir(FileInformation& output) &status, //__out PIO_STATUS_BLOCK ioStatusBlock, &buffer, //__out_bcount(Length) PVOID fileInformation, BUFFER_SIZE, //__in ULONG length, ::FindNextFileW() on all XP/Win7/Win8 uses sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * 2000 == 0x1000 - FileIdBothDirectoryInformation, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation" + QueryPolicy::fileInformationClass, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation" false, //__in BOOLEAN returnSingleEntry, NULL, //__in_opt PUNICODE_STRING mask, false); //__in BOOLEAN restartScan if (!NT_SUCCESS(rv)) - throw FileError(rtlNtStatusToDosError(rv)); //throws STATUS_NO_MORE_FILES when finished + { + if (rv == STATUS_NO_SUCH_FILE) //harmonize ntQueryDirectoryFile() error handling! failure to find a file on first call returns STATUS_NO_SUCH_FILE, + rv = STATUS_NO_MORE_FILES; //while on subsequent accesses return STATUS_NO_MORE_FILES + //note: not all directories contain "., .." entries! E.g. a drive's root directory or NetDrive + ftp.gnu.org\CRYPTO.README" + //-> addon: this is NOT a directory, it looks like on in NetDrive, but it's a file in Opera + + throw NtFileError(rv); //throws STATUS_NO_MORE_FILES when finished + } - if (status.Information == 0) //except for the first call to call ::NtQueryDirectoryFile(): - throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //if buffer size is too small, return value is STATUS_SUCCESS and Information == 0 -> we don't expect this! + if (status.Information == 0) //except for the first call to call ::NtQueryDirectoryFile(): + throw NtFileError(STATUS_BUFFER_OVERFLOW); //if buffer size is too small, return value is STATUS_SUCCESS and Information == 0 -> we don't expect this! } - const FILE_ID_BOTH_DIR_INFORMATION& dirInfo = *reinterpret_cast<FILE_ID_BOTH_DIR_INFORMATION*>(reinterpret_cast<char*>(buffer) + nextEntryOffset); + typedef typename QueryPolicy::RawFileInfo RawFileInfo; + const RawFileInfo& dirInfo = *reinterpret_cast<RawFileInfo*>(reinterpret_cast<char*>(buffer) + nextEntryOffset); if (dirInfo.NextEntryOffset == 0) nextEntryOffset = 0; //our offset is relative to the beginning of the buffer @@ -241,15 +309,16 @@ void FileSearcher::readdir(FileInformation& output) return tmp; }; + QueryPolicy::extractFileId(dirInfo, output); + output.creationTime = toFileTime(dirInfo.CreationTime); output.lastWriteTime = toFileTime(dirInfo.LastWriteTime); output.fileSize.QuadPart = dirInfo.EndOfFile.QuadPart; - output.fileId.QuadPart = dirInfo.FileId.QuadPart; output.fileAttributes = dirInfo.FileAttributes; output.shortNameLength = dirInfo.FileNameLength / sizeof(TCHAR); //FileNameLength is in bytes! if (dirInfo.FileNameLength + sizeof(TCHAR) > sizeof(output.shortName)) //this may actually happen if ::NtQueryDirectoryFile() decides to return a - throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //short name of length MAX_PATH + 1, 0-termination is not required! + throw NtFileError(STATUS_BUFFER_OVERFLOW); //short name of length MAX_PATH + 1, 0-termination is not required! ::memcpy(output.shortName, dirInfo.FileName, dirInfo.FileNameLength); output.shortName[output.shortNameLength] = 0; //NOTE: FILE_ID_BOTH_DIR_INFORMATION::FileName in general is NOT 0-terminated! It is on XP/Win7, but NOT on Win8! @@ -257,7 +326,6 @@ void FileSearcher::readdir(FileInformation& output) static_assert(sizeof(output.creationTime) == sizeof(dirInfo.CreationTime), "dang!"); static_assert(sizeof(output.lastWriteTime) == sizeof(dirInfo.LastWriteTime), "dang!"); static_assert(sizeof(output.fileSize) == sizeof(dirInfo.EndOfFile), "dang!"); - static_assert(sizeof(output.fileId) == sizeof(dirInfo.FileId), "dang!"); static_assert(sizeof(output.fileAttributes) == sizeof(dirInfo.FileAttributes), "dang!"); } @@ -268,10 +336,10 @@ FindHandle findplus::openDir(const wchar_t* dirname) { return new FileSearcher(dirname); //throw FileError } - catch (const FileError& err) + catch (const NtFileError& e) { - setWin32Error(err.win32Error); - return NULL; + setWin32Error(rtlNtStatusToDosError(e.ntError)); + return nullptr; } } @@ -280,12 +348,28 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) { try { - hnd->readdir(output); //throw FileError + hnd->readDir(output); //throw FileError return true; } - catch (const FileError& err) + catch (const NtFileError& e) { - setWin32Error(err.win32Error); + /* + fallback to default directory query method, if FileIdBothDirectoryInformation is not properly implemented + this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. + */ + if (e.ntError != STATUS_NO_MORE_FILES) + if (e.ntError == STATUS_INVALID_LEVEL || + e.ntError == STATUS_NOT_SUPPORTED || + e.ntError == STATUS_INVALID_PARAMETER || + e.ntError == STATUS_INVALID_NETWORK_RESPONSE || + e.ntError == STATUS_INVALID_INFO_CLASS || + e.ntError == STATUS_ACCESS_VIOLATION) //FileIdBothDirectoryInformation on XP accessing UDF + { + if (hnd->tryFallbackToDefaultQuery()) + return readDir(hnd, output); //implementation of tryFallbackToDefaultQuery() promises, that we don't land in an endless recursion here! + } + + setWin32Error(rtlNtStatusToDosError(e.ntError)); return false; } } @@ -293,6 +377,5 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) void findplus::closeDir(FindHandle hnd) { - if (hnd) //play a little "nice" - delete hnd; + delete hnd; } diff --git a/zen/FindFilePlus/find_file_plus.h b/zen/FindFilePlus/find_file_plus.h index 72b76dbb..33e9a178 100644 --- a/zen/FindFilePlus/find_file_plus.h +++ b/zen/FindFilePlus/find_file_plus.h @@ -34,7 +34,7 @@ struct FileInformation FILETIME creationTime; FILETIME lastWriteTime; ULARGE_INTEGER fileSize; - ULARGE_INTEGER fileId; + ULARGE_INTEGER fileId; //optional: may be 0 if not supported DWORD fileAttributes; DWORD shortNameLength; WCHAR shortName[MAX_PATH + 1]; //shortName is 0-terminated diff --git a/zen/FindFilePlus/load_dll.h b/zen/FindFilePlus/load_dll.h index 350de9f8..24ce7174 100644 --- a/zen/FindFilePlus/load_dll.h +++ b/zen/FindFilePlus/load_dll.h @@ -39,7 +39,6 @@ private: - void* /*FARPROC*/ loadSymbol(const wchar_t* libraryName, const char* functionName); } diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index 1e44afb7..d6804e34 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -2008,12 +2008,12 @@ void rawCopyStream(const Zstring& sourceFile, FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting std::vector<char>& buffer = []() -> std::vector<char>& - { - static boost::thread_specific_ptr<std::vector<char>> cpyBuf; - if (!cpyBuf.get()) - cpyBuf.reset(new std::vector<char>(512 * 1024)); //512 kb seems to be a reasonable buffer size + { + static boost::thread_specific_ptr<std::vector<char>> cpyBuf; + if (!cpyBuf.get()) + cpyBuf.reset(new std::vector<char>(512 * 1024)); //512 kb seems to be a reasonable buffer size return *cpyBuf; - }(); + }(); //copy contents of sourceFile to targetFile UInt64 totalBytesTransferred; diff --git a/zen/file_id_def.h b/zen/file_id_def.h index b2029879..7e729eb1 100644 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -29,13 +29,16 @@ FileId extractFileID(const BY_HANDLE_FILE_INFORMATION& fileInfo) ULARGE_INTEGER uint = {}; uint.HighPart = fileInfo.nFileIndexHigh; uint.LowPart = fileInfo.nFileIndexLow; - return std::make_pair(fileInfo.dwVolumeSerialNumber, uint.QuadPart); + + return fileInfo.dwVolumeSerialNumber != 0 && uint.QuadPart != 0 ? + FileId(fileInfo.dwVolumeSerialNumber, uint.QuadPart) : FileId(); } inline FileId extractFileID(DWORD dwVolumeSerialNumber, ULARGE_INTEGER fileId) { - return std::make_pair(dwVolumeSerialNumber, fileId.QuadPart); + return dwVolumeSerialNumber != 0 && fileId.QuadPart != 0 ? + FileId(dwVolumeSerialNumber, fileId.QuadPart) : FileId(); } namespace impl @@ -57,7 +60,8 @@ typedef std::pair<decltype(impl::StatDummy::st_dev), decltype(impl::StatDummy::s inline FileId extractFileID(const struct stat& fileInfo) { - return std::make_pair(fileInfo.st_dev, fileInfo.st_ino); + return fileInfo.st_dev != 0 && fileInfo.st_ino != 0 ? + FileId(fileInfo.st_dev, fileInfo.st_ino) : FileId(); } #endif } diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 8a8a8b6f..236581a3 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -31,13 +31,13 @@ namespace //implement "retry" in a generic way: template <class Command> inline //function object expecting to throw FileError if operation fails -void tryReportingError(Command cmd, zen::TraverseCallback& callback) +bool tryReportingError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored { for (;;) try { cmd(); - return; + return true; } catch (const FileError& e) { @@ -46,7 +46,7 @@ void tryReportingError(Command cmd, zen::TraverseCallback& callback) case TraverseCallback::TRAV_ERROR_RETRY: break; case TraverseCallback::TRAV_ERROR_IGNORE: - return; + return false; default: assert(false); break; @@ -119,18 +119,17 @@ const DllFun<findplus::OpenDirFunc> openDir (findplus::getDllName(), findplus:: const DllFun<findplus::ReadDirFunc> readDir (findplus::getDllName(), findplus::readDirFuncName ); //load at startup: avoid pre C++11 static initialization MT issues const DllFun<findplus::CloseDirFunc> closeDir(findplus::getDllName(), findplus::closeDirFuncName); // - /* Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir(): -struct X //see "policy based design" +struct TraverserPolicy //see "policy based design" { -typedef ... Handle; +typedef ... DirHandle; typedef ... FindData; -static Handle create(const Zstring& directoryPf, FindData& fileInfo); //throw FileError - concession to FindFirstFile(): implement two operations: 1. open handle, 2. retrieve first data set -static void destroy(Handle hnd); //throw() -static bool next(Handle hnd, const Zstring& directory, WIN32_FIND_DATA& fileInfo) //throw FileError +static void create(const Zstring& directory, DirHandle& hnd); //throw FileError - *no* concession to FindFirstFile(): open handle only, *no* return of data! +static void destroy(DirHandle hnd); //throw() +static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError -//helper routines +//FindData "member" functions static void extractFileInfo (const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output); static Int64 getModTime (const FindData& fileInfo); static const FILETIME& getModTimeRaw (const FindData& fileInfo); //yet another concession to DST hack @@ -140,35 +139,54 @@ static bool isDirectory (const FindData& fileInfo); static bool isSymlink (const FindData& fileInfo); } -Note: Win32 FindFirstFile(), FindNextFile() is a weaker abstraction than FileFilePlus openDir(), readDir(), closeDir() and Unix opendir(), closedir(), stat(), - so unfortunately we have to use former as a greatest common divisor +Note: Win32 FindFirstFile(), FindNextFile() is a weaker abstraction than FileFilePlus openDir(), readDir(), closeDir() and Unix opendir(), closedir(), stat() */ struct Win32Traverser { - typedef HANDLE Handle; + struct DirHandle + { + DirHandle() : searchHandle(NULL), firstRead(true) {} + + HANDLE searchHandle; + bool firstRead; + WIN32_FIND_DATA firstData; + }; + typedef WIN32_FIND_DATA FindData; - static Handle create(const Zstring& directory, FindData& fileInfo) //throw FileError + static void create(const Zstring& directory, DirHandle& hnd) //throw FileError { const Zstring& directoryPf = endsWith(directory, FILE_NAME_SEPARATOR) ? directory : directory + FILE_NAME_SEPARATOR; - HANDLE output = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &fileInfo); + hnd.searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &hnd.firstData); //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH - if (output == INVALID_HANDLE_VALUE) + if (hnd.searchHandle == INVALID_HANDLE_VALUE) throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); - //::GetLastError() == ERROR_FILE_NOT_FOUND -> actually NOT okay, even for an empty directory this should not occur (., ..) - return output; + + //::GetLastError() == ERROR_FILE_NOT_FOUND -> *usually* NOT okay: + //however it is unclear whether this indicates a missing directory or a completely empty directory + // note: not all directories contain "., .." entries! E.g. a drive's root directory or NetDrive + ftp.gnu.org\CRYPTO.README" + // -> addon: this is NOT a directory, it looks like one in NetDrive, but it's a file in Opera! + //we have to guess it's former and let the error propagate + // -> FindFirstFile() is a nice example of violation of API design principle of single responsibility and its consequences } - static void destroy(Handle hnd) { ::FindClose(hnd); } //throw() + static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw() - static bool next(Handle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError + static bool getEntry(DirHandle& hnd, const Zstring& directory, FindData& fileInfo) //throw FileError { - if (!::FindNextFile(hnd, &fileInfo)) + if (hnd.firstRead) + { + hnd.firstRead = false; + ::memcpy(&fileInfo, &hnd.firstData, sizeof(fileInfo)); + return true; + } + + if (!::FindNextFile(hnd.searchHandle, &fileInfo)) { if (::GetLastError() == ERROR_NO_MORE_FILES) //not an error situation return false; @@ -178,7 +196,6 @@ struct Win32Traverser return true; } - //helper routines template <class FindData> static void extractFileInfo(const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output) { @@ -208,27 +225,27 @@ struct Win32Traverser struct FilePlusTraverser { - typedef findplus::FindHandle Handle; + struct DirHandle + { + DirHandle() : searchHandle(NULL) {} + + findplus::FindHandle searchHandle; + }; + typedef findplus::FileInformation FindData; - static Handle create(const Zstring& directory, FindData& fileInfo) //throw FileError + static void create(const Zstring& directory, DirHandle& hnd) //throw FileError { - Handle output = ::openDir(applyLongPathPrefix(directory).c_str()); - if (output == NULL) + hnd.searchHandle = ::openDir(applyLongPathPrefix(directory).c_str()); + if (hnd.searchHandle == NULL) throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); - - bool rv = next(output, directory, fileInfo); //throw FileError - if (!rv) //we expect at least two successful reads, even for an empty directory: ., .. - throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted(ERROR_NO_MORE_FILES)); - - return output; } - static void destroy(Handle hnd) { ::closeDir(hnd); } //throw() + static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw() - static bool next(Handle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError + static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError { - if (!::readDir(hnd, fileInfo)) + if (!::readDir(hnd.searchHandle, fileInfo)) { if (::GetLastError() == ERROR_NO_MORE_FILES) //not an error situation return false; @@ -238,7 +255,6 @@ struct FilePlusTraverser return true; } - //helper routines template <class FindData> static void extractFileInfo(const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output) { @@ -305,31 +321,43 @@ private: throw FileError(_("Endless loop when traversing directory:") + L"\n\"" + directory + L"\""); }, sink); - typename Trav::FindData fileInfo = {}; - - typename Trav::Handle searchHandle = 0; + typename Trav::DirHandle searchHandle; - tryReportingError([&] + const bool openSuccess = tryReportingError([&] { typedef Trav Trav; //f u VS! - searchHandle = Trav::create(directory, fileInfo); //throw FileError + Trav::create(directory, searchHandle); //throw FileError }, sink); - if (searchHandle == 0) + if (!openSuccess) return; //ignored error ZEN_ON_BLOCK_EXIT(typedef Trav Trav; Trav::destroy(searchHandle)); - do + typename Trav::FindData fileInfo = {}; + + while ([&]() -> bool + { + bool moreData = false; + + typedef Trav Trav1; //f u VS! + tryReportingError([&] { - //don't return "." and ".." + typedef Trav1 Trav; //f u VS! + moreData = Trav::getEntry(searchHandle, directory, fileInfo); //throw FileError + }, sink); + + return moreData; + }()) + { + //skip "." and ".." const Zchar* const shortName = Trav::getShortName(fileInfo); if (shortName[0] == L'.' && - (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0'))) + (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0'))) continue; const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? - directory + shortName : - directory + FILE_NAME_SEPARATOR + shortName; + directory + shortName : + directory + FILE_NAME_SEPARATOR + shortName; if (Trav::isSymlink(fileInfo) && !followSymlinks_) //evaluate symlink directly { @@ -383,19 +411,6 @@ private: sink.onFile(shortName, fullName, details); } } - while ([&]() -> bool - { - bool moreData = false; - - typedef Trav Trav1; //f u VS! - tryReportingError([&] - { - typedef Trav1 Trav; //f u VS! - moreData = Trav::next(searchHandle, directory, fileInfo); //throw FileError - }, sink); - - return moreData; - }()); } |