diff options
44 files changed, 1456 insertions, 528 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt index a2b21734..5b631823 100644 --- a/FreeFileSync/Build/Changelog.txt +++ b/FreeFileSync/Build/Changelog.txt @@ -1,6 +1,21 @@ -FreeFileSync 6.9 ----------------- -Use FreeFileSync taskbar icon when available (Windows 7) +FreeFileSync 6.10 +----------------- +Fixed crash when accessing recycle bin in compatibility mode (Windows 7, 8) +Draw middle grid selection irrespective of focus column +Don't show parts of progress graph if nothing to sync +Break on missing directories before evaluating warnings +Ignore leading/trailing whitespace in search panel +Disable search panel during comparison +Disable shortkeys during comparison +Log folder pair only if files are synced +Fixed number separator formatting for english locale +Copying locked files now inactive by default +Show all affected folders when warning about a shared sub folder + + +FreeFileSync 6.9 [2014-09-01] +----------------------------- +Reuse FreeFileSync taskbar link when available (Windows 7) Limit number of retries when creating temporary files Fixed bitmap rendering issue for high-contrast color schemes Revised and fixed unclear GUI texts diff --git a/FreeFileSync/Build/Help/html/RealtimeSync.html b/FreeFileSync/Build/Help/html/RealtimeSync.html index ca6b764b..6b2e2feb 100644 --- a/FreeFileSync/Build/Help/html/RealtimeSync.html +++ b/FreeFileSync/Build/Help/html/RealtimeSync.html @@ -118,7 +118,7 @@ on the stick. RealtimeSync will also trigger each time files are modified in <FO <h3>Limitations:</h3> <UL> <LI>If multiple changes happen at the same time, only the name of the first file is written to variable <b>%changed_file%</b>. - <LI>While RealtimeSync is executing the command line, monitoring is inactive and changes occurring during this time are lost. + <LI>While RealtimeSync is executing the command line, monitoring is inactive and changes occurring during this time are not detected. </UL> </BODY> diff --git a/FreeFileSync/Build/Help/html/Schedule a Batch Job.html b/FreeFileSync/Build/Help/html/Schedule a Batch Job.html index 46c1221b..efc239e8 100644 --- a/FreeFileSync/Build/Help/html/Schedule a Batch Job.html +++ b/FreeFileSync/Build/Help/html/Schedule a Batch Job.html @@ -34,6 +34,10 @@ <LI>If you don't want error or warning messages to interrupt synchronization, set <B>Handle errors</B> to either <B>Ignore</B> or <B>Stop</B>. <br> + <LI>If log files are required, enable <B>Save log</B> and enter a folder path. Additionally FreeFileSync always stores the result of the last + synchronization in file <FONT FACE="Courier New, monospace">LastSyncs.log</FONT> (up to a user-defined size, see <A HREF="Expert%20Settings.html">Expert Settings</A>). + <br> + <LI>Set up the FreeFileSync batch job in your operating system's scheduler: <BR> diff --git a/FreeFileSync/Build/Languages/lithuanian.lng b/FreeFileSync/Build/Languages/lithuanian.lng index 69ac63f8..d8e0573f 100644 --- a/FreeFileSync/Build/Languages/lithuanian.lng +++ b/FreeFileSync/Build/Languages/lithuanian.lng @@ -8,19 +8,19 @@ </header> <source>Both sides have changed since last synchronization.</source> -<target>Abi pusės buvo pakeistos nuo paskutinio sinchronizavimo.</target> +<target>Abi pusės buvo pakeistos nuo paskutinio suvienodinimo.</target> <source>Cannot determine sync-direction:</source> -<target>Nepavyksta nustatyti sinchronizavimo krypties:</target> +<target>Nepavyksta nustatyti suvienodinimo krypties:</target> <source>No change since last synchronization.</source> -<target>Nėra pakitimo nuo pakutinio sinchronizavimo.</target> +<target>Nėra pakitimo nuo pakutinio suvienodinimo.</target> <source>The database entry is not in sync considering current settings.</source> -<target>Duomenų bazės įrašas nesinchronizuotas remiantis šiais nustatymais.</target> +<target>Duomenų bazės įrašas nesuvienodintas remiantis šiais nustatymais.</target> <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> -<target>Nustatomos numatytos sinchronizavimo kryptys: Seni failai bus perrašyti naujesniais failais.</target> +<target>Nustatomos numatytos suvienodinimo kryptys: Seni failai bus perrašyti naujesniais failais.</target> <source>Checking recycle bin availability for folder %x...</source> <target>Tikrinamas šiukšliadėžės prieinamumas aplankui %x...</target> @@ -98,7 +98,7 @@ <target>Nepavyksta rasti šių aplankų:</target> <source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> -<target>Jei žinote, kad kiekvienas katalogas yra tuščias, šią klaidą galite ignoruoti. Katalogai bus automatiškai sukurti sinchronizacijos metu.</target> +<target>Jei žinote, kad kiekvienas katalogas yra tuščias, šią klaidą galite ignoruoti. Katalogai bus automatiškai sukurti suvienodinimo metu.</target> <source>A folder input field is empty.</source> <target>Aplanko įvesties laukas yra tuščias.</target> @@ -107,7 +107,7 @@ <target>Atitinkamas aplankas bus laikomas tuščiu.</target> <source>The following folder paths are dependent from each other:</source> -<target></target> +<target>Sekančios aplankų nuorodos yra priklausomos viena nuo kitos</target> <source>File %x has an invalid date.</source> <target>Failas %x turi netinkamą datą.</target> @@ -131,7 +131,7 @@ <target>Ieškoma simbolinės nuorodos %x</target> <source>Comparing content of files %x</source> -<target>Sulyginamas failų turinys %x</target> +<target>Palyginamas failų turinys %x</target> <source>Generating file list...</source> <target>Sukuriamas failų sąrašas...</target> @@ -218,11 +218,14 @@ <source>%x GB</source> <target>%x GB</target> +<source>Cannot load file %x.</source> +<target>Nepavyksta įkelti failo %x.</target> + <source>Database file %x is incompatible.</source> <target>Duomenų bazė %x yra netinkama.</target> <source>Initial synchronization:</source> -<target>Pirminis sinchronizavimas:</target> +<target>Pirminis suvienodinimas:</target> <source>Database file %x does not yet exist.</source> <target>Duomenų bazės failo %x dar nėra.</target> @@ -322,9 +325,6 @@ <source>Please use FreeFileSync 64-bit version to create shadow copies on this system.</source> <target>Prašome naudoti 64-bit FreeFileSync versiją, kad sukurti šėšėlines kopijas šioje sistemoje.</target> -<source>Cannot load file %x.</source> -<target>Nepavyksta įkelti failo %x.</target> - <source>Cannot determine volume name for %x.</source> <target>Vietos vardo %x nustatyti nepavyko</target> @@ -347,7 +347,7 @@ <target>&Išeiti</target> <source>&File</source> -<target></target> +<target>&Failas</target> <source>&View help</source> <target>&Rodyti pagalbą</target> @@ -501,26 +501,26 @@ Komanda inicijuojama jei: <source>Data verification error: %x and %y have different content.</source> <target>Duomenų patikros klaida: %x ir %y turinys yra skirtingas.</target> -<source>Cannot find folder %x.</source> -<target></target> - <source>Target folder %x already existing.</source> <target>Tikslo aplankas %x jau yra.</target> +<source>Cannot find folder %x.</source> +<target>Negalima rasti aplanko %x.</target> + <source>Target folder input field must not be empty.</source> <target>Tikslo aplanko įvesties laukas negali būti tuščias.</target> -<source>Please enter a target folder for versioning.</source> -<target>Prašome nurodyti aplanką kitoms versijoms</target> - <source>Source folder %x not found.</source> <target>Šaltinio aplankas %x nerastas.</target> +<source>Please enter a target folder for versioning.</source> +<target>Prašome nurodyti aplanką kitoms versijoms</target> + <source>The following items have unresolved conflicts and will not be synchronized:</source> -<target>Šie elementai turi neišspręstų konfliktų ir nebus sinchronizuoti:</target> +<target>Šie elementai turi neišspręstų konfliktų ir nebus suvienodinti:</target> <source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> -<target>Šie katalogai yra labai skirtingi. Įsitikinkite, kad sinchronizacijai pasirinkote teisingus katalogus.</target> +<target>Šie katalogai yra labai skirtingi. Įsitikinkite, kad suvienodinimui pasirinkote teisingus katalogus.</target> <source>Not enough free disk space available in:</source> <target>Nepakanka laisvos disko vietos:</target> @@ -532,7 +532,7 @@ Komanda inicijuojama jei: <target>Pasiekiama:</target> <source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source> -<target></target> +<target>Aplankai grupuojami į vieną poaplankį. Prašome patikrinti jūsų pasirinkimus</target> <source>Synchronizing folder pair:</source> <target>Suvienodinama aplankų pora:</target> @@ -635,10 +635,10 @@ Komanda inicijuojama jei: <target>Pavadinimas</target> <source>Relative folder</source> -<target></target> +<target>Aplanai</target> <source>Base folder</source> -<target>Bazinis aplankas</target> +<target>Pasirinktas aplankas</target> <source>Size</source> <target>Dydis</target> @@ -656,7 +656,7 @@ Komanda inicijuojama jei: <target>Veiksmas</target> <source>Drag && drop</source> -<target>Vilkti ir numesti</target> +<target>Vilkti && numesti</target> <source>Local comparison settings</source> <target>Vietiniai palyginimo parametrai</target> @@ -677,7 +677,7 @@ Komanda inicijuojama jei: <target>Pašalinti vietinius parametrus</target> <source>Clear local filter</source> -<target></target> +<target>Ištrinti vietos filtrą</target> <source>Copy</source> <target>Kopijuoti</target> @@ -698,10 +698,10 @@ Komanda inicijuojama jei: <target>Išsaugoti kaip &užduočių paketą...</target> <source>Start &comparison</source> -<target></target> +<target>Pradėti &palyginimą</target> <source>Start &synchronization</source> -<target></target> +<target>Pradėti &suvienodinimą</target> <source>&Options</source> <target>&Parinktys</target> @@ -713,7 +713,7 @@ Komanda inicijuojama jei: <target>&Ieškoti...</target> <source>&Reset layout</source> -<target></target> +<target>&Išnaujo nustatyti išdėstymą</target> <source>&Export file list...</source> <target>&Eksportuoti failų sąrašą...</target> @@ -734,7 +734,7 @@ Komanda inicijuojama jei: <target>Atšaukti</target> <source>Compare</source> -<target>Sulyginti</target> +<target>Palyginti</target> <source>Synchronize</source> <target>Suvienodinti</target> @@ -758,7 +758,7 @@ Komanda inicijuojama jei: <target>Atitikti atveją</target> <source>New</source> -<target></target> +<target>Naujas</target> <source>Open...</source> <target>Atversti...</target> @@ -803,13 +803,13 @@ Komanda inicijuojama jei: <target>Sutapatinti vienodus failus lyginant failų turinį.</target> <source>Ignore time shift (in hours)</source> -<target></target> +<target>Ignoruoti laiko pasikeitimą (valandomis)</target> <source>Consider file times with specified offset as equal</source> -<target></target> +<target>Failai su tam tikra paklaida skaitomi kaip lygūs</target> <source>Handle daylight saving time</source> -<target></target> +<target>Naudoti vasaros laiką</target> <source>Symbolic links:</source> <target>Simbolinės nuorodos:</target> @@ -842,7 +842,7 @@ Komanda inicijuojama jei: <target>Didžiausias:</target> <source>C&lear</source> -<target></target> +<target>&Išvalyti</target> <source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> <target>Pasirinkti filtro taisykles norint išskirti pasirinktus failus iš Suvienodinimo. Įveskite failo kelią, kuris atitinka aplanką.</target> @@ -868,7 +868,7 @@ Komanda inicijuojama jei: <target>Ištrinti failus:</target> <source>&Permanent</source> -<target></target> +<target>&Pastovus</target> <source>Delete or overwrite files permanently</source> <target>Trinti ar perrašyti failus visam laikui</target> @@ -880,7 +880,7 @@ Komanda inicijuojama jei: <target>Padaryti šiukšlių dėžėje esančių ištrintų ar perrašytų failų atsarginę kopiją</target> <source>&Versioning</source> -<target></target> +<target>&Atsarginės versijos kūrimas</target> <source>Move files to a user-defined folder</source> <target>Perkelti failus į vartotojo nustatytą katalogą</target> @@ -895,7 +895,7 @@ Komanda inicijuojama jei: <target>Slėpti visus klaidų ir perspėjimų pranešimus</target> <source>&Pop-up</source> -<target></target> +<target>&Iškylančiame</target> <source>Show pop-up on errors or warnings</source> <target>Rodyti pranešimą esant klaidoms ar perspėjimams</target> @@ -931,7 +931,7 @@ Komanda inicijuojama jei: <target>Nuleisti į apačią dešinėje</target> <source>Bytes copied:</source> -<target></target> +<target>Nukopijuota baitų:</target> <source>Close</source> <target>Uždaryti</target> @@ -946,7 +946,7 @@ Komanda inicijuojama jei: <target>Sukurti paleidimo failą suvienodinimui be priežiūros. Norint jį paleisti reikia paspausti and failo du kartus pele arba nustatyti su užduočių planuotoju: %x</target> <source>&Stop</source> -<target></target> +<target>&Sustabdyti</target> <source>Stop synchronization at first error</source> <target>Stabdyti suvienodinimą, pirmai klaidai įvykus</target> @@ -1015,10 +1015,10 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <target>Apibūdinimas</target> <source>Show hidden dialogs again</source> -<target></target> +<target>Vėl rodyti paslėptus dialogo langus</target> <source>Show all permanently hidden dialogs and warning messages again</source> -<target></target> +<target>Vėl rodyti visus paslėptus dialogo langus ir įspėjamuosius pranešimus</target> <source>&Default</source> <target>&Numatyta</target> @@ -1081,10 +1081,10 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <target>Pagrindinė įrankinė</target> <source>Start comparison</source> -<target></target> +<target>Pradėti palyginimą</target> <source>Comparison settings</source> -<target>Sulyginimo nustatymai</target> +<target>Palyginimo nustatymai</target> <source>Synchronization settings</source> <target>Suvienodinimo nustatymai</target> @@ -1187,7 +1187,7 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <target>Paskutinė sesija</target> <source>Folder Comparison and Synchronization</source> -<target>Aplankų palyginimas ir suvienodinimas</target> +<target>Aplankų Palyginimas ir Suvienodinimas</target> <source>Configuration saved</source> <target>Nustatymai išsaugoti</target> @@ -1211,7 +1211,7 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <target>Suvienodinimo parametrai</target> <source>Clear filter</source> -<target></target> +<target>Panaikinti filtrą</target> <source>Show files that exist on left side only</source> <target>Rodyti failus, kurie egzistuoja tik kairėje pusėje</target> @@ -1298,7 +1298,7 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <target>Tikrinama...</target> <source>Comparing content...</source> -<target>Sulyginamas turinys...</target> +<target>Palyginimo turinys...</target> <source>Info</source> <target>Informacija</target> @@ -1372,10 +1372,10 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <target>- Kitos pusės atitikmuo %item_folder%</target> <source>Show hidden dialogs and warning messages again?</source> -<target></target> +<target>Ar vėl rodyti visus paslėptus dialogo langus ir įspėjamuosius pranešimus</target> <source>&Show</source> -<target></target> +<target>&Rodyti</target> <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> <target>Nustatyti ir skatinti pokyčius apbiejose pusėse. Trinimai, perkėlimai ir konfliktai yra aptinkami automatiškai naudojant duomenų bazę.</target> diff --git a/FreeFileSync/Build/Languages/dutch.lng b/FreeFileSync/Build/Languages/outdated/dutch.lng index 8ef337e1..d9802c29 100644 --- a/FreeFileSync/Build/Languages/dutch.lng +++ b/FreeFileSync/Build/Languages/outdated/dutch.lng @@ -7,6 +7,81 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>&Show</source> +<target></target> + +<source>Show hidden dialogs and warning messages again?</source> +<target></target> + +<source>Clear filter</source> +<target></target> + +<source>Start comparison</source> +<target></target> + +<source>Show all permanently hidden dialogs and warning messages again</source> +<target></target> + +<source>Show hidden dialogs again</source> +<target></target> + +<source>&Stop</source> +<target></target> + +<source>Bytes copied:</source> +<target></target> + +<source>&Pop-up</source> +<target></target> + +<source>&Versioning</source> +<target></target> + +<source>&Permanent</source> +<target></target> + +<source>C&lear</source> +<target></target> + +<source>Handle daylight saving time</source> +<target></target> + +<source>Consider file times with specified offset as equal</source> +<target></target> + +<source>Ignore time shift (in hours)</source> +<target></target> + +<source>New</source> +<target></target> + +<source>&Reset layout</source> +<target></target> + +<source>Start &synchronization</source> +<target></target> + +<source>Start &comparison</source> +<target></target> + +<source>Clear local filter</source> +<target></target> + +<source>Relative folder</source> +<target></target> + +<source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source> +<target></target> + +<source>Cannot find folder %x.</source> +<target></target> + +<source>&File</source> +<target></target> + +<source>The following folder paths are dependent from each other:</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Beide zijdes zijn veranderd sinds de laatste synchronisatie.</target> @@ -106,9 +181,6 @@ <source>The corresponding folder will be considered as empty.</source> <target>De overeenkomstige map zal als leeg worden beschouwd.</target> -<source>The following folder paths are dependent from each other:</source> -<target></target> - <source>File %x has an invalid date.</source> <target>Bestand %x heeft een ongeldige datum.</target> @@ -217,6 +289,9 @@ <source>%x GB</source> <target>%x GB</target> +<source>Cannot load file %x.</source> +<target>Kan bestand %x niet laden.</target> + <source>Database file %x is incompatible.</source> <target>Databasebestand %x is niet compatibel.</target> @@ -319,9 +394,6 @@ <source>Please use FreeFileSync 64-bit version to create shadow copies on this system.</source> <target>Gebruik alstublieft de FreeFileSync 64-bit versie om schaduwkopieën te maken op dit systeem.</target> -<source>Cannot load file %x.</source> -<target>Kan bestand %x niet laden.</target> - <source>Cannot determine volume name for %x.</source> <target>Kan volumenaam voor %x niet bepalen.</target> @@ -343,9 +415,6 @@ <source>&Quit</source> <target>&Afsluiten</target> -<source>&File</source> -<target></target> - <source>&View help</source> <target>Help &bekijken</target> @@ -498,21 +567,18 @@ De opdracht word geactiveerd als: <source>Data verification error: %x and %y have different content.</source> <target>Data verificatie fout: %x en %y hebben een verschillende inhoud.</target> -<source>Cannot find folder %x.</source> -<target></target> - <source>Target folder %x already existing.</source> <target>Doelmap %x bestaat al.</target> <source>Target folder input field must not be empty.</source> <target>Doelmap mag niet leeg zijn.</target> -<source>Please enter a target folder for versioning.</source> -<target>Selecteer alstublieft een doelmap voor versiebeheer.</target> - <source>Source folder %x not found.</source> <target>Bronmap %x niet gevonden.</target> +<source>Please enter a target folder for versioning.</source> +<target>Selecteer alstublieft een doelmap voor versiebeheer.</target> + <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>De volgende items hebben onopgeloste conflicten en zullen niet worden gesynchroniseerd:</target> @@ -528,9 +594,6 @@ De opdracht word geactiveerd als: <source>Available:</source> <target>Beschikbaar:</target> -<source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source> -<target></target> - <source>Synchronizing folder pair:</source> <target>Bezig met synchroniseren van folder paar:</target> @@ -630,9 +693,6 @@ De opdracht word geactiveerd als: <source>Name</source> <target>Naam</target> -<source>Relative folder</source> -<target></target> - <source>Base folder</source> <target>Hoofdmap</target> @@ -672,9 +732,6 @@ De opdracht word geactiveerd als: <source>Remove local settings</source> <target>Verwijder lokale instellingen</target> -<source>Clear local filter</source> -<target></target> - <source>Copy</source> <target>Kopiëren</target> @@ -693,12 +750,6 @@ De opdracht word geactiveerd als: <source>Save as &batch job...</source> <target>Opslaan als &batch opdracht...</target> -<source>Start &comparison</source> -<target></target> - -<source>Start &synchronization</source> -<target></target> - <source>&Options</source> <target>&Opties</target> @@ -708,9 +759,6 @@ De opdracht word geactiveerd als: <source>&Find...</source> <target>&Zoek...</target> -<source>&Reset layout</source> -<target></target> - <source>&Export file list...</source> <target>&Exporteer bestandslijst...</target> @@ -753,9 +801,6 @@ De opdracht word geactiveerd als: <source>Match case</source> <target>Hoofdlettergevoelig</target> -<source>New</source> -<target></target> - <source>Open...</source> <target>Open...</target> @@ -798,15 +843,6 @@ De opdracht word geactiveerd als: <source>Identify equal files by comparing the file content.</source> <target>Identificeer gelijke bestanden door de bestandsinhoud te vergelijken.</target> -<source>Ignore time shift (in hours)</source> -<target></target> - -<source>Consider file times with specified offset as equal</source> -<target></target> - -<source>Handle daylight saving time</source> -<target></target> - <source>Symbolic links:</source> <target>Snelkoppelingen:</target> @@ -837,9 +873,6 @@ De opdracht word geactiveerd als: <source>Maximum:</source> <target>Maximum:</target> -<source>C&lear</source> -<target></target> - <source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> <target>Selecteer filter regels om bepaalde bestanden uit te sluiten van synchronisatie. Geef bestandspaden in die relatief zijn aan hun corresponderende folderparen.</target> @@ -863,9 +896,6 @@ De opdracht word geactiveerd als: <source>Delete files:</source> <target>Verwijder bestanden:</target> -<source>&Permanent</source> -<target></target> - <source>Delete or overwrite files permanently</source> <target>Bestanden definitief verwijderen of overschrijven</target> @@ -875,9 +905,6 @@ De opdracht word geactiveerd als: <source>Back up deleted and overwritten files in the recycle bin</source> <target>Maak een backup van verwijderde en overschreven bestanden in de prullenbak</target> -<source>&Versioning</source> -<target></target> - <source>Move files to a user-defined folder</source> <target>Verplaats bestanden naar een gebruikers gedefineerde map</target> @@ -890,9 +917,6 @@ De opdracht word geactiveerd als: <source>Hide all error and warning messages</source> <target>Verberg alle fout- en waarschuwingsberichten</target> -<source>&Pop-up</source> -<target></target> - <source>Show pop-up on errors or warnings</source> <target>Laat pop-up zien bij foutmeldingen of waarschuwingen</target> @@ -926,9 +950,6 @@ De opdracht word geactiveerd als: <source>Minimize to notification area</source> <target>Minimaliseer naar systeemvak</target> -<source>Bytes copied:</source> -<target></target> - <source>Close</source> <target>Sluiten</target> @@ -941,9 +962,6 @@ De opdracht word geactiveerd als: <source>Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x</source> <target>Maak een batchbestand voor onbeheerde synchronisatie. Om te starten dubbelklikt u dit bestand of rooster in een taakplanner: %x</target> -<source>&Stop</source> -<target></target> - <source>Stop synchronization at first error</source> <target>Stop synchronisatie bij eerste foutmelding</target> @@ -1010,12 +1028,6 @@ Dit garandeert een consistente staat, zelfs in het geval van een serieuze fout. <source>Description</source> <target>Omschrijving</target> -<source>Show hidden dialogs again</source> -<target></target> - -<source>Show all permanently hidden dialogs and warning messages again</source> -<target></target> - <source>&Default</source> <target>&Standaard</target> @@ -1076,9 +1088,6 @@ Dit garandeert een consistente staat, zelfs in het geval van een serieuze fout. <source>Main Bar</source> <target>Hoofdbalk</target> -<source>Start comparison</source> -<target></target> - <source>Comparison settings</source> <target>Vergelijksinstellingen</target> @@ -1202,9 +1211,6 @@ Dit garandeert een consistente staat, zelfs in het geval van een serieuze fout. <source>Synchronization Settings</source> <target>Synchronisatie instellingen</target> -<source>Clear filter</source> -<target></target> - <source>Show files that exist on left side only</source> <target>Toon bestanden die alleen aan de linkerzijde bestaan</target> @@ -1361,12 +1367,6 @@ Dit garandeert een consistente staat, zelfs in het geval van een serieuze fout. <source>- Other side's counterpart to %item_folder%</source> <target>- Tegenhanger van de andere kant naar %item_folder%</target> -<source>Show hidden dialogs and warning messages again?</source> -<target></target> - -<source>&Show</source> -<target></target> - <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> <target>Identificeer en pas veranderingen toe aan beide kanten. Verwijderingen, verplaatsingen en conflicten worden automatisch gevonden met behulp van een database.</target> diff --git a/FreeFileSync/Build/Languages/ukrainian.lng b/FreeFileSync/Build/Languages/outdated/ukrainian.lng index a2a7ea0f..e8314f07 100644 --- a/FreeFileSync/Build/Languages/ukrainian.lng +++ b/FreeFileSync/Build/Languages/outdated/ukrainian.lng @@ -7,6 +7,81 @@ <plural_definition>n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2</plural_definition> </header> +<source>&Show</source> +<target></target> + +<source>Show hidden dialogs and warning messages again?</source> +<target></target> + +<source>Clear filter</source> +<target></target> + +<source>Start comparison</source> +<target></target> + +<source>Show all permanently hidden dialogs and warning messages again</source> +<target></target> + +<source>Show hidden dialogs again</source> +<target></target> + +<source>&Stop</source> +<target></target> + +<source>Bytes copied:</source> +<target></target> + +<source>&Pop-up</source> +<target></target> + +<source>&Versioning</source> +<target></target> + +<source>&Permanent</source> +<target></target> + +<source>C&lear</source> +<target></target> + +<source>Handle daylight saving time</source> +<target></target> + +<source>Consider file times with specified offset as equal</source> +<target></target> + +<source>Ignore time shift (in hours)</source> +<target></target> + +<source>New</source> +<target></target> + +<source>&Reset layout</source> +<target></target> + +<source>Start &synchronization</source> +<target></target> + +<source>Start &comparison</source> +<target></target> + +<source>Clear local filter</source> +<target></target> + +<source>Relative folder</source> +<target></target> + +<source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source> +<target></target> + +<source>Cannot find folder %x.</source> +<target></target> + +<source>&File</source> +<target></target> + +<source>The following folder paths are dependent from each other:</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>З моменту останньої синхронізації з обох сторін відбулися зміни.</target> @@ -109,9 +184,6 @@ <source>The corresponding folder will be considered as empty.</source> <target>Відповідна папка буде вважатися порожньою.</target> -<source>The following folder paths are dependent from each other:</source> -<target></target> - <source>File %x has an invalid date.</source> <target>Файл %x має неіснуючу дату.</target> @@ -221,6 +293,9 @@ <source>%x GB</source> <target>%x ГБ</target> +<source>Cannot load file %x.</source> +<target>Не вдається завантажити файл %x.</target> + <source>Database file %x is incompatible.</source> <target>Несумісний файл бази даних %x.</target> @@ -325,9 +400,6 @@ <source>Please use FreeFileSync 64-bit version to create shadow copies on this system.</source> <target>Будь ласка, використовуйте 64-розрядну версію FreeFileSync для створення тіньових копій у цій системі.</target> -<source>Cannot load file %x.</source> -<target>Не вдається завантажити файл %x.</target> - <source>Cannot determine volume name for %x.</source> <target>Не вдалося встановити ім'я тому для %x.</target> @@ -349,9 +421,6 @@ <source>&Quit</source> <target>&Вихід</target> -<source>&File</source> -<target></target> - <source>&View help</source> <target>&Перегляд довідки</target> @@ -504,21 +573,18 @@ The command is triggered if: <source>Data verification error: %x and %y have different content.</source> <target>Помилка перевірки даних: %x та %y мають різний вміст.</target> -<source>Cannot find folder %x.</source> -<target></target> - <source>Target folder %x already existing.</source> <target>Цільова папка %x вже існує.</target> <source>Target folder input field must not be empty.</source> <target>Поле цільової папки не повинно бути порожнім.</target> -<source>Please enter a target folder for versioning.</source> -<target>Будь ласка, введіть цільову папку для версій.</target> - <source>Source folder %x not found.</source> <target>Вихідний каталог %x не знайдено.</target> +<source>Please enter a target folder for versioning.</source> +<target>Будь ласка, введіть цільову папку для версій.</target> + <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Наступні елементи мають невирішені конфлікти і не будуть синхронізовані:</target> @@ -534,9 +600,6 @@ The command is triggered if: <source>Available:</source> <target>Доступно:</target> -<source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source> -<target></target> - <source>Synchronizing folder pair:</source> <target>Синхронізація пари папок:</target> @@ -637,9 +700,6 @@ The command is triggered if: <source>Name</source> <target>Назва</target> -<source>Relative folder</source> -<target></target> - <source>Base folder</source> <target>Базова папка</target> @@ -679,9 +739,6 @@ The command is triggered if: <source>Remove local settings</source> <target>Вилучити локальні налаштування</target> -<source>Clear local filter</source> -<target></target> - <source>Copy</source> <target>Копіювати</target> @@ -700,12 +757,6 @@ The command is triggered if: <source>Save as &batch job...</source> <target>Зберегти як &пакетне завдання</target> -<source>Start &comparison</source> -<target></target> - -<source>Start &synchronization</source> -<target></target> - <source>&Options</source> <target>&Опції</target> @@ -715,9 +766,6 @@ The command is triggered if: <source>&Find...</source> <target>&Знайти...</target> -<source>&Reset layout</source> -<target></target> - <source>&Export file list...</source> <target>&Експортувати список файлів...</target> @@ -760,9 +808,6 @@ The command is triggered if: <source>Match case</source> <target>Враховувати регістр</target> -<source>New</source> -<target></target> - <source>Open...</source> <target>Відкрити...</target> @@ -805,15 +850,6 @@ The command is triggered if: <source>Identify equal files by comparing the file content.</source> <target>Визначити однакові файли порівнюючи їх вміст.</target> -<source>Ignore time shift (in hours)</source> -<target></target> - -<source>Consider file times with specified offset as equal</source> -<target></target> - -<source>Handle daylight saving time</source> -<target></target> - <source>Symbolic links:</source> <target>Символьні посилання:</target> @@ -844,9 +880,6 @@ The command is triggered if: <source>Maximum:</source> <target>Максимум:</target> -<source>C&lear</source> -<target></target> - <source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> <target>Виберіть правила фільтрації для виключення деяких файлів із синхронізації. Введіть шляхи файлів відносно відповідної пари папок.</target> @@ -870,9 +903,6 @@ The command is triggered if: <source>Delete files:</source> <target>Вилучити файли:</target> -<source>&Permanent</source> -<target></target> - <source>Delete or overwrite files permanently</source> <target>Вилучати чи перезаписати файли назавжди</target> @@ -882,9 +912,6 @@ The command is triggered if: <source>Back up deleted and overwritten files in the recycle bin</source> <target>Резервно зберегти вилучені та перезаисані файли в Корзині</target> -<source>&Versioning</source> -<target></target> - <source>Move files to a user-defined folder</source> <target>Перемістити файли у визначену користувачем папку</target> @@ -897,9 +924,6 @@ The command is triggered if: <source>Hide all error and warning messages</source> <target>Приховати всі помилки і повідомлення з попередженнями</target> -<source>&Pop-up</source> -<target></target> - <source>Show pop-up on errors or warnings</source> <target>Показувати виринаючі вікна при помилках та попередженнях</target> @@ -933,9 +957,6 @@ The command is triggered if: <source>Minimize to notification area</source> <target>Згорнути в область повідомлень</target> -<source>Bytes copied:</source> -<target></target> - <source>Close</source> <target>Замкнути</target> @@ -948,9 +969,6 @@ The command is triggered if: <source>Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x</source> <target>Створити пакетний файл для автоматичної синхронізації. Щоб розпочати двічі клацніть цей файл або заплануйте в планувальнику завдань: %x</target> -<source>&Stop</source> -<target></target> - <source>Stop synchronization at first error</source> <target>Зупинити синхронізацію при першій помилці</target> @@ -1017,12 +1035,6 @@ This guarantees a consistent state even in case of a serious error. <source>Description</source> <target>Опис</target> -<source>Show hidden dialogs again</source> -<target></target> - -<source>Show all permanently hidden dialogs and warning messages again</source> -<target></target> - <source>&Default</source> <target>&За замовчуванням</target> @@ -1083,9 +1095,6 @@ This guarantees a consistent state even in case of a serious error. <source>Main Bar</source> <target>Головна панель</target> -<source>Start comparison</source> -<target></target> - <source>Comparison settings</source> <target>Налаштування порівнювання</target> @@ -1213,9 +1222,6 @@ This guarantees a consistent state even in case of a serious error. <source>Synchronization Settings</source> <target>Налаштування Синхронізації</target> -<source>Clear filter</source> -<target></target> - <source>Show files that exist on left side only</source> <target>Показати файли, які є тільки ліворуч</target> @@ -1374,12 +1380,6 @@ This guarantees a consistent state even in case of a serious error. <source>- Other side's counterpart to %item_folder%</source> <target>- Елемент з протилежної сторони до %item_folder%</target> -<source>Show hidden dialogs and warning messages again?</source> -<target></target> - -<source>&Show</source> -<target></target> - <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> <target>Виявити та поширити зміни на обидві сторони. Видалення, перейменування та конфлікти визначаються автоматично використовуючи базу даних.</target> diff --git a/FreeFileSync/Build/Languages/polish.lng b/FreeFileSync/Build/Languages/polish.lng index 306d3f05..6aac661e 100644 --- a/FreeFileSync/Build/Languages/polish.lng +++ b/FreeFileSync/Build/Languages/polish.lng @@ -107,7 +107,7 @@ <target>Katalog będzie oznaczony jako pusty.</target> <source>The following folder paths are dependent from each other:</source> -<target></target> +<target>Następujące ścieżki katalogów są od siebie zależne:</target> <source>File %x has an invalid date.</source> <target>Plik %x ma nieprawidłową datę.</target> @@ -218,6 +218,9 @@ <source>%x GB</source> <target>%x GB</target> +<source>Cannot load file %x.</source> +<target>Nie można wczytać pliku %x.</target> + <source>Database file %x is incompatible.</source> <target>Plik bazy danych %x nie jest kompatybilny.</target> @@ -322,9 +325,6 @@ <source>Please use FreeFileSync 64-bit version to create shadow copies on this system.</source> <target>Usługa shadow copy jest aktywna tylko w wersji 64 bitowej programu FreeFileSync.</target> -<source>Cannot load file %x.</source> -<target>Nie można wczytać pliku %x.</target> - <source>Cannot determine volume name for %x.</source> <target>Nie można określić nazwy dysku dla %x.</target> @@ -347,7 +347,7 @@ <target>Zam&knij</target> <source>&File</source> -<target></target> +<target>&Plik</target> <source>&View help</source> <target>&Pomoc</target> @@ -501,21 +501,21 @@ Komenda jest wykonywana gdy: <source>Data verification error: %x and %y have different content.</source> <target>Nastąpił błąd weryfikacji: %x oraz %y różnią się zawartością.</target> -<source>Cannot find folder %x.</source> -<target></target> - <source>Target folder %x already existing.</source> <target>Katalog docelowy %x już istnieje.</target> +<source>Cannot find folder %x.</source> +<target>Nie można znaleźć katalogu %x.</target> + <source>Target folder input field must not be empty.</source> <target>Pole katalog docelowy nie może być puste.</target> -<source>Please enter a target folder for versioning.</source> -<target>Określ katalog do wersjonowania.</target> - <source>Source folder %x not found.</source> <target>Nie znaleziono katalogu docelowego %x.</target> +<source>Please enter a target folder for versioning.</source> +<target>Określ katalog do wersjonowania.</target> + <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Te elementy znajdują się w konflikcie, którego nie można rozwiązać. Pliki nie zostaną zsynchronizowane:</target> @@ -532,7 +532,7 @@ Komenda jest wykonywana gdy: <target>Dostępne:</target> <source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source> -<target></target> +<target>Wiele par katalogów zapisuje do wspólnego podkatalogu. Przejrzyj konfigurację.</target> <source>Synchronizing folder pair:</source> <target>Synchronizacja katalgów:</target> @@ -635,7 +635,7 @@ Komenda jest wykonywana gdy: <target>Nazwa</target> <source>Relative folder</source> -<target></target> +<target>Relatywny katalog</target> <source>Base folder</source> <target>Katalog bazowy</target> @@ -677,7 +677,7 @@ Komenda jest wykonywana gdy: <target>Usuń lokalne ustawienia</target> <source>Clear local filter</source> -<target></target> +<target>Wyczyść lokalny filtr</target> <source>Copy</source> <target>Kopiuj</target> @@ -698,10 +698,10 @@ Komenda jest wykonywana gdy: <target>Zapisz w trybie &wsadowym...</target> <source>Start &comparison</source> -<target></target> +<target>Rozpo&cznij porównywanie</target> <source>Start &synchronization</source> -<target></target> +<target>Rozpocznij &synchronizację</target> <source>&Options</source> <target>&Opcje</target> @@ -713,7 +713,7 @@ Komenda jest wykonywana gdy: <target>&Szukaj...</target> <source>&Reset layout</source> -<target></target> +<target>&Resetuj układ okien</target> <source>&Export file list...</source> <target>&Eksportuj listę plików...</target> @@ -758,7 +758,7 @@ Komenda jest wykonywana gdy: <target>Uwzględnij wielkość liter</target> <source>New</source> -<target></target> +<target>Nowy</target> <source>Open...</source> <target>Otwórz...</target> @@ -803,13 +803,13 @@ Komenda jest wykonywana gdy: <target>Określ różnice w plikach na podstawie ich zawartości.</target> <source>Ignore time shift (in hours)</source> -<target></target> +<target>Ignoruj różnice w czasie (w godzinach)</target> <source>Consider file times with specified offset as equal</source> -<target></target> +<target>Traktuje czasy dwóch plików jako równe w określonych granicach</target> <source>Handle daylight saving time</source> -<target></target> +<target>Uwzględniaj przesunięcie czasu</target> <source>Symbolic links:</source> <target>Dowiązania symboliczne:</target> @@ -842,7 +842,7 @@ Komenda jest wykonywana gdy: <target>Maksymalny:</target> <source>C&lear</source> -<target></target> +<target>&Wyczyść</target> <source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> <target>Określ reguły filtrowania w celu wykluczenia niektórych plików z synchronizacji. Ścieżki plików muszą być relatywne do podanych par katalogów.</target> @@ -868,7 +868,7 @@ Komenda jest wykonywana gdy: <target>Usuwanie plików:</target> <source>&Permanent</source> -<target></target> +<target>&Permanentne</target> <source>Delete or overwrite files permanently</source> <target>Usuń lub nadpisz pliki na stałe</target> @@ -880,7 +880,7 @@ Komenda jest wykonywana gdy: <target>Przechowuj pliki, które zostały usunięte lub nadpisane w koszu.</target> <source>&Versioning</source> -<target></target> +<target>&Wersjonowanie</target> <source>Move files to a user-defined folder</source> <target>Przenieś pliki do katalogu zdefiniowanego przez użytkownika</target> @@ -895,7 +895,7 @@ Komenda jest wykonywana gdy: <target>Ukryj wszystkie informacje błędach i ostrzeżeniach</target> <source>&Pop-up</source> -<target></target> +<target>&Pop-up</target> <source>Show pop-up on errors or warnings</source> <target>Pokazuj okna pop-up dla błędów i ostrzeżeń</target> @@ -931,7 +931,7 @@ Komenda jest wykonywana gdy: <target>Minimalizuj do obszaru powiadomień</target> <source>Bytes copied:</source> -<target></target> +<target>Skopiowano bajtów:</target> <source>Close</source> <target>Zamknij</target> @@ -946,7 +946,7 @@ Komenda jest wykonywana gdy: <target>Utwórz plik wsadowy do zautomatyzowania procesu synchronizacji. Aby rozpocząć, klknij dwa razy plik lub zaplanuj zadanie w harmonogramie zadań: %x</target> <source>&Stop</source> -<target></target> +<target>&Stop</target> <source>Stop synchronization at first error</source> <target>Przerwij synchronizację przy pierwszym błędzie</target> @@ -1015,10 +1015,10 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp <target>Opis</target> <source>Show hidden dialogs again</source> -<target></target> +<target>Przywróć ukryte dialogi</target> <source>Show all permanently hidden dialogs and warning messages again</source> -<target></target> +<target>Przywróć wszystkie, stale ukryte dialogi i powiadomienia.</target> <source>&Default</source> <target>&Domyślne</target> @@ -1081,7 +1081,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp <target>Główny pasek</target> <source>Start comparison</source> -<target></target> +<target>Rozpocznij porównywanie</target> <source>Comparison settings</source> <target>Ustawienia porównywania</target> @@ -1211,7 +1211,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp <target>Ustawienia synchronizacji</target> <source>Clear filter</source> -<target></target> +<target>Wyczyść filtr</target> <source>Show files that exist on left side only</source> <target>Pokaż pliki istniejące tylko po lewej stronie</target> @@ -1372,10 +1372,10 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp <target>- Odpowiednik %item_folder% po drugiej stronie</target> <source>Show hidden dialogs and warning messages again?</source> -<target></target> +<target>Przywrócić ukryte dialogi i powiadomienia?</target> <source>&Show</source> -<target></target> +<target>&Przywróć</target> <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> <target>Wyszukaj oraz zastosuj zmiany po obu stronach. Wszystkie operacje na plikach takie jak usunięcia, zmiany oraz konflikty wykrywane są automatycznie przy użyciu bazy danych.</target> diff --git a/FreeFileSync/Build/Languages/turkish.lng b/FreeFileSync/Build/Languages/turkish.lng index 076ebc56..3dc44c4c 100644 --- a/FreeFileSync/Build/Languages/turkish.lng +++ b/FreeFileSync/Build/Languages/turkish.lng @@ -107,7 +107,7 @@ <target>Karşıdaki klasör boş olarak kabul edilecek.</target> <source>The following folder paths are dependent from each other:</source> -<target></target> +<target>Aşağıdaki klasör yolları birbirine bağlı:</target> <source>File %x has an invalid date.</source> <target>%x dosyasının tarihi geçersiz.</target> @@ -217,6 +217,9 @@ <source>%x GB</source> <target>%x GB</target> +<source>Cannot load file %x.</source> +<target>%x dosyası yüklenemedi.</target> + <source>Database file %x is incompatible.</source> <target>%x veritabanı dosyası uyumsuz</target> @@ -319,9 +322,6 @@ <source>Please use FreeFileSync 64-bit version to create shadow copies on this system.</source> <target>Bu sistem üzerinde gölge kopyalar oluşturmak için FreeFileSync 64-bit sürümünü kullanın.</target> -<source>Cannot load file %x.</source> -<target>%x dosyası yüklenemedi.</target> - <source>Cannot determine volume name for %x.</source> <target>%x için birim adı belirlenemedi.</target> @@ -344,7 +344,7 @@ <target>Çı&kın</target> <source>&File</source> -<target></target> +<target>&Dosya</target> <source>&View help</source> <target>&Yardıma bakın</target> @@ -498,21 +498,21 @@ Komut şu durumlarda yürütülür: <source>Data verification error: %x and %y have different content.</source> <target>Veri doğrulama hatası: %x ve %y farklı içeriklere sahip.</target> -<source>Cannot find folder %x.</source> -<target></target> - <source>Target folder %x already existing.</source> <target>%x hedef klasörü zaten var.</target> +<source>Cannot find folder %x.</source> +<target>%x klasörü bulunamadı.</target> + <source>Target folder input field must not be empty.</source> <target>Hedef klasör giriş alanı boş olmamalı.</target> -<source>Please enter a target folder for versioning.</source> -<target>Sürüm izlemesinde kullanılacak bir hedef klasör yazın.</target> - <source>Source folder %x not found.</source> <target>%x kaynak klasörü bulunamadı.</target> +<source>Please enter a target folder for versioning.</source> +<target>Sürüm izlemesinde kullanılacak bir hedef klasör yazın.</target> + <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Uyuşmazlığı çözülmemiş şu ögeler eşleştirilmeyecek:</target> @@ -529,7 +529,7 @@ Komut şu durumlarda yürütülür: <target>Kullanılabilir:</target> <source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source> -<target></target> +<target>Birden çok klasör çifti aynı alt klasöre yazıyor. Lütfen ayarlarınızı gözden geçirin.</target> <source>Synchronizing folder pair:</source> <target>Eşleştirilen klasör çifti:</target> @@ -631,7 +631,7 @@ Komut şu durumlarda yürütülür: <target>Ad</target> <source>Relative folder</source> -<target></target> +<target>Bağıl klasör</target> <source>Base folder</source> <target>Başlangıç klasörü</target> @@ -673,7 +673,7 @@ Komut şu durumlarda yürütülür: <target>Yerel ayarları silin</target> <source>Clear local filter</source> -<target></target> +<target>Yerel süzgeci temizleyin</target> <source>Copy</source> <target>Kopyalayın</target> @@ -694,10 +694,10 @@ Komut şu durumlarda yürütülür: <target>&Toplu iş olarak kaydedin...</target> <source>Start &comparison</source> -<target></target> +<target>&Karşılaştırmayı başlatın</target> <source>Start &synchronization</source> -<target></target> +<target>&Eşleştirmeyi başlatın</target> <source>&Options</source> <target>A&yarlar</target> @@ -709,7 +709,7 @@ Komut şu durumlarda yürütülür: <target>A&rayın...</target> <source>&Reset layout</source> -<target></target> +<target>Düzeni sıfı&rlayın</target> <source>&Export file list...</source> <target>Dosya list&esini verin...</target> @@ -754,7 +754,7 @@ Komut şu durumlarda yürütülür: <target>Büyük-küçük harf uysun</target> <source>New</source> -<target></target> +<target>Ekleyin</target> <source>Open...</source> <target>Açın...</target> @@ -799,13 +799,13 @@ Komut şu durumlarda yürütülür: <target>Dosyaların aynı olup olmadığı, içeriklerine göre belirlensin.</target> <source>Ignore time shift (in hours)</source> -<target></target> +<target>Yoksayılacak zaman kayması (saat olarak)</target> <source>Consider file times with specified offset as equal</source> -<target></target> +<target>Belirtilen saat kadar zaman farkı yoksayılır</target> <source>Handle daylight saving time</source> -<target></target> +<target>Yaz saati hesaba katılsın</target> <source>Symbolic links:</source> <target>Sembolik bağlantılar:</target> @@ -838,7 +838,7 @@ Komut şu durumlarda yürütülür: <target>En büyük:</target> <source>C&lear</source> -<target></target> +<target>&Temizleyin</target> <source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> <target>Eşleştirmeye katılmayacak dosyaların süzülme kurallarını belirleyin. Dosya yollarını bulundukları klasör çiftine göre yazın.</target> @@ -864,7 +864,7 @@ Komut şu durumlarda yürütülür: <target>Dosya silme işlemi:</target> <source>&Permanent</source> -<target></target> +<target>&Kalıcı</target> <source>Delete or overwrite files permanently</source> <target>Dosyalar kalıcı olarak silinir ya da üzerine yazılır</target> @@ -876,7 +876,7 @@ Komut şu durumlarda yürütülür: <target>Silinen ya da üzerine yazılan dosyalar geri dönüşüm kutusuna gönderilir</target> <source>&Versioning</source> -<target></target> +<target>&Sürümleme</target> <source>Move files to a user-defined folder</source> <target>Dosyalar kullanıcı tarafından belirtilen bir klasöre taşınır</target> @@ -891,7 +891,7 @@ Komut şu durumlarda yürütülür: <target>Hiçbir hata ve uyarı iletisi görüntülenmez</target> <source>&Pop-up</source> -<target></target> +<target>Açılan &Pencere</target> <source>Show pop-up on errors or warnings</source> <target>Hata ya da uyarılar açılır pencerede görüntülenir</target> @@ -927,7 +927,7 @@ Komut şu durumlarda yürütülür: <target>Bildirim alanına küçültün</target> <source>Bytes copied:</source> -<target></target> +<target>Kopyalanan bayt:</target> <source>Close</source> <target>Kapatın</target> @@ -942,7 +942,7 @@ Komut şu durumlarda yürütülür: <target>Hiç bir soru sorulmadan eşleştirme yapılması için bir toplu iş dosyası oluşturun. İşlemi başlatmak için bu dosyaya çift tıklayın ya da bir görev zamanlayıcıya şu şekilde ekleyin: %x</target> <source>&Stop</source> -<target></target> +<target>&Durdurun</target> <source>Stop synchronization at first error</source> <target>Oluşacak ilk hatada eşleştirme durdurulur</target> @@ -1011,10 +1011,10 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Açıklama</target> <source>Show hidden dialogs again</source> -<target></target> +<target>Gizlenen iletiler yeniden görüntülensin</target> <source>Show all permanently hidden dialogs and warning messages again</source> -<target></target> +<target>Gizlenen tüm ileti ve uyarılar yeniden görüntülensin</target> <source>&Default</source> <target>&Varsayılan</target> @@ -1077,7 +1077,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Ana Çubuk</target> <source>Start comparison</source> -<target></target> +<target>Karşılaştırmayı başlatın</target> <source>Comparison settings</source> <target>Karşılaştırma ayarları</target> @@ -1203,7 +1203,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Eşleştirme Ayarları</target> <source>Clear filter</source> -<target></target> +<target>Süzgeci temizleyin</target> <source>Show files that exist on left side only</source> <target>Yalnız sol tarafta bulunan dosyaları görüntüler ya da gizler</target> @@ -1362,10 +1362,10 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>%item_folder% ögesinin diğer taraftaki karşılığı</target> <source>Show hidden dialogs and warning messages again?</source> -<target></target> +<target>Gizlenen tüm ileti ve uyarılar yeniden görüntülensin mi?</target> <source>&Show</source> -<target></target> +<target>&Görüntülensin</target> <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> <target>İki tarafta da değişikliklere bakılır ve karşılıklı kopyalanır. Silinmiş, taşınmış ve çakışan ögeler, veritabanı kullanılarak kendiliğinden algılanır.</target> diff --git a/FreeFileSync/Source/RealtimeSync/application.cpp b/FreeFileSync/Source/RealtimeSync/application.cpp index 20195baf..068a7cef 100644 --- a/FreeFileSync/Source/RealtimeSync/application.cpp +++ b/FreeFileSync/Source/RealtimeSync/application.cpp @@ -70,8 +70,8 @@ bool Application::OnInit() //SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application." ::SetErrorMode(SEM_FAILCRITICALERRORS); - setAppUserModeId(L"RealtimeSync", L"SourceForge.RealtimeSync"); //noexcept - //consider: RealtimeSync.exe, RealtimeSync_Win32.exe, RealtimeSync_x64.exe + setAppUserModeId(L"RealtimeSync", L"SourceForge.RealtimeSync"); //noexcept + //consider: RealtimeSync.exe, RealtimeSync_Win32.exe, RealtimeSync_x64.exe wxToolTip::SetMaxWidth(-1); //disable tooltip wrapping -> Windows only diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 6a3a095d..9056d962 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -155,8 +155,8 @@ bool Application::OnInit() //SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application." ::SetErrorMode(SEM_FAILCRITICALERRORS); - setAppUserModeId(L"FreeFileSync", L"SourceForge.FreeFileSync"); //noexcept - //consider: FreeFileSync.exe, FreeFileSync_Win32.exe, FreeFileSync_x64.exe + setAppUserModeId(L"FreeFileSync", L"SourceForge.FreeFileSync"); //noexcept + //consider: FreeFileSync.exe, FreeFileSync_Win32.exe, FreeFileSync_x64.exe wxToolTip::SetMaxWidth(-1); //disable tooltip wrapping -> Windows only diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp index 821976f5..1d22f455 100644 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/lib/dir_lock.cpp @@ -496,7 +496,7 @@ void waitOnDirLock(const Zstring& lockfilepath, DirLockCallback* callback) //thr if (dist(lastLifeSign, now) / TICKS_PER_SEC > EMIT_LIFE_SIGN_INTERVAL) { const int remainingSeconds = std::max<int>(0, DETECT_ABANDONED_INTERVAL - dist(lastLifeSign, getTicks()) / TICKS_PER_SEC); - const std::wstring remSecMsg = replaceCpy(_P("1 sec", "%x sec", remainingSeconds), L"%x", numberTo<std::wstring>(remainingSeconds)); + const std::wstring remSecMsg = replaceCpy(_P("1 sec", "%x sec", remainingSeconds), L"%x", toGuiString(remainingSeconds)); callback->reportStatus(infoMsg + L" | " + _("Detecting abandoned lock...") + L' ' + remSecMsg); } else diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/lib/icon_buffer.cpp index c9f60ad9..73fe47d1 100644 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/lib/icon_buffer.cpp @@ -619,11 +619,11 @@ void WorkerThread::operator()() //thread entry //Prerequisites, see thumbnail.h //1. Initialize COM - if (FAILED(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) - { - assert(false); - return; - } + if (FAILED(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) + { + assert(false); + return; + } ZEN_ON_SCOPE_EXIT(::CoUninitialize()); //2. Initialize system image list diff --git a/FreeFileSync/Source/lib/osx_file_icon.h b/FreeFileSync/Source/lib/osx_file_icon.h new file mode 100644 index 00000000..5edfd740 --- /dev/null +++ b/FreeFileSync/Source/lib/osx_file_icon.h @@ -0,0 +1,36 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef OSX_FILE_ICON_8427508422345342 +#define OSX_FILE_ICON_8427508422345342 + +#include <vector> +#include <zen/sys_error.h> + +namespace osx +{ +struct ImageData +{ + ImageData(int w, int h) : width(w), height(h), rgb(w* h * 3), alpha(w* h) {} + ImageData(ImageData&& tmp) : width(tmp.width), height(tmp.height) { rgb.swap(tmp.rgb); alpha.swap(tmp.alpha); } + + const int width; + const int height; + std::vector<unsigned char> rgb; //rgb-byte order for use with wxImage + std::vector<unsigned char> alpha; + +private: + ImageData(const ImageData&); + ImageData& operator=(const ImageData&); +}; + +ImageData getThumbnail(const char* filename, int requestedSize); //throw SysError +ImageData getFileIcon (const char* filename, int requestedSize); //throw SysError +ImageData getDefaultFileIcon (int requestedSize); //throw SysError +ImageData getDefaultFolderIcon(int requestedSize); //throw SysError +} + +#endif //OSX_FILE_ICON_8427508422345342 diff --git a/FreeFileSync/Source/lib/osx_file_icon.mm b/FreeFileSync/Source/lib/osx_file_icon.mm new file mode 100644 index 00000000..e6b384d3 --- /dev/null +++ b/FreeFileSync/Source/lib/osx_file_icon.mm @@ -0,0 +1,179 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "osx_file_icon.h" +#include <zen/osx_throw_exception.h> +#include <zen/scope_guard.h> +#include <zen/basic_math.h> + +namespace +{ +osx::ImageData extractBytes(NSImage* nsImg, int requestedSize) //throw SysError; NSException? +{ + /* + wxBitmap(NSImage*) is not good enough: it calls "[NSBitmapImageRep imageRepWithData:[img TIFFRepresentation]]" + => inefficient: TIFFRepresentation converts all contained images of an NSImage + => lacking: imageRepWithData extracts the first contained image only! + => wxBitmap(NSImage*) is wxCocoa only, deprecated! + => wxWidgets generally is not thread-safe so care must be taken to use wxBitmap from main thread only! (e.g. race-condition on non-atomic ref-count!!!) + + -> we need only a single bitmap at a specific size => extract raw bytes for use with wxImage in a thread-safe way! + */ + + //we choose the Core Graphics solution; for the equivalent App-Kit way see: http://www.cocoabuilder.com/archive/cocoa/193131-is-lockfocus-main-thread-specific.html#193191 + + ZEN_OSX_ASSERT(requestedSize > 0); + NSRect rectProposed = NSMakeRect(0, 0, requestedSize, requestedSize); //this is merely a hint! + + CGImageRef imgRef = [nsImg CGImageForProposedRect:&rectProposed context:nil hints:nil]; + ZEN_OSX_ASSERT(imgRef != NULL); //can this fail? not documented; ownership?? not documented! + + const size_t width = ::CGImageGetWidth (imgRef); + const size_t height = ::CGImageGetHeight(imgRef); + + ZEN_OSX_ASSERT(width > 0 && height > 0 && requestedSize > 0); + + int trgWidth = width; + int trgHeight = height; + + const int maxExtent = std::max(width, height); //don't stretch small images, but shrink large ones instead! + if (requestedSize < maxExtent) + { + trgWidth = width * requestedSize / maxExtent; + trgHeight = height * requestedSize / maxExtent; + } + + CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB(); + ZEN_OSX_ASSERT(colorSpace != NULL); //may fail + ZEN_ON_SCOPE_EXIT(::CGColorSpaceRelease(colorSpace)); + + std::vector<unsigned char> buf(trgWidth* trgHeight * 4); //32-bit ARGB, little endian byte order -> already initialized with 0 = fully transparent + + //supported color spaces: https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB + CGContextRef ctxRef = ::CGBitmapContextCreate(&buf[0], //void *data, + trgWidth, //size_t width, + trgHeight, //size_t height, + 8, //size_t bitsPerComponent, + 4 * trgWidth, //size_t bytesPerRow, + colorSpace, //CGColorSpaceRef colorspace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); //CGBitmapInfo bitmapInfo + ZEN_OSX_ASSERT(ctxRef != NULL); + ZEN_ON_SCOPE_EXIT(::CGContextRelease(ctxRef)); + + ::CGContextDrawImage(ctxRef, CGRectMake(0, 0, trgWidth, trgHeight), imgRef); //can this fail? not documented + + //::CGContextFlush(ctxRef); //"If you pass [...] a bitmap context, this function does nothing." + + osx::ImageData imgOut(trgWidth, trgHeight); + + auto it = buf.begin(); + auto itOutRgb = imgOut.rgb.begin(); + auto itOutAlpha = imgOut.alpha.begin(); + for (int i = 0; i < trgWidth * trgHeight; ++i) + { + const unsigned char b = *it++; + const unsigned char g = *it++; + const unsigned char r = *it++; + const unsigned char a = *it++; + + //unsigned arithmetics caveat! + auto demultiplex = [&](unsigned char c) { return static_cast<unsigned char>(numeric::clampCpy(a == 0 ? 0 : (c * 255 + a - 1) / a, 0, 255)); }; //=ceil(c * 255 / a) + + *itOutRgb++ = demultiplex(r); + *itOutRgb++ = demultiplex(g); + *itOutRgb++ = demultiplex(b); + *itOutAlpha++ = a; + } + + return imgOut; +} +} + + +osx::ImageData osx::getThumbnail(const char* filename, int requestedSize) //throw SysError +{ + @try + { + @autoreleasepool + { + NSString* nsFile = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding]; + ZEN_OSX_ASSERT(nsFile != nil); //throw SysError; can this fail? not documented + //stringWithCString returns string which is already set to autorelease! + + NSImage* nsImg = [[[NSImage alloc] initWithContentsOfFile:nsFile] autorelease]; + ZEN_OSX_ASSERT(nsImg != nil); //may fail + + return extractBytes(nsImg, requestedSize); //throw SysError + } + } + @catch(NSException* e) + { + throwSysError(e); //throw SysError + } +} + + +osx::ImageData osx::getFileIcon(const char* filename, int requestedSize) //throw SysError +{ + @try + { + @autoreleasepool + { + NSString* nsFile = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding]; + ZEN_OSX_ASSERT(nsFile != nil); //throw SysError; can this fail? not documented + //stringWithCString returns string which is already set to autorelease! + + NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFile:nsFile]; + ZEN_OSX_ASSERT(nsImg != nil); //can this fail? not documented + + return extractBytes(nsImg, requestedSize); //throw SysError + } + } + @catch (NSException* e) + { + throwSysError(e); //throw SysError + } +} + + +osx::ImageData osx::getDefaultFileIcon(int requestedSize) //throw SysError +{ + @try + { + @autoreleasepool + { + NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)]; + //NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:@"dat"]; + ZEN_OSX_ASSERT(nsImg != nil); //can this fail? not documented + + return extractBytes(nsImg, requestedSize); //throw SysError + } + } + @catch (NSException* e) + { + throwSysError(e); //throw SysError + } +} + + +osx::ImageData osx::getDefaultFolderIcon(int requestedSize) //throw SysError +{ + @try + { + @autoreleasepool + { + NSImage* nsImg = [NSImage imageNamed:NSImageNameFolder]; + //NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)]; + ZEN_OSX_ASSERT(nsImg != nil); //may fail + + return extractBytes(nsImg, requestedSize); //throw SysError + } + } + @catch (NSException* e) + { + throwSysError(e); //throw SysError + } +} diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/lib/process_xml.h index 394b3f65..6d834922 100644 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/lib/process_xml.h @@ -135,7 +135,7 @@ struct XmlGlobalSettings XmlGlobalSettings() : programLanguage(zen::retrieveSystemLanguage()), failsafeFileCopy(true), - copyLockedFiles(true), + copyLockedFiles(false), //safer default: avoid copies of partially written files copyFilePermissions(false), automaticRetryCount(0), automaticRetryDelay(5), diff --git a/FreeFileSync/Source/lib/shadow.cpp b/FreeFileSync/Source/lib/shadow.cpp new file mode 100644 index 00000000..be9ba1f3 --- /dev/null +++ b/FreeFileSync/Source/lib/shadow.cpp @@ -0,0 +1,128 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "shadow.h" +#include <stdexcept> +#include <zen/win.h> //includes "windows.h" +#include <zen/dll.h> +#include <zen/win_ver.h> +#include <zen/assert_static.h> +#include <zen/long_path_prefix.h> +#include <zen/symlink_target.h> +#include "../dll/ShadowCopy/shadow.h" +#include <zen/scope_guard.h> + +using namespace zen; +using namespace shadow; + + +namespace +{ +bool runningWOW64() //test if process is running under WOW64 (reference http://msdn.microsoft.com/en-us/library/ms684139(VS.85).aspx) +{ + typedef BOOL (WINAPI* IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process); + + const SysDllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process"); + if (isWow64Process) + { + BOOL isWow64 = FALSE; + if (isWow64Process(::GetCurrentProcess(), &isWow64)) + return isWow64 != FALSE; + } + return false; +} +} + +//############################################################################################################# + +class ShadowCopy::ShadowVolume +{ +public: + ShadowVolume(const Zstring& volumeNamePf) : //throw FileError + createShadowCopy (getDllName(), funName_createShadowCopy), + releaseShadowCopy (getDllName(), funName_releaseShadowCopy), + getShadowVolume (getDllName(), funName_getShadowVolume), + getLastErrorMessage(getDllName(), funName_getLastErrorMessage), + backupHandle(nullptr) + { + //VSS does not support running under WOW64 except for Windows XP and Windows Server 2003 + //reference: http://msdn.microsoft.com/en-us/library/aa384627(VS.85).aspx + if (runningWOW64()) + throw FileError(_("Cannot access the Volume Shadow Copy Service."), + _("Please use FreeFileSync 64-bit version to create shadow copies on this system.")); + + //check if shadow copy dll was loaded correctly + if (!createShadowCopy || !releaseShadowCopy || !getShadowVolume || !getLastErrorMessage) + throw FileError(_("Cannot access the Volume Shadow Copy Service."), + replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName()))); + + //--------------------------------------------------------------------------------------------------------- + //start volume shadow copy service: + backupHandle = createShadowCopy(volumeNamePf.c_str()); + if (!backupHandle) + throw FileError(_("Cannot access the Volume Shadow Copy Service."), getLastErrorMessage() + std::wstring(L" Volume: ") + fmtFileName(volumeNamePf)); + + shadowVolPf = appendSeparator(getShadowVolume(backupHandle)); //shadowVolName NEVER has a trailing backslash + } + + ~ShadowVolume() { releaseShadowCopy(backupHandle); } //fast! no performance optimization necessary + + Zstring geNamePf() const { return shadowVolPf; } //with trailing path separator + +private: + ShadowVolume (const ShadowVolume&) = delete; + ShadowVolume& operator=(const ShadowVolume&) = delete; + + const DllFun<FunType_createShadowCopy > createShadowCopy; + const DllFun<FunType_releaseShadowCopy > releaseShadowCopy; + const DllFun<FunType_getShadowVolume > getShadowVolume; + const DllFun<FunType_getLastErrorMessage> getLastErrorMessage; + + Zstring shadowVolPf; + ShadowHandle backupHandle; +}; + +//############################################################################################################# + +Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile, const std::function<void(const Zstring& volumeNamePf)>& onBeforeMakeVolumeCopy) +{ + Zstring filepathFinal = inputFile; + + //try to resolve symlinks and junctions: + //1. symlinks: we need to retrieve the target path, else we would just return a symlink on a VSS volume while the target outside were still locked! + //2. junctions: C:\Users\<username> is a junction that may link to e.g. D:\Users\<username>, so GetVolumePathName() returns "D:\" => "Volume name %x not part of file name %y." + if (vistaOrLater()) + filepathFinal = getResolvedFilePath(inputFile); //throw FileError; requires Vista or later! + //-> returns paths with \\?\ prefix! => make sure to avoid duplicate shadow copies for volume paths with/without prefix + + DWORD bufferSize = 10000; + std::vector<wchar_t> volBuffer(bufferSize); + if (!::GetVolumePathName(filepathFinal.c_str(), //__in LPCTSTR lpszFileName, + &volBuffer[0], //__out LPTSTR lpszVolumePathName, + bufferSize)) //__in DWORD cchBufferLength + throwFileError(replaceCpy(_("Cannot determine volume name for %x."), L"%x", fmtFileName(filepathFinal)), L"GetVolumePathName", ::GetLastError()); + + const Zstring volumeNamePf = appendSeparator(&volBuffer[0]); //msdn: if buffer is 1 char too short, GetVolumePathName() may skip last separator without error! + + //input file is always absolute! directory formatting takes care of this! Therefore volume name can always be found. + const size_t pos = filepathFinal.find(volumeNamePf); //filepathFinal needs NOT to begin with volumeNamePf: consider \\?\ prefix! + if (pos == Zstring::npos) + throw FileError(replaceCpy(replaceCpy(_("Volume name %x is not part of file path %y."), + L"%x", fmtFileName(volumeNamePf)), + L"%y", fmtFileName(filepathFinal))); + + //get or create instance of shadow volume + auto it = shadowVol.find(volumeNamePf); + if (it == shadowVol.end()) + { + onBeforeMakeVolumeCopy(volumeNamePf); //notify client before (potentially) blocking some time + auto newEntry = std::make_shared<ShadowVolume>(volumeNamePf); //throw FileError + it = shadowVol.insert(std::make_pair(volumeNamePf, newEntry)).first; + } + + //return filepath alias on shadow copy volume + return it->second->geNamePf() + Zstring(filepathFinal.c_str() + pos + volumeNamePf.length()); +} diff --git a/FreeFileSync/Source/lib/shadow.h b/FreeFileSync/Source/lib/shadow.h new file mode 100644 index 00000000..4f2dbf32 --- /dev/null +++ b/FreeFileSync/Source/lib/shadow.h @@ -0,0 +1,36 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef SHADOW_H_INCLUDED +#define SHADOW_H_INCLUDED + +#include <map> +#include <memory> +#include <functional> +#include <zen/zstring.h> +#include <zen/file_error.h> + +namespace shadow +{ +class ShadowCopy //take and buffer Windows Volume Shadow Copy snapshots as needed +{ +public: + ShadowCopy() {} + + //return filepath on shadow copy volume - follows symlinks! + Zstring makeShadowCopy(const Zstring& inputFile, const std::function<void(const Zstring& volumeNamePf)>& onBeforeMakeVolumeCopy); //throw FileError + +private: + ShadowCopy (const ShadowCopy&) = delete; + ShadowCopy& operator=(const ShadowCopy&) = delete; + + class ShadowVolume; + typedef std::map<Zstring, std::shared_ptr<ShadowVolume>, LessFilename> VolNameShadowMap; + VolNameShadowMap shadowVol; +}; +} + +#endif //SHADOW_H_INCLUDED diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp index af37ffc8..2c3af007 100644 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/synchronization.cpp @@ -1898,19 +1898,6 @@ void SynchronizeFolderPair::verifyFileCopy(const Zstring& source, const Zstring& //########################################################################################### -/* -struct LessDependentDirectory : public std::binary_function<Zstring, Zstring, bool> -{ --> a *very* bad idea: this is NOT a strict weak ordering! No transitivity of equivalence! - - bool operator()(const Zstring& lhs, const Zstring& rhs) const - { - return LessFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())), - Zstring(rhs.c_str(), std::min(lhs.length(), rhs.length()))); - } -}; -*/ - template <SelectedSide side> //create base directories first (if not yet existing) -> no symlink or attribute copying! bool createBaseDirectory(BaseDirPair& baseDirObj, ProcessCallback& callback) //nothrow; return false if fatal error occurred { @@ -1918,18 +1905,7 @@ bool createBaseDirectory(BaseDirPair& baseDirObj, ProcessCallback& callback) //n if (dirpath.empty()) return true; - if (baseDirObj.isExisting<side>()) //atomicity: do NOT check directory existence again! - { - //just convenience: exit sync right here instead of showing tons of error messages during file copy - zen::Opt<std::wstring> errMsg = tryReportingError([&] - { - if (!dirExistsUpdating(dirpath, false, callback)) - throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(dirpath))); //should be logged as a "fatal error" if ignored by the user... - }, callback); //may throw in error-callback! - - return !errMsg; - } - else //create target directory: user presumably ignored error "dir existing" in order to have it created automatically + if (!baseDirObj.isExisting<side>()) //create target directory: user presumably ignored error "dir existing" in order to have it created automatically { bool temporaryNetworkDrop = false; zen::Opt<std::wstring> errMsg = tryReportingError([&] @@ -1942,7 +1918,7 @@ bool createBaseDirectory(BaseDirPair& baseDirObj, ProcessCallback& callback) //n } catch (const ErrorTargetExisting&) { - //TEMPORARY network drop: base directory not found during comparison, but reappears during synchronization + //TEMPORARY network drop! base directory not found during comparison, but reappears during synchronization //=> sync-directions are based on false assumptions! Abort. callback.reportFatalError(replaceCpy(_("Target folder %x already existing."), L"%x", fmtFileName(dirpath))); temporaryNetworkDrop = true; @@ -1955,7 +1931,16 @@ bool createBaseDirectory(BaseDirPair& baseDirObj, ProcessCallback& callback) //n }, callback); //may throw in error-callback! return !errMsg && !temporaryNetworkDrop; } + + return true; } + +struct ReadWriteCount +{ + ReadWriteCount() : reads(), writes() {} + size_t reads; + size_t writes; +}; } @@ -2011,6 +1996,19 @@ void zen::synchronize(const TimeComp& timeStamp, //-------------------execute basic checks all at once before starting sync-------------------------------------- + auto dirNotFoundAnymore = [&](const Zstring& baseDirPf, bool wasExisting) + { + if (wasExisting) + if (Opt<std::wstring> errMsg = tryReportingError([&] + { + if (!dirExistsUpdating(baseDirPf, false, callback)) + throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(baseDirPf))); //should be logged as a "fatal error" if ignored by the user... + }, callback)) //may throw in error-callback! + return true; + + return false; + }; + auto dependentDir = [](const Zstring& lhs, const Zstring& rhs) //note: this is NOT an equivalence relation! { return EqualFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())), @@ -2018,20 +2016,24 @@ void zen::synchronize(const TimeComp& timeStamp, }; //aggregate information - std::map<Zstring, std::pair<size_t, size_t>, LessFilename> dirReadWriteCount; //count read/write accesses + std::map<Zstring, ReadWriteCount, LessFilename> dirReadWriteCount; //count read/write accesses + for (auto j = begin(folderCmp); j != end(folderCmp); ++j) + { + dirReadWriteCount[j->getBaseDirPf<LEFT_SIDE >()]; //create all entries first! + dirReadWriteCount[j->getBaseDirPf<RIGHT_SIDE>()]; //=> counting accesses is complex for later inserts! + } + auto incReadCount = [&](const Zstring& baseDir) { - dirReadWriteCount[baseDir]; //create entry for (auto& item : dirReadWriteCount) if (dependentDir(baseDir, item.first)) - ++item.second.first; + ++item.second.reads; }; auto incWriteCount = [&](const Zstring& baseDir) { - dirReadWriteCount[baseDir]; //create entry for (auto& item : dirReadWriteCount) if (dependentDir(baseDir, item.first)) - ++item.second.second; + ++item.second.writes; }; std::vector<std::pair<Zstring, Zstring>> significantDiffPairs; @@ -2047,16 +2049,18 @@ void zen::synchronize(const TimeComp& timeStamp, for (auto j = begin(folderCmp); j != end(folderCmp); ++j) { const size_t folderIndex = j - begin(folderCmp); + const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex]; //exclude some pathological case (leftdir, rightdir are empty) if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) + { + skipFolderPair[folderIndex] = true; continue; + } - const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex]; - + //aggregate basic information const SyncStatistics folderPairStat(*j); - //aggregate basic information const bool writeLeft = folderPairStat.getCreate<LEFT_SIDE>() + folderPairStat.getUpdate<LEFT_SIDE>() + folderPairStat.getDelete<LEFT_SIDE>() > 0; @@ -2073,15 +2077,6 @@ void zen::synchronize(const TimeComp& timeStamp, continue; } - //check empty input fields: this only makes sense if empty field is source (and no DB files need to be created) - if ((j->getBaseDirPf<LEFT_SIDE >().empty() && (writeLeft || folderPairCfg.saveSyncDB_)) || - (j->getBaseDirPf<RIGHT_SIDE>().empty() && (writeRight || folderPairCfg.saveSyncDB_))) - { - callback.reportFatalError(_("Target folder input field must not be empty.")); - skipFolderPair[folderIndex] = true; - continue; - } - //aggregate information of folders used by multiple pairs in read/write access if (!dependentDir(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) //true in general { @@ -2107,45 +2102,60 @@ void zen::synchronize(const TimeComp& timeStamp, incWriteCount(j->getBaseDirPf<LEFT_SIDE>()); } + //check empty input fields: this only makes sense if empty field is source (and no DB files need to be created) + if ((j->getBaseDirPf<LEFT_SIDE >().empty() && (writeLeft || folderPairCfg.saveSyncDB_)) || + (j->getBaseDirPf<RIGHT_SIDE>().empty() && (writeRight || folderPairCfg.saveSyncDB_))) + { + callback.reportFatalError(_("Target folder input field must not be empty.")); + skipFolderPair[folderIndex] = true; + continue; + } - if (folderPairStat.getUpdate() + folderPairStat.getDelete() > 0 && - folderPairCfg.handleDeletion == zen::DELETE_TO_VERSIONING) + //check for network drops after comparison + // - convenience: exit sync right here instead of showing tons of errors during file copy + // - early failure! there's no point in evaluating subsequent warnings + if (dirNotFoundAnymore(j->getBaseDirPf<LEFT_SIDE >(), j->isExisting<LEFT_SIDE >()) || + dirNotFoundAnymore(j->getBaseDirPf<RIGHT_SIDE>(), j->isExisting<RIGHT_SIDE>())) { - //check if user-defined directory for deletion was specified - if (folderPairCfg.versioningFolder.empty()) //already trimmed by getFormattedDirectoryPath() - { - //should never arrive here: already checked in SyncCfgDialog - callback.reportFatalError(_("Please enter a target folder for versioning.")); - skipFolderPair[folderIndex] = true; - continue; - } + skipFolderPair[folderIndex] = true; + continue; } //the following scenario is covered by base directory creation below in case source directory exists (accessible or not), but latter doesn't cover source created after comparison, but before sync!!! - auto checkSourceMissing = [&](const Zstring& baseDirPf, bool wasExisting) -> bool //avoid race-condition: we need to evaluate existence status from time of comparison! + auto sourceDirNotFound = [&](const Zstring& baseDirPf, bool wasExisting) -> bool //avoid race-condition: we need to evaluate existence status from time of comparison! { if (!baseDirPf.empty()) - { //PERMANENT network drop: avoid data loss when source directory is not found AND user chose to ignore errors (else we wouldn't arrive here) - if (folderPairStat.getCreate() + - folderPairStat.getUpdate() == 0 && + if (folderPairStat.getCreate() + folderPairStat.getUpdate() == 0 && folderPairStat.getDelete() > 0) //deletions only... (respect filtered items!) //folderPairStat.getConflict() == 0 && -> there COULD be conflicts for <automatic> if directory existence check fails, but loading sync.ffs_db succeeds - //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3531351&group_id=234430 -> fixed, but still better not consider conflicts - { + //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3531351&group_id=234430 -> fixed, but still better not consider conflicts! if (!wasExisting) //avoid race-condition: we need to evaluate existence status from time of comparison! { callback.reportFatalError(replaceCpy(_("Source folder %x not found."), L"%x", fmtFileName(baseDirPf))); - skipFolderPair[folderIndex] = true; - return false; + return true; } - } - } - return true; + return false; }; - if (!checkSourceMissing(j->getBaseDirPf<LEFT_SIDE >(), j->isExisting<LEFT_SIDE >()) || - !checkSourceMissing(j->getBaseDirPf<RIGHT_SIDE>(), j->isExisting<RIGHT_SIDE>())) + if (sourceDirNotFound(j->getBaseDirPf<LEFT_SIDE >(), j->isExisting<LEFT_SIDE >()) || + sourceDirNotFound(j->getBaseDirPf<RIGHT_SIDE>(), j->isExisting<RIGHT_SIDE>())) + { + skipFolderPair[folderIndex] = true; continue; + } + + //check if user-defined directory for deletion was specified + if (folderPairCfg.handleDeletion == zen::DELETE_TO_VERSIONING && + folderPairStat.getUpdate() + folderPairStat.getDelete() > 0) + { + if (folderPairCfg.versioningFolder.empty()) //already trimmed by getFormattedDirectoryPath() + { + //should not arrive here: already checked in SyncCfgDialog + callback.reportFatalError(_("Please enter a target folder for versioning.")); + skipFolderPair[folderIndex] = true; + continue; + } + } //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted if (significantDifferenceDetected(folderPairStat)) @@ -2172,7 +2182,8 @@ void zen::synchronize(const TimeComp& timeStamp, //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong auto checkRecycler = [&](const Zstring& baseDirPf) { - if (!baseDirPf.empty()) //should be + assert(!baseDirPf.empty()); + if (!baseDirPf.empty()) if (baseDirHasRecycler.find(baseDirPf) == baseDirHasRecycler.end()) //perf: avoid duplicate checks! { callback.reportStatus(replaceCpy(_("Checking recycle bin availability for folder %x..."), L"%x", fmtFileName(baseDirPf), false)); @@ -2255,12 +2266,8 @@ void zen::synchronize(const TimeComp& timeStamp, { std::vector<Zstring> conflictDirs; for (const auto& item : dirReadWriteCount) - { - const std::pair<size_t, size_t>& countRef = item.second; //# read/write accesses - - if (countRef.first + countRef.second >= 2 && countRef.second >= 1) //race condition := multiple accesses of which at least one is a write + if (item.second.reads + item.second.writes >= 2 && item.second.writes >= 1) //race condition := multiple accesses of which at least one is a write conflictDirs.push_back(item.first); - } if (!conflictDirs.empty()) { @@ -2286,8 +2293,10 @@ void zen::synchronize(const TimeComp& timeStamp, //loop through all directory pairs for (auto j = begin(folderCmp); j != end(folderCmp); ++j) { - //exclude pathological cases (e.g. leftdir, rightdir are empty) - if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) + const size_t folderIndex = j - begin(folderCmp); + const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex]; + + if (skipFolderPair[folderIndex]) //folder pairs may be skipped after fatal errors were found continue; //------------------------------------------------------------------------------------------ @@ -2296,16 +2305,15 @@ void zen::synchronize(const TimeComp& timeStamp, L" " + j->getBaseDirPf<RIGHT_SIDE>()); //------------------------------------------------------------------------------------------ - const size_t folderIndex = j - begin(folderCmp); - const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex]; - - if (skipFolderPair[folderIndex]) //folder pairs may be skipped after fatal errors were found + //checking a second time: (a long time may have passed since the intro checks!) + if (dirNotFoundAnymore(j->getBaseDirPf<LEFT_SIDE >(), j->isExisting<LEFT_SIDE >()) || + dirNotFoundAnymore(j->getBaseDirPf<RIGHT_SIDE>(), j->isExisting<RIGHT_SIDE>())) continue; //create base directories first (if not yet existing) -> no symlink or attribute copying! if (!createBaseDirectory<LEFT_SIDE >(*j, callback) || !createBaseDirectory<RIGHT_SIDE>(*j, callback)) - continue; //skip this folder pair + continue; //------------------------------------------------------------------------------------------ //execute synchronization recursively @@ -2338,7 +2346,7 @@ void zen::synchronize(const TimeComp& timeStamp, if (folderPairCfg.handleDeletion == DELETE_TO_RECYCLER) { auto it = baseDirHasRecycler.find(baseDirPf); - if (it != baseDirHasRecycler.end()) + if (it != baseDirHasRecycler.end()) //buffer filled during intro checks (but only if deletions are expected) if (!it->second) return DELETE_PERMANENTLY; //Windows' ::SHFileOperation() will do this anyway, but we have a better and faster deletion routine (e.g. on networks) } @@ -2370,8 +2378,8 @@ void zen::synchronize(const TimeComp& timeStamp, syncFP.startSync(*j); //(try to gracefully) cleanup temporary Recycle bin folders and versioning -> will be done in ~DeletionHandling anyway... - tryReportingError([&] { delHandlerL.tryCleanup(); }, callback); //show error dialog if necessary - tryReportingError([&] { delHandlerR.tryCleanup(); }, callback); // + tryReportingError([&] { delHandlerL.tryCleanup(); /*throw FileError*/}, callback); //show error dialog if necessary + tryReportingError([&] { delHandlerR.tryCleanup(); /*throw FileError*/}, callback); // //(try to gracefully) write database file if (folderPairCfg.saveSyncDB_) @@ -2379,7 +2387,7 @@ void zen::synchronize(const TimeComp& timeStamp, callback.reportStatus(_("Generating database...")); callback.forceUiRefresh(); - tryReportingError([&] { zen::saveLastSynchronousState(*j); }, callback); //throw FileError + tryReportingError([&] { zen::saveLastSynchronousState(*j); /*throw FileError*/ }, callback); guardUpdateDb.dismiss(); } } diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index bf8bf758..b40941fa 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -380,7 +380,7 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er //auto-retry if (retryNumber < automaticRetryCount_) { - errorLog.logMsg(errorMessage + L"\n=> " + + errorLog.logMsg(errorMessage + L"\n-> " + _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", automaticRetryDelay_), TYPE_INFO); //delay const int iterations = static_cast<int>(1000 * automaticRetryDelay_ / UI_UPDATE_INTERVAL); //always round down: don't allow for negative remaining time below @@ -418,7 +418,7 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er case ConfirmationButton3::DONT_DO_IT: //retry guardWriteLog.dismiss(); - errorLog.logMsg(errorMessage + L"\n=> " + _("Retrying operation..."), TYPE_INFO); + errorLog.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); return ProcessCallback::RETRY; case ConfirmationButton3::CANCEL: diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp index c40c5f86..98f9b37f 100644 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/custom_grid.cpp @@ -506,7 +506,7 @@ private: static const int GAP_SIZE = 2; - virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool selected) override + virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override { wxRect rectTmp = rect; @@ -958,20 +958,33 @@ private: virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override { - const FileSystemObject* fsObj = getRawData(row); - GridData::drawCellBackground(dc, rect, enabled, selected, highlightSyncAction_ ? - getBackGroundColorSyncAction(fsObj) : - getBackGroundColorCmpCategory(fsObj)); + if (enabled) + { + if (selected) + dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST); + else + { + if (const FileSystemObject* fsObj = getRawData(row)) + { + if (fsObj->isActive()) + fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0); + else + clearArea(dc, rect, COLOR_NOT_ACTIVE); + } + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + } + } + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); } - virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool selected) override + virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override { - auto drawInactiveColumBackground = [&](const FileSystemObject& fsObj) + auto drawHighlightBackground = [&](const FileSystemObject& fsObj, const wxColor& col) { - if (fsObj.isActive()) - fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0); - else - clearArea(dc, rect, COLOR_NOT_ACTIVE); + if (enabled && !selected && fsObj.isActive()) //coordinate with renderRowBackgound()! + clearArea(dc, rect, col); }; switch (static_cast<ColumnTypeMiddle>(colType)) @@ -979,54 +992,46 @@ private: case COL_TYPE_CHECKBOX: if (const FileSystemObject* fsObj = getRawData(row)) { - wxRect rectInside = rect; - - //if sync action is shown draw notch on left side, right side otherwise - if (notch.GetHeight() != rectInside.GetHeight()) - notch.Rescale(notch.GetWidth(), rectInside.GetHeight()); - if (highlightSyncAction_) - { - drawBitmapRtlMirror(dc, notch, rectInside, wxALIGN_LEFT, buffer); - rectInside.x += notch.GetWidth(); - rectInside.width -= notch.GetWidth(); - } - else - { - //wxWidgets screws up again and has wxALIGN_RIGHT off by one pixel! -> use wxALIGN_LEFT instead - wxRect rectNotch(rectInside.x + rectInside.width - notch.GetWidth(), rectInside.y, - notch.GetWidth(), rectInside.height); - drawBitmapRtlMirror(dc, notch, rectNotch, wxALIGN_LEFT, buffer); - rectInside.width -= notch.GetWidth(); - } - const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->row_ : false; const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->blockPos_ : BLOCKPOS_CHECK_BOX; if (rowHighlighted && highlightBlock == BLOCKPOS_CHECK_BOX) - drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrueFocus" : L"checkboxFalseFocus"), rectInside, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrueFocus" : L"checkboxFalseFocus"), rect, wxALIGN_CENTER, buffer); else //default - drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), rectInside, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), rect, wxALIGN_CENTER, buffer); } break; case COL_TYPE_CMP_CATEGORY: if (const FileSystemObject* fsObj = getRawData(row)) { - if (highlightSyncAction_) - drawInactiveColumBackground(*fsObj); + if (!highlightSyncAction_) + drawHighlightBackground(*fsObj, getBackGroundColorCmpCategory(fsObj)); + + wxRect rectTmp = rect; + { + //draw notch on left side + if (notch.GetHeight() != rectTmp.GetHeight()) + notch.Rescale(notch.GetWidth(), rectTmp.GetHeight()); + + //wxWidgets screws up again and has wxALIGN_RIGHT off by one pixel! -> use wxALIGN_LEFT instead + const wxRect rectNotch(rectTmp.x + rectTmp.width - notch.GetWidth(), rectTmp.y, notch.GetWidth(), rectTmp.height); + drawBitmapRtlMirror(dc, notch, rectNotch, wxALIGN_LEFT, buffer); + rectTmp.width -= notch.GetWidth(); + } if (!highlightSyncAction_) - drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectTmp, wxALIGN_CENTER, buffer); else if (fsObj->getCategory() != FILE_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(fsObj->getCategory())), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(fsObj->getCategory())), rectTmp, wxALIGN_CENTER, buffer); } break; case COL_TYPE_SYNC_ACTION: if (const FileSystemObject* fsObj = getRawData(row)) { - if (!highlightSyncAction_) - drawInactiveColumBackground(*fsObj); + if (highlightSyncAction_) + drawHighlightBackground(*fsObj, getBackGroundColorSyncAction(fsObj)); const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->row_ : false; const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->blockPos_ : BLOCKPOS_CHECK_BOX; @@ -1620,14 +1625,14 @@ void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std //gridLeft .showScrollBars(Grid::SB_SHOW_AUTOMATIC, Grid::SB_SHOW_NEVER); -> redundant: configuration happens in GridEventManager::onAlignScrollBars() //gridCenter.showScrollBars(Grid::SB_SHOW_NEVER, Grid::SB_SHOW_NEVER); - const int widthCategory = 30; const int widthCheckbox = getResourceImage(L"checkboxTrue").GetWidth() + 4 + getResourceImage(L"notch").GetWidth(); + const int widthCategory = 30; const int widthAction = 45; gridCenter.SetSize(widthCategory + widthCheckbox + widthAction, -1); std::vector<Grid::ColumnAttribute> attribMiddle; - attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_CMP_CATEGORY), widthCategory, 0, true)); attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_CHECKBOX ), widthCheckbox, 0, true)); + attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_CMP_CATEGORY), widthCategory, 0, true)); attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_SYNC_ACTION ), widthAction, 0, true)); gridCenter.setColumnConfig(attribMiddle); } diff --git a/FreeFileSync/Source/ui/dir_name.cpp b/FreeFileSync/Source/ui/dir_name.cpp index 4926985c..ea8a9386 100644 --- a/FreeFileSync/Source/ui/dir_name.cpp +++ b/FreeFileSync/Source/ui/dir_name.cpp @@ -221,7 +221,7 @@ void DirectoryName<NameControl>::onSelectDir(wxCommandEvent& event) '\x8d', '\xc2', '\xc', '\xa5', '\xef', '\x59', '\x6e', '\x3b' }; //some random GUID => have Windows save IFileDialog state separately from other file/dir pickers! - showFolderPicker(static_cast<HWND>(selectButton_.GetHWND()), //in; ==HWND + showFolderPicker(static_cast<HWND>(selectButton_.GetHWND()), //in; ==HWND defaultdirpath.empty() ? static_cast<const wchar_t*>(nullptr) : defaultdirpath.c_str(), //in, optional! &guid, selectedFolder, //out: call freeString() after use! diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index cb6ecf25..de75eb3f 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -385,7 +385,7 @@ ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& err //auto-retry if (retryNumber < automaticRetryCount_) { - errorLog.logMsg(errorMessage + L"\n=> " + + errorLog.logMsg(errorMessage + L"\n-> " + _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", automaticRetryDelay_), TYPE_INFO); //delay const int iterations = static_cast<int>(1000 * automaticRetryDelay_ / UI_UPDATE_INTERVAL); //always round down: don't allow for negative remaining time below @@ -423,7 +423,7 @@ ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& err case ConfirmationButton3::DONT_DO_IT: //retry guardWriteLog.dismiss(); - errorLog.logMsg(errorMessage + L"\n=> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! + errorLog.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! return ProcessCallback::RETRY; case ConfirmationButton3::CANCEL: diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index e7f99fdf..69aa6fb6 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -474,7 +474,8 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, manualTimeSpanTo (0), folderHistoryLeft (std::make_shared<FolderHistory>()), //make sure it is always bound folderHistoryRight(std::make_shared<FolderHistory>()), // - focusWindowAfterSearch(nullptr) + focusWindowAfterSearch(nullptr), + localKeyEventsEnabled(true) { m_directoryLeft ->init(folderHistoryLeft); m_directoryRight->init(folderHistoryRight); @@ -1547,6 +1548,8 @@ void MainDialog::disableAllElements(bool enableAbort) //OS X: wxWidgets portability promise is again a mess: http://wxwidgets.10942.n7.nabble.com/Disable-panel-and-appropriate-children-windows-linux-macos-td35357.html + localKeyEventsEnabled = false; + m_menubar1->EnableTop(0, false); m_menubar1->EnableTop(1, false); m_menubar1->EnableTop(2, false); @@ -1559,6 +1562,7 @@ void MainDialog::disableAllElements(bool enableAbort) m_panelViewFilter ->Disable(); m_panelConfig ->Disable(); m_gridNavi ->Disable(); + m_panelSearch ->Disable(); if (enableAbort) { @@ -1582,6 +1586,8 @@ void MainDialog::enableAllElements() EnableCloseButton(true); + localKeyEventsEnabled = true; + m_menubar1->EnableTop(0, true); m_menubar1->EnableTop(1, true); m_menubar1->EnableTop(2, true); @@ -1594,6 +1600,7 @@ void MainDialog::enableAllElements() m_panelViewFilter ->Enable(); m_panelConfig ->Enable(); m_gridNavi ->Enable(); + m_panelSearch ->Enable(); //show compare button m_buttonCancel->Disable(); @@ -1845,6 +1852,12 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without explicit menu entry :) { + if (!localKeyEventsEnabled) + { + event.Skip(); + return; + } + const int keyCode = event.GetKeyCode(); //CTRL + X @@ -1926,7 +1939,7 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without m_gridMainL->SetFocus(); event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK! - evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it... + evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but code in key_event.h prevents it... event.Skip(false); //definitively handled now! return; } @@ -4055,7 +4068,7 @@ void MainDialog::OnSearchPanelKeyPressed(wxKeyEvent& event) switch (event.GetKeyCode()) { case WXK_RETURN: - case WXK_NUMPAD_ENTER: //catches ENTER keys while focus is on *any* part of m_panelSearch! Seems to obsolete OnHideSearchPanel()! + case WXK_NUMPAD_ENTER: //catches ENTER keys while focus is on *any* part of m_panelSearch! Seems to obsolete OnSearchGridEnter()! startFindNext(); return; case WXK_ESCAPE: @@ -4096,7 +4109,8 @@ void MainDialog::hideFindPanel() void MainDialog::startFindNext() //F3 or ENTER in m_textCtrlSearchTxt { - const wxString& searchString = m_textCtrlSearchTxt->GetValue(); + wxString searchString = m_textCtrlSearchTxt->GetValue(); + trim(searchString); if (searchString.empty()) showFindPanel(); diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 84a52e8b..a9becd95 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -319,6 +319,8 @@ private: std::unique_ptr<zen::FilterConfig> filterCfgOnClipboard; //copy/paste of filter config wxWindow* focusWindowAfterSearch; //used to restore focus after search panel is closed + + bool localKeyEventsEnabled; }; #endif //MAINDIALOG_H_891048132454564 diff --git a/FreeFileSync/Source/ui/osx_dock.h b/FreeFileSync/Source/ui/osx_dock.h new file mode 100644 index 00000000..62237524 --- /dev/null +++ b/FreeFileSync/Source/ui/osx_dock.h @@ -0,0 +1,17 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef OSX_DOCK_837210847312534 +#define OSX_DOCK_837210847312534 + +#include <zen/sys_error.h> + +namespace osx +{ +void dockIconSetText(const char* str); //throw SysError +} + +#endif //OSX_DOCK_837210847312534 diff --git a/FreeFileSync/Source/ui/osx_dock.mm b/FreeFileSync/Source/ui/osx_dock.mm new file mode 100644 index 00000000..8f42ae88 --- /dev/null +++ b/FreeFileSync/Source/ui/osx_dock.mm @@ -0,0 +1,24 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "osx_dock.h" +#include <Cocoa/Cocoa.h> +#include <zen/osx_throw_exception.h> + + +void osx::dockIconSetText(const char* str) //throw SysError +{ + @try + { + NSString* label = [NSString stringWithCString:str encoding:NSUTF8StringEncoding]; + //stringWithCString returns string which is already set to autorelease! + [[NSApp dockTile] setBadgeLabel:label]; //label may be nil + } + @catch (NSException* e) + { + throwSysError(e); //throw SysError + } +} diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 76117f6c..dc1f42a6 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -515,7 +515,7 @@ public: return wxEmptyString; } - virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool selected) override + virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override { wxRect rectTmp = rect; @@ -954,24 +954,24 @@ private: class CurveDataRectangleArea : public CurveData { public: - CurveDataRectangleArea () : x(0), y(0) {} + CurveDataRectangleArea() : x_(), y_() {} - void setValue (double xVal, double yVal) { x = xVal; y = yVal; } - void setValueX(double xVal) { x = xVal; } - double getValueX() const { return x; } + void setValue (double x, double y) { x_ = x; y_ = y; } + void setValueX(double x) { x_ = x; } + double getValueX() const { return x_; } private: - virtual std::pair<double, double> getRangeX() const override { return std::make_pair(x, x); } //conceptually just a vertical line! + virtual std::pair<double, double> getRangeX() const override { return std::make_pair(x_, x_); } //conceptually just a vertical line! virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const override { - points.push_back(CurvePoint(0, y)); - points.push_back(CurvePoint(x, y)); - points.push_back(CurvePoint(x, 0)); + points.push_back(CurvePoint(0, y_)); + points.push_back(CurvePoint(x_, y_)); + points.push_back(CurvePoint(x_, 0)); } - double x; //time elapsed in seconds - double y; //items/bytes processed + double x_; //time elapsed in seconds + double y_; //items/bytes processed }; @@ -1375,9 +1375,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::initNewPhase() curveDataItemsCurrent->setValue(0, 0); curveDataBytesTotal ->setValue(0, 0); curveDataItemsTotal ->setValue(0, 0); - - curveDataBytes->clear(); - curveDataItems->clear(); + curveDataBytes ->clear(); + curveDataItems ->clear(); notifyProgressChange(); //make sure graphs get initial values @@ -1403,9 +1402,14 @@ void SyncProgressDialogImpl<TopLevelDialog>::notifyProgressChange() //noexcept! break; case ProcessCallback::PHASE_COMPARING_CONTENT: case ProcessCallback::PHASE_SYNCHRONIZING: - curveDataBytes->addRecord(timeElapsed.timeMs(), syncStat_->getDataCurrent (syncStat_->currentPhase())); - curveDataItems->addRecord(timeElapsed.timeMs(), syncStat_->getObjectsCurrent(syncStat_->currentPhase())); - break; + { + const std::int64_t dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); + const int itemsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); + + curveDataBytes->addRecord(timeElapsed.timeMs(), dataCurrent); + curveDataItems->addRecord(timeElapsed.timeMs(), itemsCurrent); + } + break; } } @@ -1538,10 +1542,10 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) case ProcessCallback::PHASE_COMPARING_CONTENT: case ProcessCallback::PHASE_SYNCHRONIZING: { - const int itemsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); - const int itemsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); const std::int64_t dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); const std::int64_t dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); + const int itemsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); + const int itemsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); //add both data + obj-count, to handle "deletion-only" cases const double fraction = dataTotal + itemsTotal == 0 ? 1 : std::max(0.0, 1.0 * (dataCurrent + itemsCurrent) / (dataTotal + itemsTotal)); diff --git a/FreeFileSync/Source/ui/taskbar.cpp b/FreeFileSync/Source/ui/taskbar.cpp index d65d1fbc..9e7cbcde 100644 --- a/FreeFileSync/Source/ui/taskbar.cpp +++ b/FreeFileSync/Source/ui/taskbar.cpp @@ -30,15 +30,15 @@ using namespace tbseven; class Taskbar::Pimpl //throw TaskbarNotAvailable { public: - Pimpl(const wxFrame& window) : - assocWindow(window.GetHWND()), - setStatus_ (getDllName(), funName_setStatus), - setProgress_(getDllName(), funName_setProgress) + Pimpl(const wxFrame& window) : assocWindow(window.GetHWND()) { - if (!assocWindow || !setProgress_ || !setStatus_) + if (!win7OrLater()) //check *before* trying to load DLL throw TaskbarNotAvailable(); - if (!zen::win7OrLater()) + setStatus_ = DllFun<FunType_setStatus >(getDllName(), funName_setStatus); + setProgress_ = DllFun<FunType_setProgress>(getDllName(), funName_setProgress); + + if (!assocWindow || !setStatus_ || !setProgress_) throw TaskbarNotAvailable(); } @@ -72,9 +72,9 @@ public: } private: - void* assocWindow; //HWND - const DllFun<FunType_setStatus> setStatus_; - const DllFun<FunType_setProgress> setProgress_; + void* const assocWindow; //HWND + DllFun<FunType_setStatus> setStatus_; + DllFun<FunType_setProgress> setProgress_; }; #elif defined HAVE_UBUNTU_UNITY //Ubuntu unity diff --git a/FreeFileSync/Source/ui/tray_icon.cpp b/FreeFileSync/Source/ui/tray_icon.cpp index 71ee7e20..c9debfb1 100644 --- a/FreeFileSync/Source/ui/tray_icon.cpp +++ b/FreeFileSync/Source/ui/tray_icon.cpp @@ -56,7 +56,7 @@ wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate ico return wxIcon(); const int pixelCount = logo.GetWidth() * logo.GetHeight(); - const int startFillPixel = numeric::confineCpy(numeric::round(fraction * pixelCount), 0, pixelCount); + const int startFillPixel = numeric::clampCpy(numeric::round(fraction * pixelCount), 0, pixelCount); //minor optimization static std::pair<int, wxIcon> buffer = std::make_pair(-1, wxNullIcon); diff --git a/FreeFileSync/Source/ui/tree_view.cpp b/FreeFileSync/Source/ui/tree_view.cpp index 9768c01a..de952465 100644 --- a/FreeFileSync/Source/ui/tree_view.cpp +++ b/FreeFileSync/Source/ui/tree_view.cpp @@ -866,7 +866,7 @@ private: clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); } - virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool selected) override + virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override { //wxRect rectTmp= drawCellBorder(dc, rect); wxRect rectTmp = rect; diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index a1def1a9..558b09ea 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t currentVersion[] = L"6.9"; //internal linkage! +const wchar_t currentVersion[] = L"6.10"; //internal linkage! } #endif diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 22358922..f4e758c1 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -19,7 +19,8 @@ using namespace zen; const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType(); -const std::shared_ptr<LabelFormatter> Graph2D::MainAttributes::defaultFormat = std::make_shared<DecimalNumberFormatter>(); //for some buggy reason MSVC isn't able to use a temporary as a default argument +//for some buggy reason MSVC isn't able to use a temporary as a default argument +const std::shared_ptr<LabelFormatter> Graph2D::MainAttributes::defaultFormat = std::make_shared<DecimalNumberFormatter>(); double zen::nextNiceNumber(double blockSize) //round to next number which is a convenient to read block size @@ -82,7 +83,7 @@ public: outOfBoundsLow (-1 * scaleToReal + valMin), outOfBoundsHigh((screenSize + 1) * scaleToReal + valMin) { if (outOfBoundsLow > outOfBoundsHigh) std::swap(outOfBoundsLow, outOfBoundsHigh); } - double screenToReal(double screenPos) const //input value: [0, screenSize - 1] + double screenToReal(double screenPos) const //map [0, screenSize] -> [valMin, valMax] { return screenPos * scaleToReal + min_; } @@ -93,7 +94,7 @@ public: int realToScreenRound(double realPos) const //returns -1 and screenSize + 1 if out of bounds! { //catch large double values: if double is larger than what int can represent => undefined behavior! - numeric::confine(realPos , outOfBoundsLow, outOfBoundsHigh); + numeric::clamp(realPos , outOfBoundsLow, outOfBoundsHigh); return numeric::round(realToScreen(realPos)); } @@ -120,9 +121,14 @@ void widenRange(double& valMin, double& valMax, //in/out valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); if (!numeric::isNull(valRangePerBlock)) { - valMin = std::floor(valMin / valRangePerBlock) * valRangePerBlock; - valMax = std::ceil (valMax / valRangePerBlock) * valRangePerBlock; - blockCount = numeric::round((valMax - valMin) / valRangePerBlock); //"round" to avoid IEEE 754 surprises + int blockMin = std::floor(valMin / valRangePerBlock); + int blockMax = std::ceil (valMax / valRangePerBlock); + if (blockMin == blockMax) //handle valMin == valMax == integer + ++blockMax; + + valMin = blockMin * valRangePerBlock; + valMax = blockMax * valRangePerBlock; + blockCount = blockMax - blockMin; return; } } @@ -670,10 +676,10 @@ void Graph2D::render(wxDC& dc) const const wxPoint screenCurrent = activeSel->refCurrentPos() - graphAreaOrigin; //normalize positions: a mouse selection is symmetric and *not* an half-open range! - double screenFromX = confineCpy(screenStart .x, 0, graphArea.width - 1); - double screenFromY = confineCpy(screenStart .y, 0, graphArea.height - 1); - double screenToX = confineCpy(screenCurrent.x, 0, graphArea.width - 1); - double screenToY = confineCpy(screenCurrent.y, 0, graphArea.height - 1); + double screenFromX = clampCpy(screenStart .x, 0, graphArea.width - 1); + double screenFromY = clampCpy(screenStart .y, 0, graphArea.height - 1); + double screenToX = clampCpy(screenCurrent.x, 0, graphArea.width - 1); + double screenToY = clampCpy(screenCurrent.y, 0, graphArea.height - 1); widen(&screenFromX, &screenToX); //use full pixel range for selection! widen(&screenFromY, &screenToY); @@ -731,10 +737,10 @@ void Graph2D::render(wxDC& dc) const shrink(&screenFromX, &screenToX); shrink(&screenFromY, &screenToY); - confine(screenFromX, 0.0, graphArea.width - 1.0); - confine(screenFromY, 0.0, graphArea.height - 1.0); - confine(screenToX, 0.0, graphArea.width - 1.0); - confine(screenToY, 0.0, graphArea.height - 1.0); + clamp(screenFromX, 0.0, graphArea.width - 1.0); + clamp(screenFromY, 0.0, graphArea.height - 1.0); + clamp(screenToX, 0.0, graphArea.width - 1.0); + clamp(screenToY, 0.0, graphArea.height - 1.0); const wxPoint pixelFrom = wxPoint(numeric::round(screenFromX), numeric::round(screenFromY)) + graphAreaOrigin; diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 248a82ef..5bcac1a5 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -79,7 +79,7 @@ void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool } -void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool selected) +void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) { wxRect rectTmp = drawCellBorder(dc, rect); @@ -938,7 +938,7 @@ private: { const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight); RecursiveDcClipper dummy3(dc, cellRect); - prov->renderCell(dc, cellRect, row, cw.type_, drawAsSelected(row)); + prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row)); } cellAreaTL.x += cw.width_; } @@ -1161,7 +1161,7 @@ private: { //select current row *after* scrolling wxPoint clientPosTrimmed = clientPos; - numeric::confine(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window! + numeric::clamp(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window! const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed); const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range @@ -1310,8 +1310,8 @@ void Grid::updateWindowSizes(bool updateScrollbar) { ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; - numeric::confine<ptrdiff_t>(yFrom, 0, logicalHeight - 1); - numeric::confine<ptrdiff_t>(yTo, 0, logicalHeight - 1); + numeric::clamp<ptrdiff_t>(yFrom, 0, logicalHeight - 1); + numeric::clamp<ptrdiff_t>(yTo, 0, logicalHeight - 1); const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom); const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo); @@ -1416,8 +1416,8 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) { ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; - numeric::confine<ptrdiff_t>(yFrom, 0, logicalHeight - 1); - numeric::confine<ptrdiff_t>(yTo, 0, logicalHeight - 1); + numeric::clamp<ptrdiff_t>(yFrom, 0, logicalHeight - 1); + numeric::clamp<ptrdiff_t>(yTo, 0, logicalHeight - 1); const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom); const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo); @@ -1454,7 +1454,7 @@ void Grid::onKeyDown(wxKeyEvent& event) { if (rowCount > 0) { - numeric::confine<ptrdiff_t>(row, 0, rowCount - 1); + numeric::clamp<ptrdiff_t>(row, 0, rowCount - 1); setGridCursor(row); } }; @@ -1463,7 +1463,7 @@ void Grid::onKeyDown(wxKeyEvent& event) { if (rowCount > 0) { - numeric::confine<ptrdiff_t>(row, 0, rowCount - 1); + numeric::clamp<ptrdiff_t>(row, 0, rowCount - 1); selectWithCursor(row); } }; @@ -2009,8 +2009,8 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv auto rowLast = std::max(rowFrom, rowTo) + 1; const size_t rowCount = getRowCount(); - numeric::confine<ptrdiff_t>(rowFirst, 0, rowCount); - numeric::confine<ptrdiff_t>(rowLast, 0, rowCount); + numeric::clamp<ptrdiff_t>(rowFirst, 0, rowCount); + numeric::clamp<ptrdiff_t>(rowLast, 0, rowCount); selection.selectRange(rowFirst, rowLast, positive); @@ -92,8 +92,8 @@ public: //grid area virtual wxString getValue(size_t row, ColumnType colType) const = 0; - virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected); //default implementation - virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool selected); // + virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected); //default implementation + virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected); // virtual int getBestSize (wxDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()! virtual wxString getToolTip(size_t row, ColumnType colType) const { return wxString(); } @@ -250,8 +250,8 @@ private: { if (rowFirst <= rowLast) { - numeric::confine<size_t>(rowFirst, 0, rowSelectionValue.size()); - numeric::confine<size_t>(rowLast, 0, rowSelectionValue.size()); + numeric::clamp<size_t>(rowFirst, 0, rowSelectionValue.size()); + numeric::clamp<size_t>(rowLast, 0, rowSelectionValue.size()); std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive); } diff --git a/zen/basic_math.h b/zen/basic_math.h index 56cfd923..69e861be 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -32,9 +32,9 @@ template <class T> const T& max(const T& a, const T& b, const T& c); template <class T> -void confine(T& val, const T& minVal, const T& maxVal); //make sure minVal <= val && val <= maxVal +void clamp(T& val, const T& minVal, const T& maxVal); //make sure minVal <= val && val <= maxVal template <class T> -T confineCpy(const T& val, const T& minVal, const T& maxVal); +T clampCpy(const T& val, const T& minVal, const T& maxVal); template <class T, class InputIterator> //precondition: range must be sorted! auto nearMatch(const T& val, InputIterator first, InputIterator last) -> typename std::iterator_traits<InputIterator>::value_type; @@ -134,7 +134,7 @@ const T& max(const T& a, const T& b, const T& c) template <class T> inline -T confineCpy(const T& val, const T& minVal, const T& maxVal) +T clampCpy(const T& val, const T& minVal, const T& maxVal) { assert(minVal <= maxVal); if (val < minVal) @@ -145,7 +145,7 @@ T confineCpy(const T& val, const T& minVal, const T& maxVal) } template <class T> inline -void confine(T& val, const T& minVal, const T& maxVal) //name trim, clamp? +void clamp(T& val, const T& minVal, const T& maxVal) { assert(minVal <= maxVal); if (val < minVal) diff --git a/zen/dll.h b/zen/dll.h new file mode 100644 index 00000000..f6422fa7 --- /dev/null +++ b/zen/dll.h @@ -0,0 +1,121 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef DLLLOADER_H_4239582598670968 +#define DLLLOADER_H_4239582598670968 + +#include <memory> +#ifdef ZEN_WIN +#include <string> +#include "scope_guard.h" +#include "win.h" //includes "windows.h" + +#elif defined ZEN_LINUX || defined ZEN_MAC +#include <dlfcn.h> +#endif + +namespace zen +{ +/* +Manage DLL function and library ownership + - thread safety: like built-in type + - full value semantics + + Usage: + typedef BOOL (WINAPI* FunType_IsWow64Process)(HANDLE hProcess, PBOOL Wow64Process); + const zen::SysDllFun<FunType_IsWow64Process> isWow64Process(L"kernel32.dll", "IsWow64Process"); + if (isWow64Process) ... use function ptr ... + + Usage 2: + #define DEF_DLL_FUN(name) DllFun<dll_ns::FunType_##name> name(dll_ns::getDllName(), dll_ns::funName_##name); + DEF_DLL_FUN(funname1); DEF_DLL_FUN(funname2); DEF_DLL_FUN(funname3); +*/ + +template <class Func> +class DllFun +{ +public: + DllFun() : fun(nullptr) {} + +#ifdef ZEN_WIN + DllFun(const wchar_t* libraryName, const char* functionName) : + hLibRef(::LoadLibrary(libraryName), ::FreeLibrary), + fun(hLibRef ? reinterpret_cast<Func>(::GetProcAddress(static_cast<HMODULE>(hLibRef.get()), functionName)) : nullptr) {} +#elif defined ZEN_LINUX || defined ZEN_MAC + DllFun(const char* libraryName, const char* functionName) : + hLibRef(::dlopen(libraryName, RTLD_LAZY), ::dlclose), + fun(hLibRef ? reinterpret_cast<Func>(::dlsym(hLibRef.get(), functionName)) : nullptr) {} +#endif + operator Func() const { return fun; } + +private: + std::shared_ptr<void> hLibRef; //we would prefer decltype(*HMODULE()) if only it would work... + Func fun; +}; + + +#ifdef ZEN_WIN +//if the dll is already part of the process space, e.g. "kernel32.dll" or "shell32.dll", we can use a faster variant: +//NOTE: since the lifetime of the referenced library is *not* controlled, this is safe to use only for permanently loaded libraries like these! +template <class Func> +class SysDllFun +{ +public: + SysDllFun() : fun(nullptr) {} + + SysDllFun(const wchar_t* systemLibrary, const char* functionName) + { + HMODULE mod = ::GetModuleHandle(systemLibrary); + fun = mod ? reinterpret_cast<Func>(::GetProcAddress(mod, functionName)) : nullptr; + } + + operator Func() const { return fun; } + +private: + Func fun; +}; + +/* +extract binary resources from .exe/.dll: + +-- resource.h -- +#define MY_BINARY_RESOURCE 1337 + +-- resource.rc -- +MY_BINARY_RESOURCE RCDATA "filename.dat" +*/ +std::string getResourceStream(const std::wstring& libraryName, size_t resourceId); +#endif + + + + + + + + + + +//--------------- implementation--------------------------------------------------- +#ifdef ZEN_WIN +inline +std::string getResourceStream(const wchar_t* libraryName, size_t resourceId) +{ + if (HMODULE module = ::LoadLibrary(libraryName)) + { + ZEN_ON_SCOPE_EXIT(::FreeLibrary(module)); + + if (HRSRC res = ::FindResource(module, MAKEINTRESOURCE(resourceId), RT_RCDATA)) + if (HGLOBAL resHandle = ::LoadResource(module, res)) + if (const char* stream = static_cast<const char*>(::LockResource(resHandle))) + return std::string(stream, static_cast<size_t>(::SizeofResource(module, res))); //size is 0 on error + } + return std::string(); +} +#endif +} + +#endif //DLLLOADER_H_4239582598670968 diff --git a/zen/file_error.h b/zen/file_error.h index 73cfa17a..9276e8c5 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -38,10 +38,10 @@ DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume); //CAVEAT: evalulate global error code *before* "throw" statement which may overwrite error code //due to a memory allocation before it creates the thrown instance! (e.g. affects MinGW + Win XP!!!) -inline +template <class FE = FileError> inline void throwFileError(const std::wstring& msg, const std::wstring& functionName, const ErrorCode ec) //throw FileError { - throw FileError(msg, formatSystemError(functionName, ec)); + throw FE(msg, formatSystemError(functionName, ec)); } @@ -11,7 +11,7 @@ #include <memory> #include <cstdint> #include "string_tools.h" - +#include "format_unit.h" //minimal layer enabling text translation - without platform/library dependencies! #ifdef __WXMSW__ //we have wxWidgets @@ -75,7 +75,7 @@ std::wstring translate(const std::wstring& singular, const std::wstring& plural, return translation; } else - return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", zen::numberTo<std::wstring>(n)); + return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", toGuiString(n)); } template <class T> inline diff --git a/zen/osx_string.h b/zen/osx_string.h new file mode 100644 index 00000000..ba83ca27 --- /dev/null +++ b/zen/osx_string.h @@ -0,0 +1,82 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef OSX_STRING_1873641732143214324 +#define OSX_STRING_1873641732143214324 + +#include <CoreFoundation/CoreFoundation.h> //CFString +#include "zstring.h" + +namespace osx +{ +Zstring cfStringToZstring(const CFStringRef& cfStr); + +CFStringRef createCFString (const char* utf8Str); //returns nullptr on error +CFMutableStringRef createMutableCFString(const char* utf8Str); //pass ownership! => ZEN_ON_SCOPE_EXIT(::CFRelease(str)); + + + + + + + + + + + + + +//################# implementation ##################### +inline +Zstring cfStringToZstring(const CFStringRef& cfStr) +{ + if (cfStr) + { + //perf: try to get away cheap: + if (const char* utf8Str = ::CFStringGetCStringPtr(cfStr, kCFStringEncodingUTF8)) + return utf8Str; + + CFIndex length = ::CFStringGetLength(cfStr); + if (length > 0) + { + CFIndex bufferSize = ::CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); + Zstring buffer; + buffer.resize(bufferSize); + + if (::CFStringGetCString(cfStr, &*buffer.begin(), bufferSize, kCFStringEncodingUTF8)) + { + buffer.resize(zen::strLength(buffer.c_str())); //caveat: memory consumption of returned string! + return buffer; + } + } + } + return Zstring(); +} + + +inline +CFStringRef createCFString(const char* utf8Str) +{ + //don't bother with CFStringCreateWithBytes: it's slightly slower, despite passing length info + return ::CFStringCreateWithCString(nullptr, //CFAllocatorRef alloc, + utf8Str, //const char *cStr, + kCFStringEncodingUTF8); //CFStringEncoding encoding +} + + +inline +CFMutableStringRef createMutableCFString(const char* utf8Str) +{ + if (CFMutableStringRef strRef = ::CFStringCreateMutable(NULL, 0)) + { + ::CFStringAppendCString(strRef, utf8Str, kCFStringEncodingUTF8); + return strRef; + } + return nullptr; +} +} + +#endif //OSX_STRING_1873641732143214324 diff --git a/zen/osx_throw_exception.h b/zen/osx_throw_exception.h new file mode 100644 index 00000000..07e3af3e --- /dev/null +++ b/zen/osx_throw_exception.h @@ -0,0 +1,56 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef OSX_EXCEPTION_89274305834255 +#define OSX_EXCEPTION_89274305834255 + +#import <Cocoa/Cocoa.h> +#include "sys_error.h" +#include "utf.h" + +namespace osx +{ +//for use in Objective C implementation files only! +void throwSysError(NSException* e); //throw SysError + +#define ZEN_OSX_ASSERT(obj) ZEN_OSX_ASSERT_IMPL(obj, #obj) //throw SysError +/* +Example: ZEN_OSX_ASSERT(obj); + +Equivalent to: + if (!obj) + throw zen::SysError(L"Assertion failed: \"obj\"."); +*/ + + + + + + +//######################## implmentation ############################ +inline +void throwSysError(NSException* e) //throw SysError +{ + std::string msg; + if (const char* name = [[e name ] cStringUsingEncoding:NSUTF8StringEncoding]) //"const char*" NOT owned by us! + msg += name; + if (const char* descr = [[e reason] cStringUsingEncoding:NSUTF8StringEncoding]) + { + msg += "\n"; + msg += descr; + } + throw zen::SysError(zen::utfCvrtTo<std::wstring>(msg)); + /* + e.g. + NSInvalidArgumentException + *** +[NSString stringWithCString:encoding:]: NULL cString + */ +} +} + +#define ZEN_OSX_ASSERT_IMPL(obj, txt) if (!(obj)) throw zen::SysError(std::wstring(L"Assertion failed: \"") + L ## txt + L"\"."); + +#endif //OSX_EXCEPTION_89274305834255 diff --git a/zen/privilege.cpp b/zen/privilege.cpp new file mode 100644 index 00000000..c2db4701 --- /dev/null +++ b/zen/privilege.cpp @@ -0,0 +1,144 @@ +#include "privilege.h" +#include <map> +//#include <mutex> +#include "win.h" //includes "windows.h" +#include "thread.h" +#include "zstring.h" +#include "scope_guard.h" +#include "win_ver.h" + +using namespace zen; + + +namespace +{ +bool privilegeIsActive(const wchar_t* privilege) //throw FileError +{ + HANDLE hToken = nullptr; + if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, + TOKEN_QUERY, //__in DWORD DesiredAccess, + &hToken)) //__out PHANDLE TokenHandle + throwFileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), L"OpenProcessToken", getLastError()); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hToken)); + + LUID luid = {}; + if (!::LookupPrivilegeValue(nullptr, //__in_opt LPCTSTR lpSystemName, + privilege, //__in LPCTSTR lpName, + &luid )) //__out PLUID lpLuid + throwFileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), L"LookupPrivilegeValue", getLastError()); + + PRIVILEGE_SET priv = {}; + priv.PrivilegeCount = 1; + priv.Control = PRIVILEGE_SET_ALL_NECESSARY; + priv.Privilege[0].Luid = luid; + priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; + + BOOL alreadyGranted = FALSE; + if (!::PrivilegeCheck(hToken, //__in HANDLE ClientToken, + &priv, //__inout PPRIVILEGE_SET RequiredPrivileges, + &alreadyGranted)) //__out LPBOOL pfResult + throwFileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), L"PrivilegeCheck", getLastError()); + + return alreadyGranted != FALSE; +} + + +void setPrivilege(const wchar_t* privilege, bool enable) //throw FileError +{ + HANDLE hToken = nullptr; + if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, + TOKEN_ADJUST_PRIVILEGES, //__in DWORD DesiredAccess, + &hToken)) //__out PHANDLE TokenHandle + throwFileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), L"OpenProcessToken", getLastError()); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hToken)); + + LUID luid = {}; + if (!::LookupPrivilegeValue(nullptr, //__in_opt LPCTSTR lpSystemName, + privilege, //__in LPCTSTR lpName, + &luid )) //__out PLUID lpLuid + throwFileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), L"LookupPrivilegeValue", getLastError()); + + TOKEN_PRIVILEGES tp = {}; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + if (!::AdjustTokenPrivileges(hToken, //__in HANDLE TokenHandle, + false, //__in BOOL DisableAllPrivileges, + &tp, //__in_opt PTOKEN_PRIVILEGES NewState, + 0, //__in DWORD BufferLength, + nullptr, //__out_opt PTOKEN_PRIVILEGES PreviousState, + nullptr)) //__out_opt PDWORD ReturnLength + throwFileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), L"AdjustTokenPrivileges", getLastError()); + + DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! + if (lastError == ERROR_NOT_ALL_ASSIGNED) //check although previous function returned with success! + { +#ifdef __MINGW32__ //Shobjidl.h +#define ERROR_ELEVATION_REQUIRED 740L +#endif + if (vistaOrLater()) //replace this useless error code with what it *really* means! + lastError = ERROR_ELEVATION_REQUIRED; + + throwFileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), L"AdjustTokenPrivileges", lastError); + } +} + + +class Privileges +{ +public: + static Privileges& getInstance() + { + //meyers singleton: avoid static initialization order problem in global namespace! + static Privileges inst; + return inst; + } + + void ensureActive(const wchar_t* privilege) //throw FileError + { + boost::lock_guard<boost::mutex> dummy(lockPrivileges); + + if (activePrivileges.find(privilege) != activePrivileges.end()) + return; //privilege already active + + if (privilegeIsActive(privilege)) //privilege was already active before starting this tool + activePrivileges.insert(std::make_pair(privilege, false)); + else + { + setPrivilege(privilege, true); + activePrivileges.insert(std::make_pair(privilege, true)); + } + } + +private: + Privileges() {} + Privileges (const Privileges&) = delete; + Privileges& operator=(const Privileges&) = delete; + + ~Privileges() //clean up: deactivate all privileges that have been activated by this application + { + for (const auto& priv : activePrivileges) + if (priv.second) + { + try + { + setPrivilege(priv.first.c_str(), false); //throw FileError + } + catch (FileError&) {} + } + } + + std::map<Zstring, bool> activePrivileges; //bool: enabled by this application +boost::mutex lockPrivileges; +}; + +//caveat: function scope static initialization is not thread-safe in VS 2010! +auto& dummy = Privileges::getInstance(); +} + + +void zen::activatePrivilege(const wchar_t* privilege) //throw FileError +{ + Privileges::getInstance().ensureActive(privilege); +} diff --git a/zen/privilege.h b/zen/privilege.h new file mode 100644 index 00000000..e9b83be9 --- /dev/null +++ b/zen/privilege.h @@ -0,0 +1,17 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef PRIVILEGE_H_INCLUDED +#define PRIVILEGE_H_INCLUDED + +#include "file_error.h" + +namespace zen +{ +void activatePrivilege(const wchar_t* privilege); //throw FileError; thread-safe!!! +} + +#endif // PRIVILEGE_H_INCLUDED diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 21833492..4106ed02 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -157,6 +157,13 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError { using namespace zen; #ifdef ZEN_WIN + //GetFinalPathNameByHandle() is not available before Vista! + typedef DWORD (WINAPI* GetFinalPathNameByHandleWFunc)(HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); + const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); + if (!getFinalPathNameByHandle) + throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); + + const HANDLE hDir = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName, 0, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, @@ -169,13 +176,6 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); - //GetFinalPathNameByHandle() is not available before Vista! - typedef DWORD (WINAPI* GetFinalPathNameByHandleWFunc)(HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); - const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); - - if (!getFinalPathNameByHandle) - throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); - const DWORD bufferSize = getFinalPathNameByHandle(hDir, nullptr, 0, 0); if (bufferSize == 0) throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"GetFinalPathNameByHandle", getLastError()); diff --git a/zen/win_ver.h b/zen/win_ver.h index e123737d..0d3f8d70 100644 --- a/zen/win_ver.h +++ b/zen/win_ver.h @@ -7,30 +7,48 @@ #ifndef WINDOWS_VERSION_HEADER_238470348254325 #define WINDOWS_VERSION_HEADER_238470348254325 -#include <cstdint> +#include <utility> #include "win.h" //includes "windows.h" namespace zen { -std::uint64_t getOsVersion(); -std::uint64_t toBigOsNumber(DWORD high, DWORD low); + struct OsVersion + { + OsVersion() : major(), minor() {} + OsVersion(DWORD high, DWORD low) : major(high), minor(low) {} + + DWORD major; + DWORD minor; + }; + inline bool operator< (const OsVersion& lhs, const OsVersion& rhs) { return lhs.major != rhs.major ? lhs.major < rhs.major : lhs.minor < rhs.minor; } + inline bool operator==(const OsVersion& lhs, const OsVersion& rhs) { return lhs.major == rhs.major && lhs.minor == rhs.minor; } + //version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx -const std::uint64_t osVersionWin81 = toBigOsNumber(6, 3); -const std::uint64_t osVersionWin8 = toBigOsNumber(6, 2); -const std::uint64_t osVersionWin7 = toBigOsNumber(6, 1); -const std::uint64_t osVersionWinVista = toBigOsNumber(6, 0); -const std::uint64_t osVersionWinServer2003 = toBigOsNumber(5, 2); -const std::uint64_t osVersionWinXp = toBigOsNumber(5, 1); +const OsVersion osVersionWin81 (6, 3); +const OsVersion osVersionWin8 (6, 2); +const OsVersion osVersionWin7 (6, 1); +const OsVersion osVersionWinVista (6, 0); +const OsVersion osVersionWinServer2003(5, 2); +const OsVersion osVersionWinXp (5, 1); -inline bool win81OrLater () { return getOsVersion() >= osVersionWin81; } -inline bool win8OrLater () { return getOsVersion() >= osVersionWin8; } -inline bool win7OrLater () { return getOsVersion() >= osVersionWin7; } -inline bool vistaOrLater () { return getOsVersion() >= osVersionWinVista; } -inline bool winServer2003orLater() { return getOsVersion() >= osVersionWinServer2003; } -inline bool winXpOrLater () { return getOsVersion() >= osVersionWinXp; } +/* + NOTE: there are two basic APIs to check Windows version: (empiric study following) + GetVersionEx -> reports version considering compatibility mode (and compatibility setting in app manifest since Windows 8.1) + VerifyVersionInfo -> always reports *real* Windows Version +*/ +//GetVersionEx()-based APIs: +OsVersion getOsVersion(); +inline bool win81OrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWin81; } +inline bool win8OrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWin8; } +inline bool win7OrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWin7; } +inline bool vistaOrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWinVista; } +inline bool winServer2003orLater() { using namespace std::rel_ops; return getOsVersion() >= osVersionWinServer2003; } +inline bool winXpOrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWinXp; } +//VerifyVersionInfo()-based APIs: +bool isRealOsVersion(const OsVersion& ver); @@ -39,25 +57,37 @@ inline bool winXpOrLater () { return getOsVersion() >= osVersionWinXp; //######################### implementation ######################### inline -std::uint64_t toBigOsNumber(DWORD high, DWORD low) +OsVersion getOsVersion() { - ULARGE_INTEGER tmp = {}; - tmp.HighPart = high; - tmp.LowPart = low; - - static_assert(sizeof(tmp) == sizeof(std::uint64_t), ""); - return tmp.QuadPart; + OSVERSIONINFO osvi = {}; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (!::GetVersionEx(&osvi)) //38 ns per call! (yes, that's nano!) -> we do NOT miss C++11 thread-safe statics right now... + { + assert(false); + return OsVersion(); + } + return OsVersion(osvi.dwMajorVersion, osvi.dwMinorVersion); } inline -std::uint64_t getOsVersion() +bool isRealOsVersion(const OsVersion& ver) { - OSVERSIONINFO osvi = {}; - osvi.dwOSVersionInfoSize = sizeof(osvi); - if (!::GetVersionEx(&osvi)) //38 ns per call! (yes, that's nano!) -> we do NOT miss C++11 thread safe statics right now... - return 0; - return toBigOsNumber(osvi.dwMajorVersion, osvi.dwMinorVersion); + OSVERSIONINFOEX verInfo = {}; + verInfo.dwOSVersionInfoSize = sizeof(verInfo); + verInfo.dwMajorVersion = ver.major; + verInfo.dwMinorVersion = ver.minor; + + //Syntax: http://msdn.microsoft.com/en-us/library/windows/desktop/ms725491%28v=vs.85%29.aspx + DWORDLONG conditionMask = 0; + VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_EQUAL); + VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_EQUAL); + + const bool rv = ::VerifyVersionInfo(&verInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) + == TRUE; //silence VC "performance warnings" + assert(rv || GetLastError() == ERROR_OLD_WIN_VERSION); + + return rv; } } |