diff options
103 files changed, 2573 insertions, 1892 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt index 1b4363ae..25c8e121 100644 --- a/FreeFileSync/Build/Changelog.txt +++ b/FreeFileSync/Build/Changelog.txt @@ -1,6 +1,24 @@ +FreeFileSync 7.3 [2015-08-01] +----------------------------- +New context menu option to copy selected files to alternate folder (create diffs) +Fill a folder pair by dropping two folders at a time from Explorer +Added option to set non-standard SFTP port +Prevent recursive creation of temporary Recycle Bin directories (Windows) +Retrieve grid column label colors from the system +Fixed detection of already existing files when moving (Linux) +Follow os convention for preferences (OS X) +Prevent progress dialog from hiding behind main dialog (OS X) +Fixed config saved status not updating when changing certain settings +Support for high dpi display settings +Fixed crash when help viewer is open during exit (Windows) +Show manual deletion progress within comparison status panel +Further reduced number of file accesses during versioning +Fixed folder picker failing to select Desktop folder (Windows) + + FreeFileSync 7.2 [2015-07-01] ----------------------------- -Support synchronization with SFTP (SSH File Transfer Protocol) +Support synchronization via SFTP (SSH File Transfer Protocol) Detailed error reporting when checking folder existence Synchronize MTP devices with no modification time support Set focus to comparison button on startup diff --git a/FreeFileSync/Build/Help/html/Tips and Tricks.html b/FreeFileSync/Build/Help/html/Tips and Tricks.html index 88132bfd..0a816759 100644 --- a/FreeFileSync/Build/Help/html/Tips and Tricks.html +++ b/FreeFileSync/Build/Help/html/Tips and Tricks.html @@ -56,7 +56,15 @@ <b>You can open a batch configuration without running it via the Windows Explorer context menu.</b><br> <img src="../images/explorer_context.png" alt="Explorer context menu"> <div class="separation_line"></div> - + + <b>You can drag and drop two folders at a time from Windows Explorer to fill a folder pair in one go.</b><br> + <img src="../images/two_folder_drop.png" alt="Two-folder drop"> + <div class="separation_line"></div> + + <b>You can copy files selected on the main dialog to an alternate folder and thereby save a "diff".</b><br> + <img src="../images/copy_alternative_path.png" alt="Copy to alternative path"> + <div class="separation_line"></div> + <li> <b>You can use a volume name instead of a drive letter.</b><br> <img src="../images/VolumeName.png" alt="Drive letter by volume name"> diff --git a/FreeFileSync/Build/Help/images/copy_alternative_path.png b/FreeFileSync/Build/Help/images/copy_alternative_path.png Binary files differnew file mode 100644 index 00000000..c402f67b --- /dev/null +++ b/FreeFileSync/Build/Help/images/copy_alternative_path.png diff --git a/FreeFileSync/Build/Help/images/two_folder_drop.png b/FreeFileSync/Build/Help/images/two_folder_drop.png Binary files differnew file mode 100644 index 00000000..b71d163f --- /dev/null +++ b/FreeFileSync/Build/Help/images/two_folder_drop.png diff --git a/FreeFileSync/Build/Languages/chinese_traditional.lng b/FreeFileSync/Build/Languages/chinese_traditional.lng index 01d388d8..375f0aac 100644 --- a/FreeFileSync/Build/Languages/chinese_traditional.lng +++ b/FreeFileSync/Build/Languages/chinese_traditional.lng @@ -7,6 +7,9 @@ <plural_definition>0</plural_definition> </header> +<source>Port:</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>自上次同步後,兩邊均已變更過。</target> diff --git a/FreeFileSync/Build/Languages/english_uk.lng b/FreeFileSync/Build/Languages/english_uk.lng index ba5160de..f7446956 100644 --- a/FreeFileSync/Build/Languages/english_uk.lng +++ b/FreeFileSync/Build/Languages/english_uk.lng @@ -7,6 +7,33 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>Please enter a target folder.</source> +<target></target> + +<source> +<pluralform>Copy the following item to another folder?</pluralform> +<pluralform>Copy the following %x items to another folder?</pluralform> +</source> +<target></target> + +<source>Copy to...</source> +<target></target> + +<source>Copy items</source> +<target></target> + +<source>&Copy</source> +<target></target> + +<source>&Overwrite existing files</source> +<target></target> + +<source>&Keep relative paths</source> +<target></target> + +<source>Port:</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Both sides have changed since last synchronisation.</target> @@ -22,8 +49,14 @@ <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Setting default synchronisation directions: Old files will be overwritten with newer files.</target> -<source>Checking recycle bin availability for folder %x...</source> -<target>Checking recycle bin availability for folder %x...</target> +<source>Creating folder %x</source> +<target>Creating folder %x</target> + +<source>Creating file %x</source> +<target>Creating file %x</target> + +<source>Creating symbolic link %x</source> +<target>Creating symbolic link %x</target> <source>Moving file %x to the recycle bin</source> <target>Moving file %x to the recycle bin</target> @@ -43,6 +76,9 @@ <source>Deleting symbolic link %x</source> <target>Deleting symbolic link %x</target> +<source>Checking recycle bin availability for folder %x...</source> +<target>Checking recycle bin availability for folder %x...</target> + <source>The recycle bin is not available for the following folders. Files will be deleted permanently instead:</source> <target>The recycle bin is not available for the following folders. Files will be deleted permanently instead:</target> @@ -345,9 +381,6 @@ Actual: %y bytes <source>Detecting abandoned lock...</source> <target>Detecting abandoned lock...</target> -<source>Creating file %x</source> -<target>Creating file %x</target> - <source>Saving file %x...</source> <target>Saving file %x...</target> @@ -557,12 +590,6 @@ The command is triggered if: <source>Removing old versions...</source> <target>Removing old versions...</target> -<source>Creating symbolic link %x</source> -<target>Creating symbolic link %x</target> - -<source>Creating folder %x</source> -<target>Creating folder %x</target> - <source>Updating file %x</source> <target>Updating file %x</target> @@ -797,8 +824,8 @@ The command is triggered if: <source>&Actions</source> <target>&Actions</target> -<source>&Options</source> -<target>&Options</target> +<source>&Preferences</source> +<target>&Preferences</target> <source>&Language</source> <target>&Language</target> @@ -1189,8 +1216,8 @@ This guarantees a consistent state even in case of a serious error. <source>Select Time Span</source> <target>Select Time Span</target> -<source>&Preferences</source> -<target>&Preferences</target> +<source>&Options</source> +<target>&Options</target> <source>Main Bar</source> <target>Main Bar</target> @@ -1462,9 +1489,6 @@ This guarantees a consistent state even in case of a serious error. <pluralform>Do you really want to delete the following %x items?</pluralform> </target> -<source>Preferences</source> -<target>Preferences</target> - <source>Copy DACL, SACL, Owner, Group</source> <target>Copy DACL, SACL, Owner, Group</target> diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index a0b4ecaa..dc96d9d9 100644 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -22,8 +22,14 @@ <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Setze Standardwerte für Synchronisationsrichtungen: Alte Dateien werden durch neuere überschrieben.</target> -<source>Checking recycle bin availability for folder %x...</source> -<target>Prüfe Verfügbarkeit des Papierkorbs für Ordner %x...</target> +<source>Creating folder %x</source> +<target>Erstelle Ordner %x</target> + +<source>Creating file %x</source> +<target>Erstelle Datei %x</target> + +<source>Creating symbolic link %x</source> +<target>Erstelle symbolischen Verknüpfung %x</target> <source>Moving file %x to the recycle bin</source> <target>Verschiebe Datei %x in den Papierkorb</target> @@ -43,6 +49,9 @@ <source>Deleting symbolic link %x</source> <target>Lösche symbolische Verknüpfung %x</target> +<source>Checking recycle bin availability for folder %x...</source> +<target>Prüfe Verfügbarkeit des Papierkorbs für Ordner %x...</target> + <source>The recycle bin is not available for the following folders. Files will be deleted permanently instead:</source> <target>Der Papierkorb ist für die folgenden Ordner nicht verfügbar. Dateien werden stattdessen permanent gelöscht:</target> @@ -243,12 +252,6 @@ Tatsächlich: %y bytes <source>Cannot open file %x.</source> <target>Die Datei %x kann nicht geöffnet werden.</target> -<source>Cannot delete directory %x.</source> -<target>Das Verzeichnis %x kann nicht gelöscht werden.</target> - -<source>Cannot delete file %x.</source> -<target>Die Datei %x kann nicht gelöscht werden.</target> - <source>Cannot find device %x.</source> <target>Das Gerät %x wurde nicht gefunden.</target> @@ -258,6 +261,12 @@ Tatsächlich: %y bytes <source>Cannot create directory %x.</source> <target>Das Verzeichnis %x kann nicht erstellt werden.</target> +<source>Cannot delete directory %x.</source> +<target>Das Verzeichnis %x kann nicht gelöscht werden.</target> + +<source>Cannot delete file %x.</source> +<target>Die Datei %x kann nicht gelöscht werden.</target> + <source>Cannot write modification time of %x.</source> <target>Die Änderungszeit von %x kann nicht geschrieben werden.</target> @@ -345,9 +354,6 @@ Tatsächlich: %y bytes <source>Detecting abandoned lock...</source> <target>Ermittle aufgegebene Sperre...</target> -<source>Creating file %x</source> -<target>Erstelle Datei %x</target> - <source>Saving file %x...</source> <target>Speichere Datei %x...</target> @@ -557,12 +563,6 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Removing old versions...</source> <target>Entferne alte Versionen...</target> -<source>Creating symbolic link %x</source> -<target>Erstelle symbolischen Verknüpfung %x</target> - -<source>Creating folder %x</source> -<target>Erstelle Ordner %x</target> - <source>Updating file %x</source> <target>Aktualisiere Datei %x</target> @@ -797,8 +797,8 @@ Die Befehlszeile wird ausgelöst, wenn: <source>&Actions</source> <target>&Aktionen</target> -<source>&Options</source> -<target>&Optionen</target> +<source>&Preferences</source> +<target>&Einstellungen</target> <source>&Language</source> <target>&Sprache</target> @@ -1015,6 +1015,9 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Server name or IP address:</source> <target>Servername oder IP-Adresse:</target> +<source>Port:</source> +<target>Port:</target> + <source>Examples:</source> <target>Beispiele:</target> @@ -1093,6 +1096,15 @@ Die Befehlszeile wird ausgelöst, wenn: <source>How can I schedule a batch job?</source> <target>Wie können Batchaufträge in den Taskplaner eingetragen werden?</target> +<source>&Keep relative paths</source> +<target>&Relative Pfade beibehalten</target> + +<source>&Overwrite existing files</source> +<target>&Existierende Dateien überschreiben</target> + +<source>&Copy</source> +<target>&Kopieren</target> + <source>The following settings are used for all synchronization jobs.</source> <target>Die folgenden Einstellungen werden für alle Synchronisationsaufgaben verwendet.</target> @@ -1183,14 +1195,17 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>Delete Items</source> <target>Elemente löschen</target> +<source>Copy items</source> +<target>Elemente kopieren</target> + <source>Options</source> <target>Optionen</target> <source>Select Time Span</source> <target>Zeitspanne auswählen</target> -<source>&Preferences</source> -<target>&Einstellungen</target> +<source>&Options</source> +<target>&Optionen</target> <source>Main Bar</source> <target>Hauptleiste</target> @@ -1270,6 +1285,9 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>Exclude temporarily</source> <target>Temporär ausschließen</target> +<source>Copy to...</source> +<target>Kopieren nach...</target> + <source>Delete</source> <target>Löschen</target> @@ -1442,6 +1460,18 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <target>Protokoll</target> <source> +<pluralform>Copy the following item to another folder?</pluralform> +<pluralform>Copy the following %x items to another folder?</pluralform> +</source> +<target> +<pluralform>Soll das folgende Element in einen anderen Ordner kopiert werden?</pluralform> +<pluralform>Sollen die folgenden %x Elemente in einen anderen Ordner kopiert werden?</pluralform> +</target> + +<source>Please enter a target folder.</source> +<target>Bitte geben Sie einen Zielorder ein.</target> + +<source> <pluralform>Do you really want to move the following item to the recycle bin?</pluralform> <pluralform>Do you really want to move the following %x items to the recycle bin?</pluralform> </source> @@ -1462,9 +1492,6 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <pluralform>Sollen die folgenden %x Elemente wirklich gelöscht werden?</pluralform> </target> -<source>Preferences</source> -<target>Einstellungen</target> - <source>Copy DACL, SACL, Owner, Group</source> <target>DACL, SACL, Besitzer und Gruppe kopieren</target> diff --git a/FreeFileSync/Build/Languages/italian.lng b/FreeFileSync/Build/Languages/italian.lng index c76c3bf7..3542ee7e 100644 --- a/FreeFileSync/Build/Languages/italian.lng +++ b/FreeFileSync/Build/Languages/italian.lng @@ -56,7 +56,7 @@ <target>Errore di sintassi</target> <source>Cannot find file %x.</source> -<target></target> +<target>Impossibile trovare il file %x.</target> <source>File %x does not contain a valid configuration.</source> <target>Il file %x non contiene una configurazione valida.</target> @@ -95,7 +95,7 @@ <target>Qualsiasi numero di coppie di directory alternative per al massimo un file di configurazione.</target> <source>Open configuration for editing without executing it.</source> -<target></target> +<target>Apri configurazione per la modifica senza eseguirlo.</target> <source>Cannot find the following folders:</source> <target>Impossibile trovare le seguenti cartelle:</target> @@ -210,13 +210,17 @@ Unexpected size of data stream. Expected: %x bytes Actual: %y bytes </source> -<target></target> +<target> +Dimensioni impreviste del flusso di dati. +Previsto: %x byte +Attuale: %y byte +</target> <source>Cannot write permissions of %x.</source> <target>Impossibile scrivere i permessi di %x.</target> <source>Operation not supported for different base folder types.</source> -<target></target> +<target>Operazione non supportata per i diversi tipi di cartelle di base.</target> <source>Cannot write file %x.</source> <target>Impossibile scrivere il file %x.</target> @@ -234,26 +238,26 @@ Actual: %y bytes <target>Impossibile leggere gli attributi del file %x.</target> <source>Cannot find %x.</source> -<target></target> +<target>Impossibile trovare %x.</target> <source>Cannot open file %x.</source> <target>Impossibile aprire il file %x.</target> -<source>Cannot delete directory %x.</source> -<target>Impossibile eliminare la directory %x.</target> - -<source>Cannot delete file %x.</source> -<target>Impossibile eliminare il file %x.</target> - <source>Cannot find device %x.</source> -<target></target> +<target>Impossibile trovare dispositivo %x.</target> <source>Cannot determine free disk space for %x.</source> -<target></target> +<target>Impossibile determinare lo spazio libero su disco per %x.</target> <source>Cannot create directory %x.</source> <target>Impossibile creare la cartella %x.</target> +<source>Cannot delete directory %x.</source> +<target>Impossibile eliminare la directory %x.</target> + +<source>Cannot delete file %x.</source> +<target>Impossibile eliminare il file %x.</target> + <source>Cannot write modification time of %x.</source> <target>Impossibile scrivere data e ora di modifica di %x.</target> @@ -276,7 +280,7 @@ Actual: %y bytes <target>Codice Errore %x:</target> <source>Failed to connect to SFTP server %x.</source> -<target></target> +<target>Impossibile connettersi al server SFTP %x.</target> <source> <pluralform>1 byte</pluralform> @@ -318,7 +322,7 @@ Actual: %y bytes <target>Ricerca della cartella %x...</target> <source>Time out while searching for folder %x.</source> -<target></target> +<target>Pausa durante la ricerca per la cartella %x.</target> <source>Cannot get process information.</source> <target>Impossibile ottenere informazioni sul processo.</target> @@ -378,7 +382,7 @@ Actual: %y bytes <target>/sec</target> <source>%x items/sec</source> -<target>%x elementi/sec</target> +<target>%x oggetti/sec</target> <source>Show in Explorer</source> <target>Mostra in Esplora Risorse</target> @@ -450,7 +454,7 @@ Actual: %y bytes <target>3. Premi 'Avvio'.</target> <source>To get started just import a .ffs_batch file.</source> -<target>Per iniziare è sufficiente importare un file con estensione .ffs_batch</target> +<target>Per iniziare è sufficiente importare un file con estensione .ffs_batch.</target> <source>Folders to watch:</source> <target>Cartelle da guardare:</target> @@ -572,10 +576,10 @@ Il comando è attivato se: <target>Aggiornamento attributi di %x</target> <source>%x and %y have different content.</source> -<target></target> +<target>%x e %y hanno un contenuto diverso.</target> <source>Data verification error:</source> -<target></target> +<target>Errore di verifica dei dati:</target> <source>Creating a Volume Shadow Copy for %x...</source> <target>Creazione di un Volume Shadow Copy per %x...</target> @@ -596,7 +600,7 @@ Il comando è attivato se: <target>I seguenti oggetti hanno conflitti irrisolti e non saranno sincronizzati:</target> <source>The following folders are significantly different. Make sure you have selected the correct folders for synchronization.</source> -<target></target> +<target>Le seguenti cartelle sono significativamente differenti. Assicurarsi di aver selezionato le cartelle corrette per la sincronizzazione.</target> <source>Not enough free disk space available in:</source> <target>Spazio libero su disco insufficiente in:</target> @@ -731,7 +735,7 @@ Il comando è attivato se: <target>Categoria</target> <source>Action</source> -<target>Azioni</target> +<target>Azione</target> <source>Local comparison settings</source> <target>Impostazioni di confronto locali</target> @@ -764,7 +768,7 @@ Il comando è attivato se: <target>Impostazioni di sincronizzazione locale</target> <source>The selected folder %x cannot be used with FreeFileSync. Please select a folder on a local file system, network or an MTP device.</source> -<target></target> +<target>La cartella selezionata %x non può essere utilizzata con FreeFileSync. Si prega di selezionare una cartella su un file system locale, di rete o un dispositivo MTP.</target> <source>&New</source> <target>&Nuovo</target> @@ -836,7 +840,7 @@ Il comando è attivato se: <target>Elimina la coppia di cartelle</target> <source>Select SFTP folder</source> -<target></target> +<target>Selezionare la cartella SFTP</target> <source>Swap sides</source> <target>Inverti i lati</target> @@ -896,7 +900,7 @@ Il comando è attivato se: <target>Identificare i file uguali confrontando il contenuto del file.</target> <source>&Ignore time shift (in hours)</source> -<target></target> +<target>&Ignora tempo di spostamento (in ore)</target> <source>Consider file times with specified offset as equal</source> <target>Considerare i tempi dei file con specifico offset o uguale</target> @@ -905,13 +909,13 @@ Il comando è attivato se: <target>Maneggiare l'ora legale</target> <source>Include &symbolic links:</source> -<target></target> +<target>Includere collegamenti &simbolici:</target> <source>&Follow</source> -<target></target> +<target>&Esegui</target> <source>&Direct</source> -<target></target> +<target>&Diretta</target> <source>More information</source> <target>Maggiori informazioni</target> @@ -954,7 +958,11 @@ Il comando è attivato se: - Requires and creates database files - Detection not available for first sync </source> -<target></target> +<target> +- Non supportato da tutti i file system +- Richiede e crea file di database +- Rilevazione non disponibile per prima sincronizzazione +</target> <source>Detect synchronization directions with the help of database files</source> <target>Rileva indicazioni sincronizzazione con l'aiuto del file di database</target> @@ -1002,27 +1010,25 @@ Il comando è attivato se: <target>OK</target> <source>Enter your SFTP login details:</source> -<target></target> +<target>Inserisci i tuoi dati d'accesso SFTP:</target> <source>Server name or IP address:</source> -<target></target> +<target>Nome server o indirizzo IP:</target> <source>Examples:</source> -<target></target> +<target>Esempi:</target> <source>User name:</source> -<target></target> +<target>Nome utente:</target> <source>Password:</source> -<target></target> +<target>Password:</target> <source>&Show password</source> -<target></target> +<target>&Mostra password</target> <source>Directory on server:</source> -<target></target> - - +<target>Directory su server:</target> <source>Start synchronization now?</source> <target>Avviare la sincronizzazione ora?</target> @@ -1034,7 +1040,7 @@ Il comando è attivato se: <target>&Non mostrare più questo avviso</target> <source>Arrange folder pair</source> -<target></target> +<target>Disporre coppia di cartelle</target> <source>Items found:</source> <target>Oggetti trovati:</target> @@ -1169,7 +1175,7 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Ringraziamenti per la traduzione:</target> <source>SSH File Transfer Protocol</source> -<target></target> +<target>Protocollo di trasferimento SSH</target> <source>Save as Batch Job</source> <target>Salva come Processo Batch</target> @@ -1184,7 +1190,7 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Selezionare Intervallo di Tempo</target> <source>&Preferences</source> -<target></target> +<target>&Preferenze</target> <source>Main Bar</source> <target>Barra Principale</target> @@ -1313,7 +1319,7 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>No&n salvare</target> <source>Remove entry from list</source> -<target></target> +<target>Togliere voce dalla lista</target> <source>Synchronization Settings</source> <target>Impostazioni di Sincronizzazione</target> @@ -1367,7 +1373,7 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Mostra file filtrati o temporaneamente esclusi</target> <source>Save as default</source> -<target></target> +<target>Salva come predefinito</target> <source>Filter</source> <target>Filtro</target> @@ -1379,10 +1385,10 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Impossibile trovare %x</target> <source>Move up</source> -<target></target> +<target>Sposta su</target> <source>Move down</source> -<target></target> +<target>Sposta giù</target> <source>Comma-separated values</source> <target>Valori separati da virgole</target> @@ -1460,7 +1466,7 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Preferenze</target> <source>Copy DACL, SACL, Owner, Group</source> -<target></target> +<target>Copia DACL, SACL, Proprietario, Gruppo</target> <source>Integrate external applications into context menu. The following macros are available:</source> <target>Integra applicazioni esterne nel menu contestuale. Sono disponibili le seguenti macro:</target> @@ -1559,7 +1565,7 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Impossibile registrarsi per ricevere i messaggi di sistema.</target> <source>Unable to register device notifications for %x.</source> -<target></target> +<target>Impossibile registrare le notifiche di dispositivo per %x.</target> <source>Cannot monitor directory %x.</source> <target>Impossibile monitorare la directory %x.</target> @@ -1580,13 +1586,13 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Impossibile leggere i permessi di %x.</target> <source>Cannot copy permissions from %x to %y.</source> -<target></target> +<target>Non è possibile copiare i permessi da %x per %y.</target> <source>Cannot find system function %x.</source> <target>Impossibile trovare la funzione di sistema %x.</target> <source>Cannot copy attributes from %x to %y.</source> -<target></target> +<target>Non è possibile copiare gli attributi da %x per %y.</target> <source>Cannot copy file %x to %y.</source> <target>Impossibile copiare il file %x in %y.</target> @@ -1640,10 +1646,10 @@ Questo garantisce uno stato consistente anche in caso di errore grave. <target>Controllo cestino non riuscito per la cartella %x.</target> <source>The following XML elements could not be read:</source> -<target></target> +<target>I seguenti elementi XML non possono essere letti:</target> <source>Configuration file %x is incomplete. The missing elements will be set to their default values.</source> -<target></target> +<target>File di configurazione %x incompleta. Gli elementi mancanti saranno impostati sui valori predefiniti.</target> <source>Prepare installation</source> <target>Preparare l'installazione</target> diff --git a/FreeFileSync/Build/Languages/arabic.lng b/FreeFileSync/Build/Languages/outdated/arabic.lng index c70f3237..7f0bd890 100644 --- a/FreeFileSync/Build/Languages/arabic.lng +++ b/FreeFileSync/Build/Languages/outdated/arabic.lng @@ -7,6 +7,158 @@ <plural_definition>n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5</plural_definition> </header> +<source>Configuration file %x is incomplete. The missing elements will be set to their default values.</source> +<target></target> + +<source>The following XML elements could not be read:</source> +<target></target> + +<source>Cannot copy attributes from %x to %y.</source> +<target></target> + +<source>Cannot copy permissions from %x to %y.</source> +<target></target> + +<source>Unable to register device notifications for %x.</source> +<target></target> + +<source>Copy DACL, SACL, Owner, Group</source> +<target></target> + +<source>Please enter a target folder.</source> +<target></target> + +<source> +<pluralform>Copy the following item to another folder?</pluralform> +<pluralform>Copy the following %x items to another folder?</pluralform> +</source> +<target></target> + +<source>Move down</source> +<target></target> + +<source>Move up</source> +<target></target> + +<source>Save as default</source> +<target></target> + +<source>Remove entry from list</source> +<target></target> + +<source>Copy to...</source> +<target></target> + +<source>Copy items</source> +<target></target> + +<source>SSH File Transfer Protocol</source> +<target></target> + +<source>&Copy</source> +<target></target> + +<source>&Overwrite existing files</source> +<target></target> + +<source>&Keep relative paths</source> +<target></target> + +<source>Arrange folder pair</source> +<target></target> + +<source>Directory on server:</source> +<target></target> + +<source>&Show password</source> +<target></target> + +<source>Password:</source> +<target></target> + +<source>User name:</source> +<target></target> + +<source>Examples:</source> +<target></target> + +<source>Port:</source> +<target></target> + +<source>Server name or IP address:</source> +<target></target> + +<source>Enter your SFTP login details:</source> +<target></target> + +<source> +- Not supported by all file systems +- Requires and creates database files +- Detection not available for first sync +</source> +<target></target> + +<source>&Direct</source> +<target></target> + +<source>&Follow</source> +<target></target> + +<source>Include &symbolic links:</source> +<target></target> + +<source>&Ignore time shift (in hours)</source> +<target></target> + +<source>Select SFTP folder</source> +<target></target> + +<source>&Preferences</source> +<target></target> + +<source>The selected folder %x cannot be used with FreeFileSync. Please select a folder on a local file system, network or an MTP device.</source> +<target></target> + +<source>The following folders are significantly different. Make sure you have selected the correct folders for synchronization.</source> +<target></target> + +<source>Data verification error:</source> +<target></target> + +<source>%x and %y have different content.</source> +<target></target> + +<source>Time out while searching for folder %x.</source> +<target></target> + +<source>Failed to connect to SFTP server %x.</source> +<target></target> + +<source>Cannot determine free disk space for %x.</source> +<target></target> + +<source>Cannot find device %x.</source> +<target></target> + +<source>Cannot find %x.</source> +<target></target> + +<source>Operation not supported for different base folder types.</source> +<target></target> + +<source> +Unexpected size of data stream. +Expected: %x bytes +Actual: %y bytes +</source> +<target></target> + +<source>Open configuration for editing without executing it.</source> +<target></target> + +<source>Cannot find file %x.</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>كلا الجانبين قد تغير منذ المزامنة الأخيرة.</target> @@ -22,8 +174,14 @@ <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>تحديد الاتجاهات الافتراضية للمزامنة: ستتم الكتابة فوق الملفات القديمة بالملفات الأحدث.</target> -<source>Checking recycle bin availability for folder %x...</source> -<target>التحقق من توافر سلة المحذوفات من أجل المجلد %x...</target> +<source>Creating folder %x</source> +<target>إنشاء مجلد %x</target> + +<source>Creating file %x</source> +<target>إنشاء الملف %x</target> + +<source>Creating symbolic link %x</source> +<target>إنشاء ارتباط رمزي %x</target> <source>Moving file %x to the recycle bin</source> <target>نقل الملف %x إلى سلة المهملات</target> @@ -43,6 +201,9 @@ <source>Deleting symbolic link %x</source> <target>حذف الارتباط الرمزي %x</target> +<source>Checking recycle bin availability for folder %x...</source> +<target>التحقق من توافر سلة المحذوفات من أجل المجلد %x...</target> + <source>The recycle bin is not available for the following folders. Files will be deleted permanently instead:</source> <target>سلة المهملات غير متوفرة للمجلدات التالية. سيتم حذف الملفات بشكل نهائي بدلاً عن ذلك:</target> @@ -55,20 +216,17 @@ <source>Syntax error</source> <target>خطأ في البنية</target> -<source>Cannot find file %x.</source> -<target></target> - <source>File %x does not contain a valid configuration.</source> <target>لا يحتوي الملف %x تكويناً صحيحاً.</target> <source>Unequal number of left and right directories specified.</source> -<target>لم يتم تحديد عدد متساوي من المسارات على الطرفين</target> +<target>لم يتم تحديد عدد متساوي من المسارات على الطرفين.</target> <source>The config file must not contain settings at directory pair level when directories are set via command line.</source> -<target>يجب أن يحتوي ملف الخيارات الخيارات على مستوى أزواج المسارات عند تحديد المسارات بواسطة سطر الأوامر</target> +<target>يجب أن يحتوي ملف الخيارات الخيارات على مستوى أزواج المسارات عند تحديد المسارات بواسطة سطر الأوامر.</target> <source>Directories cannot be set for more than one configuration file.</source> -<target>لا يمكن اختيار المسارات لأكثر من ملف خيارات واحد</target> +<target>لا يمكن اختيار المسارات لأكثر من ملف خيارات واحد.</target> <source>Command line</source> <target>سطر الأوامر</target> @@ -86,28 +244,25 @@ <target>مسار</target> <source>Path to an alternate GlobalSettings.xml file.</source> -<target>تحديد مسار مختلف لملف GlobalSettings.xml</target> +<target>تحديد مسار مختلف لملف GlobalSettings.xml.</target> <source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> -<target>أي عدد من ملفات خيارات FreeFileSyn أو\و _gui and/or .ffs_batch</target> +<target>أي عدد من ملفات خيارات FreeFileSyn أو\و _gui and/or .ffs_batch.</target> <source>Any number of alternative directory pairs for at most one config file.</source> -<target>أي عدد من أزواج المسارات البديلة من أجل ملف خيارات واحد</target> - -<source>Open configuration for editing without executing it.</source> -<target></target> +<target>أي عدد من أزواج المسارات البديلة من أجل ملف خيارات واحد.</target> <source>Cannot find the following folders:</source> <target>تعذر العثور على المجلدات التالية:</target> <source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> -<target>بإمكانك إهمال هذا الخطأ لاعتبار كل مجلد على أنه مجلد فارغ. سيتم إنشاء المجلدات تلقائياً خلال المزامنة</target> +<target>بإمكانك إهمال هذا الخطأ لاعتبار كل مجلد على أنه مجلد فارغ. سيتم إنشاء المجلدات تلقائياً خلال المزامنة.</target> <source>A folder input field is empty.</source> <target>حقل إدخال خاص بمجلد فارغ.</target> <source>The corresponding folder will be considered as empty.</source> -<target>سيتم اعتبار المجلد الموافق كمجلد فارغ</target> +<target>سيتم اعتبار المجلد الموافق كمجلد فارغ.</target> <source>The following folder paths are dependent from each other:</source> <target>مسارات المجلدات التالية معتمدة على بعضها:</target> @@ -205,24 +360,14 @@ <source>Cannot read file %x.</source> <target>لا يمكن قراءة الملف %x.</target> -<source> -Unexpected size of data stream. -Expected: %x bytes -Actual: %y bytes -</source> -<target></target> - <source>Cannot write permissions of %x.</source> <target>لا يمكن كتابة أذونات %x.</target> -<source>Operation not supported for different base folder types.</source> -<target></target> - <source>Cannot write file %x.</source> <target>لا يمكن كتابة الملف %x.</target> <source>Cannot copy symbolic link %x to %y.</source> -<target>لا يمكن نسخ الرابط الرمزي من %x إلى %y</target> +<target>لا يمكن نسخ الرابط الرمزي من %x إلى %y.</target> <source>Cannot move file %x to %y.</source> <target>لا يمكن نقل الملف %x إلى %y.</target> @@ -233,27 +378,18 @@ Actual: %y bytes <source>Cannot read file attributes of %x.</source> <target>لا يمكن قراءة سمات الملف %x.</target> -<source>Cannot find %x.</source> -<target></target> - <source>Cannot open file %x.</source> <target>تعذر فتح الملف %x.</target> +<source>Cannot create directory %x.</source> +<target>لا يمكن إنشاء المسار %x.</target> + <source>Cannot delete directory %x.</source> <target>لا يمكن حذف المسار %x.</target> <source>Cannot delete file %x.</source> <target>لا يمكن حذف الملف %x.</target> -<source>Cannot find device %x.</source> -<target></target> - -<source>Cannot determine free disk space for %x.</source> -<target></target> - -<source>Cannot create directory %x.</source> -<target>لا يمكن إنشاء المسار %x.</target> - <source>Cannot write modification time of %x.</source> <target>لا يمكن كتابة وقت تعديل %x.</target> @@ -275,9 +411,6 @@ Actual: %y bytes <source>Error Code %x:</source> <target>خطأ رقم %x:</target> -<source>Failed to connect to SFTP server %x.</source> -<target></target> - <source> <pluralform>1 byte</pluralform> <pluralform>%x bytes</pluralform> @@ -321,9 +454,6 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>البحث عن المجلد %x...</target> -<source>Time out while searching for folder %x.</source> -<target></target> - <source>Cannot get process information.</source> <target>لا يمكن الحصول على معلومات العملية.</target> @@ -349,9 +479,6 @@ Actual: %y bytes <source>Detecting abandoned lock...</source> <target>اكتشاف قفل مهمل...</target> -<source>Creating file %x</source> -<target>إنشاء الملف %x</target> - <source>Saving file %x...</source> <target>جاري حفظ الملف %x...</target> @@ -370,9 +497,6 @@ Actual: %y bytes <source>Cannot set directory lock for %x.</source> <target>تعذر إنشاء قفل للمسار %x.</target> -<source>Scanning:</source> -<target>الفحص:</target> - <source> <pluralform>1 thread</pluralform> <pluralform>%x threads</pluralform> @@ -386,6 +510,9 @@ Actual: %y bytes <pluralform>%x بند</pluralform> </target> +<source>Scanning:</source> +<target>الفحص:</target> + <source>/sec</source> <target>\ثانية</target> @@ -408,7 +535,7 @@ Actual: %y bytes <target>الرجاء استخدام إصدار الـ 64-bit للبرنامج لإنشاء ملفات الظل الاحتياطية على هذا النظام.</target> <source>Cannot determine volume name for %x.</source> -<target>تعذر تحديد اسم الوسط %x</target> +<target>تعذر تحديد اسم الوسط %x.</target> <source>Volume name %x is not part of file path %y.</source> <target>اسم وحدة التخزين %x ليس جزءاُ من اسم الملف %y.</target> @@ -477,7 +604,7 @@ Actual: %y bytes <target>تصفح</target> <source>Idle time (in seconds):</source> -<target>وقت الخمول (بالثانية)</target> +<target>وقت الخمول (بالثانية):</target> <source>Idle time between last detected change and execution of command</source> <target>وقت الخمول بين آخر تغيير تم الكشف عنه وتنفيذ الأوامر</target> @@ -565,12 +692,6 @@ The command is triggered if: <source>Removing old versions...</source> <target>إزالة الإصدارات القديمة...</target> -<source>Creating symbolic link %x</source> -<target>إنشاء ارتباط رمزي %x</target> - -<source>Creating folder %x</source> -<target>إنشاء مجلد %x</target> - <source>Updating file %x</source> <target>جاري تحديث الملف %x</target> @@ -583,12 +704,6 @@ The command is triggered if: <source>Updating attributes of %x</source> <target>تحديث سمات %x</target> -<source>%x and %y have different content.</source> -<target></target> - -<source>Data verification error:</source> -<target></target> - <source>Creating a Volume Shadow Copy for %x...</source> <target>جاري إنشاء نسخة ظل وسيطة لـ %x...</target> @@ -607,9 +722,6 @@ The command is triggered if: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>العناصر التالية لم تحل اختلافاتها، و لن يتم مزامنتها:</target> -<source>The following folders are significantly different. Make sure you have selected the correct folders for synchronization.</source> -<target></target> - <source>Not enough free disk space available in:</source> <target>المساحة الحرة المتوفرة على القرص غير كافية:</target> @@ -708,7 +820,7 @@ The command is triggered if: <target>البرنامج هو الأحدث حتى الآن.</target> <source>Unable to connect to www.freefilesync.org.</source> -<target>تعذر الاتصال بـ www.freefilesync.org</target> +<target>تعذر الاتصال بـ www.freefilesync.org.</target> <source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> <target>لم نستطع العثور على على رقم إصدار FreeFileSync على الشبكة. هل تريد التحقق يدوياً؟</target> @@ -779,9 +891,6 @@ The command is triggered if: <source>Local Synchronization Settings</source> <target>إعدادات المزامنة المحلية</target> -<source>The selected folder %x cannot be used with FreeFileSync. Please select a folder on a local file system, network or an MTP device.</source> -<target></target> - <source>&New</source> <target>&جديد</target> @@ -809,9 +918,6 @@ The command is triggered if: <source>&Actions</source> <target>&مهام</target> -<source>&Options</source> -<target>&خيارات</target> - <source>&Language</source> <target>الل&غة</target> @@ -851,9 +957,6 @@ The command is triggered if: <source>Remove folder pair</source> <target>إزالة زوج مجلدات</target> -<source>Select SFTP folder</source> -<target></target> - <source>Swap sides</source> <target>مبادلة الجانبين</target> @@ -861,7 +964,7 @@ The command is triggered if: <target>إغلاق شريط البحث</target> <source>Find:</source> -<target>بحث</target> +<target>بحث:</target> <source>Match case</source> <target>مطابقة حالة الأحرف (Match case)</target> @@ -906,13 +1009,10 @@ The command is triggered if: <target>اختيار بديل:</target> <source>Identify equal files by comparing modification time and size.</source> -<target>التعرف على الملفات المتساوية عن طريق مقارنة التاريخ و الحجم</target> +<target>التعرف على الملفات المتساوية عن طريق مقارنة التاريخ و الحجم.</target> <source>Identify equal files by comparing the file content.</source> -<target>التعرف على الملفات المتساوية عن طريق مقارنة محتوى الملف</target> - -<source>&Ignore time shift (in hours)</source> -<target></target> +<target>التعرف على الملفات المتساوية عن طريق مقارنة محتوى الملف.</target> <source>Consider file times with specified offset as equal</source> <target>اعتبار الملفات ذات الفارق الزمني المحدد و كأنها متساوية</target> @@ -920,20 +1020,11 @@ The command is triggered if: <source>Handle daylight saving time</source> <target>تعامل مع التوقيت الصيفي</target> -<source>Include &symbolic links:</source> -<target></target> - -<source>&Follow</source> -<target></target> - -<source>&Direct</source> -<target></target> - <source>More information</source> <target>المزيد من المعلومات</target> <source>Local settings:</source> -<target>الإعدادات المحلية</target> +<target>الإعدادات المحلية:</target> <source>Include:</source> <target>إدخال:</target> @@ -965,13 +1056,6 @@ The command is triggered if: <source>Detect moved files</source> <target>اكتشاف الملفات المنقولة</target> -<source> -- Not supported by all file systems -- Requires and creates database files -- Detection not available for first sync -</source> -<target></target> - <source>Detect synchronization directions with the help of database files</source> <target>تحديد اتجاه المزامنة بالاستعانة بقواعد بيانات الملفات</target> @@ -1017,41 +1101,15 @@ The command is triggered if: <source>OK</source> <target>موافق</target> -<source>Enter your SFTP login details:</source> -<target></target> - -<source>Server name or IP address:</source> -<target></target> - -<source>Examples:</source> -<target></target> - -<source>User name:</source> -<target></target> - -<source>Password:</source> -<target></target> - -<source>&Show password</source> -<target></target> - -<source>Directory on server:</source> -<target></target> - - - <source>Start synchronization now?</source> <target>بدأ المزامنة الآن؟</target> <source>Variant:</source> -<target>بديل</target> +<target>بديل:</target> <source>&Don't show this dialog again</source> <target>&لا تظهر نافذة الحوار هذه مرة ثانية</target> -<source>Arrange folder pair</source> -<target></target> - <source>Items found:</source> <target>العناصر التي تم العثور عليها:</target> @@ -1092,10 +1150,10 @@ The command is triggered if: <target>تشغيل بوضع التصغير</target> <source>Save log:</source> -<target>حفظ السجل</target> +<target>حفظ السجل:</target> <source>Limit:</source> -<target>حدود</target> +<target>حدود:</target> <source>Limit maximum number of log files</source> <target>تقييد الحد الأقصى لعدد ملفات السجل</target> @@ -1104,7 +1162,7 @@ The command is triggered if: <target>كيف يمكنني جدولة مهمة دفعية؟</target> <source>The following settings are used for all synchronization jobs.</source> -<target>هذه الإعدادات مستخدمة لجميع مهمات المزامنة</target> +<target>هذه الإعدادات مستخدمة لجميع مهمات المزامنة.</target> <source>Fail-safe file copy</source> <target>نسخ ملفات آمن من الفشل</target> @@ -1125,7 +1183,7 @@ This guarantees a consistent state even in case of a serious error. <target>نسخ الملفات المقفلة</target> <source>Copy shared or locked files using the Volume Shadow Copy Service.</source> -<target>نسخ الملفات المشتركة مع مستخدمين آخرين أو المقفولة باستخدام خدمة نسخ الظل الوسيط</target> +<target>نسخ الملفات المشتركة مع مستخدمين آخرين أو المقفولة باستخدام خدمة نسخ الظل الوسيط.</target> <source>(requires administrator rights)</source> <target>(بحاجة امتيازات المدير)</target> @@ -1146,7 +1204,7 @@ This guarantees a consistent state even in case of a serious error. <target>التأخير (بالثواني):</target> <source>Customize context menu:</source> -<target>تخصيص القائمة المحلية</target> +<target>تخصيص القائمة المحلية:</target> <source>Description</source> <target>الوصف</target> @@ -1184,9 +1242,6 @@ This guarantees a consistent state even in case of a serious error. <source>Many thanks for localization:</source> <target>شكرا جزيلا للترجمة:</target> -<source>SSH File Transfer Protocol</source> -<target></target> - <source>Save as Batch Job</source> <target>حفظ كمهمة دفعية</target> @@ -1199,8 +1254,8 @@ This guarantees a consistent state even in case of a serious error. <source>Select Time Span</source> <target>اختيار المطال الزمني</target> -<source>&Preferences</source> -<target></target> +<source>&Options</source> +<target>&خيارات</target> <source>Main Bar</source> <target>الشريط الرئيسي</target> @@ -1285,7 +1340,7 @@ This guarantees a consistent state even in case of a serious error. <target>تحديد متعدد</target> <source>Include via filter:</source> -<target>تضمن عن طريق فلتر</target> +<target>تضمن عن طريق فلتر:</target> <source>Exclude via filter:</source> <target>استبعاد باستخدام عامل الفلترة:</target> @@ -1344,9 +1399,6 @@ This guarantees a consistent state even in case of a serious error. <source>Do&n't save</source> <target>&لا تحفظ</target> -<source>Remove entry from list</source> -<target></target> - <source>Synchronization Settings</source> <target>إعدادات المزامنة</target> @@ -1398,9 +1450,6 @@ This guarantees a consistent state even in case of a serious error. <source>Show filtered or temporarily excluded files</source> <target>إظهار الملفات التي تم فلترتها أو استبعادها بشكل مؤقت</target> -<source>Save as default</source> -<target></target> - <source>Filter</source> <target>عامل الفلترة</target> @@ -1410,12 +1459,6 @@ This guarantees a consistent state even in case of a serious error. <source>Cannot find %x</source> <target>لا يمكن العثور على %x</target> -<source>Move up</source> -<target></target> - -<source>Move down</source> -<target></target> - <source>Comma-separated values</source> <target>قائمة قيم مفصولة بفواصل</target> @@ -1496,12 +1539,6 @@ This guarantees a consistent state even in case of a serious error. <pluralform>هل تريد حقاً حذف الـ %x ملف التالية ؟</pluralform> </target> -<source>Preferences</source> -<target>خيارات</target> - -<source>Copy DACL, SACL, Owner, Group</source> -<target></target> - <source>Integrate external applications into context menu. The following macros are available:</source> <target>دمج تطبيقات خارجية في قائمة السياق. تتوفر وحدات الماكرو التالية:</target> @@ -1524,13 +1561,13 @@ This guarantees a consistent state even in case of a serious error. <target>&إظهار</target> <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> -<target>تحديد التغيرات و مواكبتها على الجانبين. عمليات الحذف, النقل و المشاكل المكتشفة باستخدام قواعد البيانات</target> +<target>تحديد التغيرات و مواكبتها على الجانبين. عمليات الحذف, النقل و المشاكل المكتشفة باستخدام قواعد البيانات.</target> <source>Create a mirror backup of the left folder by adapting the right folder to match.</source> <target>إنشاء نسخة احتياطية من الجانب الأيمن عن طريق تعديل الطرف الأيسر ليطابق الأيمن.</target> <source>Copy new and updated files to the right folder.</source> -<target>نسخ الملفات المحدثة إلى المجلد المناسب</target> +<target>نسخ الملفات المحدثة إلى المجلد المناسب.</target> <source>Configure your own synchronization rules.</source> <target>تحديد قواعد المزامنة الخاصة بك.</target> @@ -1598,9 +1635,6 @@ This guarantees a consistent state even in case of a serious error. <source>Unable to register to receive system messages.</source> <target>تعذر التسجيل لاستقبال رسائل النظام.</target> -<source>Unable to register device notifications for %x.</source> -<target></target> - <source>Cannot monitor directory %x.</source> <target>لا يمكن مراقبة المسار %x.</target> @@ -1619,15 +1653,9 @@ This guarantees a consistent state even in case of a serious error. <source>Cannot read permissions of %x.</source> <target>لا يمكن قراءة أذونات %x.</target> -<source>Cannot copy permissions from %x to %y.</source> -<target></target> - <source>Cannot find system function %x.</source> <target>لا يمكن العثور على وظيفة نظام %x.</target> -<source>Cannot copy attributes from %x to %y.</source> -<target></target> - <source>Cannot copy file %x to %y.</source> <target>لا يمكن نسخ الملف %x إلى %y.</target> @@ -1686,22 +1714,16 @@ This guarantees a consistent state even in case of a serious error. <target>تعذر تعليق وضع النوم للنظام.</target> <source>Cannot change process I/O priorities.</source> -<target>تعذر تغيير أولويات I/O للعملية</target> +<target>تعذر تغيير أولويات I/O للعملية.</target> <source>Checking recycle bin failed for folder %x.</source> <target>فشل تصفح سلة المهملات من أجل الملف %x.</target> -<source>The following XML elements could not be read:</source> -<target></target> - -<source>Configuration file %x is incomplete. The missing elements will be set to their default values.</source> -<target></target> - <source>Prepare installation</source> <target>الاستعداد للتثبيت</target> <source>Choose which components you want to install.</source> -<target>اختر المكونات التي تريد تنصيبها</target> +<target>اختر المكونات التي تريد تنصيبها.</target> <source>Select installation type:</source> <target>اختيار نوع التنصيب:</target> @@ -1734,10 +1756,10 @@ This guarantees a consistent state even in case of a serious error. <target>انسخ الملفات فقط</target> <source>Choose a directory for installation:</source> -<target>اختيار مسار التثبيت</target> +<target>اختيار مسار التثبيت:</target> <source>Create shortcuts:</source> -<target>إنشاء اختصار</target> +<target>إنشاء اختصار:</target> <source>Desktop</source> <target>سطح المكتب</target> diff --git a/FreeFileSync/Build/Languages/norwegian.lng b/FreeFileSync/Build/Languages/outdated/norwegian.lng index 37efb208..e02ec59f 100644 --- a/FreeFileSync/Build/Languages/norwegian.lng +++ b/FreeFileSync/Build/Languages/outdated/norwegian.lng @@ -1,6 +1,6 @@ <header> <language>Norsk</language> - <translator>bjorn96</translator> + <translator>enter your name here</translator> <locale>nb_NO</locale> <image>flag_norway.png</image> <plural_count>2</plural_count> @@ -77,16 +77,16 @@ <target>Syntaks:</target> <source>global config file:</source> -<target>Global konfigureringsfil</target> +<target>Global konfigureringsfil:</target> <source>config files:</source> -<target>Konfigurer filer</target> +<target>Konfigurer filer:</target> <source>directory</source> <target>mappe</target> <source>Path to an alternate GlobalSettings.xml file.</source> -<target>Sti til en alternativ GlobalSettings.xml fil</target> +<target>Sti til en alternativ GlobalSettings.xml fil.</target> <source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> <target>Vilkårlig antall FreeFileSync .ffs_gui og/eller .ffs_batch indstillingsfiler.</target> @@ -110,7 +110,7 @@ <target>Den tilsvarende mappe betraktes som tom.</target> <source>The following folder paths are dependent from each other:</source> -<target>De følgende mappebaner er avhengige av hverandre</target> +<target>De følgende mappebaner er avhengige av hverandre:</target> <source>File %x has an invalid date.</source> <target>Filen %x har en ugyldig dato.</target> diff --git a/FreeFileSync/Build/Languages/portuguese.lng b/FreeFileSync/Build/Languages/outdated/portuguese.lng index c4e33b9b..c4e33b9b 100644 --- a/FreeFileSync/Build/Languages/portuguese.lng +++ b/FreeFileSync/Build/Languages/outdated/portuguese.lng diff --git a/FreeFileSync/Build/Languages/turkish.lng b/FreeFileSync/Build/Languages/turkish.lng index 1ecec53a..4fa90ea2 100644 --- a/FreeFileSync/Build/Languages/turkish.lng +++ b/FreeFileSync/Build/Languages/turkish.lng @@ -8,19 +8,19 @@ </header> <source>Both sides have changed since last synchronization.</source> -<target>Son eşleştirmeden bu yana iki tarafın da içeriği değişmiş.</target> +<target>Son eşitlemeden bu yana iki tarafın da içeriği değişmiş.</target> <source>Cannot determine sync-direction:</source> -<target>Eşleştirme yönü belirlenemedi:</target> +<target>Eşitleme yönü belirlenemedi:</target> <source>No change since last synchronization.</source> -<target>Son eşleştirmeden bu yana bir değişiklik olmamış.</target> +<target>Son eşilemeden bu yana bir değişiklik olmamış.</target> <source>The database entry is not in sync considering current settings.</source> <target>Geçerli kayıtlar ile veritabanı kaydı aynı değil.</target> <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> -<target>Varsayılan eşleştirme yönleri ayarlanıyor: Yeni dosyalar eski dosyaların üzerine yazılacak.</target> +<target>Varsayılan eşitleme yönleri ayarlanıyor: Yeni dosyalar eski dosyaların üzerine yazılacak.</target> <source>Checking recycle bin availability for folder %x...</source> <target>%x klasörü için Geri Dönüşüm Kutusu kullanılabilir mi diye bakılıyor...</target> @@ -56,7 +56,7 @@ <target>Yazım hatası</target> <source>Cannot find file %x.</source> -<target></target> +<target>%x dosyası bulunamadı.</target> <source>File %x does not contain a valid configuration.</source> <target>%x dosyası geçerli ayar bilgilerini içermiyor.</target> @@ -92,16 +92,16 @@ <target>FreeFileSync .ffs_gui ya da .ffs_batch ayar dosyalarının sayısı.</target> <source>Any number of alternative directory pairs for at most one config file.</source> -<target>Bir ayar dosyasındaki en fazla alternatif klasör çifti sayısı.</target> +<target>En fazla bir ayar dosyası için herhangi sayıda alternatif klasör çifti.</target> <source>Open configuration for editing without executing it.</source> -<target></target> +<target>Ayarları yürütmeden düzenleme için açın.</target> <source>Cannot find the following folders:</source> <target>Aşağıdaki klasörler bulunamadı:</target> <source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> -<target>Bu hatayı yok sayarak karşıdaki klasörleri boş kabul edebilirsiniz. Bu klasörler eşleştirme sırasında kendiliğinden oluşturulur.</target> +<target>Bu hatayı yok sayarak karşıdaki klasörleri boş kabul edebilirsiniz. Bu klasörler eşitleme sırasında kendiliğinden oluşturulur.</target> <source>A folder input field is empty.</source> <target>Bir klasör giriş alanı boş.</target> @@ -110,7 +110,7 @@ <target>Karşıdaki klasör boş olarak kabul edilecek.</target> <source>The following folder paths are dependent from each other:</source> -<target>Aşağıdaki klasör yolları birbirine bağlı:</target> +<target>Şu klasörler birbirine bağlı:</target> <source>File %x has an invalid date.</source> <target>%x dosyasının tarihi geçersiz.</target> @@ -125,7 +125,7 @@ <target>Boyut:</target> <source>Content comparison was skipped for excluded files %x.</source> -<target>Katılmayan %x dosyaları için içerik karşılaştırması atlandı.</target> +<target>%x katılmayan dosya için içerik karşılaştırması atlandı.</target> <source>Items differ in attributes only</source> <target>Yalnız öznitelikleri farklı ögeler</target> @@ -143,7 +143,7 @@ <target>Karşılaştırmaya başlanıyor</target> <source>Calculating sync directions...</source> -<target>Eşleştirme yönleri hesaplanıyor...</target> +<target>Eşitleme yönleri hesaplanıyor...</target> <source>Out of memory.</source> <target>Bellek yetersiz.</target> @@ -197,7 +197,7 @@ <target>Hiçbir işlem yapılmasın</target> <source>Update attributes on left</source> -<target>Soldaki öznitelikleri güncelle</target> +<target>Soldaki öznitelikleri güncellensin</target> <source>Update attributes on right</source> <target>Sağdaki öznitelikler güncellensin</target> @@ -210,73 +210,77 @@ Unexpected size of data stream. Expected: %x bytes Actual: %y bytes </source> -<target></target> +<target> +Veri akışının boyutu beklenenden farklı. +Beklenen: %x bayt +Gerçekleşen: %y bayt +</target> <source>Cannot write permissions of %x.</source> <target>%x izinleri yazılamadı.</target> <source>Operation not supported for different base folder types.</source> -<target></target> +<target>Farklı temel klasör tipleri üzerindeki işlemler desteklenmiyor.</target> <source>Cannot write file %x.</source> <target>%x dosyası yazılamadı.</target> <source>Cannot copy symbolic link %x to %y.</source> -<target>%x sembolik bağlantısı %y olarak kopyalanamıyor.</target> +<target>%x sembolik bağlantısı %y hedefine kopyalanamadı.</target> <source>Cannot move file %x to %y.</source> -<target>%x dosyası %y üzerine taşınamadı.</target> +<target>%x dosyası %y hedefine taşınamadı.</target> <source>Cannot enumerate directory %x.</source> -<target>%x klasörü sayılamadı.</target> +<target>%x klasörü hesaplanamadı.</target> <source>Cannot read file attributes of %x.</source> -<target>%x dosyasının özellikleri okunamadı.</target> +<target>%x dosyasının öznitelikleri okunamadı.</target> <source>Cannot find %x.</source> -<target></target> +<target>%x bulunamadı.</target> <source>Cannot open file %x.</source> -<target>%x dosyası açılamıyor.</target> - -<source>Cannot delete directory %x.</source> -<target>%x klasörü silinemedi.</target> - -<source>Cannot delete file %x.</source> -<target>%x dosyası silinemiyor.</target> +<target>%x dosyası açılamadı.</target> <source>Cannot find device %x.</source> -<target></target> +<target>%x aygıtı bulunamadı.</target> <source>Cannot determine free disk space for %x.</source> -<target></target> +<target>%x için boş disk alanı belirlenemedi.</target> <source>Cannot create directory %x.</source> -<target>%x klasörü eklenemedi.</target> +<target>%x klasörü oluşturulamadı.</target> + +<source>Cannot delete directory %x.</source> +<target>%x klasörü silinemedi.</target> + +<source>Cannot delete file %x.</source> +<target>%x dosyası silinemedi.</target> <source>Cannot write modification time of %x.</source> -<target>%x son değişiklik zamanı yazılamadı.</target> +<target>%x dosyasının değişiklik tarihi yazılamadı.</target> <source>Cannot determine final path for %x.</source> -<target>Son %x yolu belirlenemedi.</target> +<target>%x dosyasının son yolu belirlenemedi.</target> <source>Cannot resolve symbolic link %x.</source> -<target>%x simge bağlantısı çözümlenemedi</target> +<target>%x sembolik bağlantısı çözümlenemedi.</target> <source>Unable to move %x to the recycle bin.</source> -<target>%x Geri Dönüşüm Kutusuna atılamıyor.</target> +<target>%x çöp kutusuna atılamadı.</target> <source>Cannot open directory %x.</source> <target>%x klasörü açılamadı.</target> <source>Incorrect command line:</source> -<target>Geçersiz komut satırı:</target> +<target>Satırdaki komut geçersiz:</target> <source>Error Code %x:</source> <target>Hata Kodu %x:</target> <source>Failed to connect to SFTP server %x.</source> -<target></target> +<target>%x SFTP sunucusuna bağlanılamadı.</target> <source> <pluralform>1 byte</pluralform> @@ -300,10 +304,10 @@ Actual: %y bytes <target>%x dosyası yüklenemedi.</target> <source>Database file %x is incompatible.</source> -<target>%x veritabanı dosyası uyumsuz</target> +<target>%x veritabanı dosyası uyumsuz.</target> <source>Initial synchronization:</source> -<target>Başlangıç eşleştirmesi:</target> +<target>Başlangıç eşitlemesi:</target> <source>Database file %x does not yet exist.</source> <target>%x veritabanı dosyası henüz yok.</target> @@ -318,7 +322,7 @@ Actual: %y bytes <target>%x klasörü aranıyor...</target> <source>Time out while searching for folder %x.</source> -<target></target> +<target>%x klasörünü araştırma işlemi zaman aşımına uğradı.</target> <source>Cannot get process information.</source> <target>İşlem bilgisi alınamadı.</target> @@ -342,7 +346,7 @@ Actual: %y bytes <target>Kaldırılmış kilit algılanıyor...</target> <source>Creating file %x</source> -<target>%x dosyası ekleniyor</target> +<target>%x dosyası oluşturuluyor</target> <source>Saving file %x...</source> <target>%x dosyası kaydediliyor...</target> @@ -360,7 +364,7 @@ Actual: %y bytes <target>%x dosyası işlenirken hata, satır %y, sütun %z.</target> <source>Cannot set directory lock for %x.</source> -<target>%x için klasör kilidi uygulanamıyor.</target> +<target>%x için klasör kilidi uygulanamadı.</target> <source>Scanning:</source> <target>Taranıyor:</target> @@ -405,10 +409,10 @@ Actual: %y bytes <target>Durdurulması istendi: Yürürlükteki işlemin bitmesi bekleniyor...</target> <source>Unable to create time stamp for versioning:</source> -<target>Sürüm izlemesi için zaman damgası oluşturulamadı:</target> +<target>Sürümlendirme için zaman damgası oluşturulamadı:</target> <source>Drag && drop</source> -<target>Dosyaları sürükleyip buraya bırakabilirsiniz</target> +<target>Dosyaları sürükleyip buraya bırakın</target> <source>Cannot find folder %x.</source> <target>%x klasörü bulunamadı.</target> @@ -417,10 +421,10 @@ Actual: %y bytes <target>Bir klasör seçin</target> <source>&Open...</source> -<target>&Açın...</target> +<target>&Aç...</target> <source>Save &as...</source> -<target>F&arklı kaydedin...</target> +<target>F&arklı kaydet...</target> <source>E&xit</source> <target>Çı&kış</target> @@ -429,7 +433,7 @@ Actual: %y bytes <target>&Dosya</target> <source>&View help</source> -<target>&Yardıma bakın</target> +<target>&Yardım konuları</target> <source>&About</source> <target>H&akkında</target> @@ -441,7 +445,7 @@ Actual: %y bytes <target>Kullanım:</target> <source>1. Select folders to watch.</source> -<target>1. İzlenecek klasörleri seçin</target> +<target>1. İzlenecek klasörleri seçin.</target> <source>2. Enter a command line.</source> <target>2. Bir komut satırı yazın.</target> @@ -485,7 +489,7 @@ Komut şu durumlarda yürütülür: </target> <source>&Start</source> -<target>&Başlayın</target> +<target>&Başlatın</target> <source>About</source> <target>Hakkında</target> @@ -497,7 +501,7 @@ Komut şu durumlarda yürütülür: <target>Tüm dosyalar</target> <source>Automated Synchronization</source> -<target>Kendiliğinden Eşleştirme</target> +<target>Kendiliğinden Eşitleme</target> <source>Directory monitoring active</source> <target>Klasör izleme kullanılıyor</target> @@ -554,10 +558,10 @@ Komut şu durumlarda yürütülür: <target>Eski sürümler siliniyor...</target> <source>Creating symbolic link %x</source> -<target>%x sembolik bağlantısı ekleniyor</target> +<target>%x sembolik bağlantısı oluşturuluyor</target> <source>Creating folder %x</source> -<target>%x klasörü ekleniyor</target> +<target>%x klasörü oluşturuluyor</target> <source>Updating file %x</source> <target>%x dosyası güncelleniyor</target> @@ -572,10 +576,10 @@ Komut şu durumlarda yürütülür: <target>%x öznitelikleri güncelleniyor</target> <source>%x and %y have different content.</source> -<target></target> +<target>%x ve %y farklı içeriklere sahip.</target> <source>Data verification error:</source> -<target></target> +<target>Veri doğrulama hatası:</target> <source>Creating a Volume Shadow Copy for %x...</source> <target>%x için Birim Gölge Hizmeti oluşturuluyor...</target> @@ -593,10 +597,10 @@ Komut şu durumlarda yürütülür: <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> +<target>Uyuşmazlığı çözülmemiş şu ögeler eşitlenmeyecek:</target> <source>The following folders are significantly different. Make sure you have selected the correct folders for synchronization.</source> -<target></target> +<target>Şu klasörler arasında oldukça büyük farklar var. Eşitleme için doğru klasörleri seçtiğinize emin olun.</target> <source>Not enough free disk space available in:</source> <target>Şurada yeterli disk alanı yok :</target> @@ -608,10 +612,10 @@ 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>Birden çok klasör çifti aynı alt klasöre yazıyor. Lütfen ayarlarınızı gözden geçirin.</target> +<target>Çoklu klasör çiftleri ortak bir klasöre yazılır. Lütfen ayarlarınızı gözden geçirin.</target> <source>Synchronizing folder pair:</source> -<target>Eşleştirilen klasör çifti:</target> +<target>Eşitlenen klasör çifti:</target> <source>Generating database...</source> <target>Veri tabanı oluşturuluyor...</target> @@ -620,25 +624,25 @@ Komut şu durumlarda yürütülür: <target>iş adı</target> <source>Synchronization stopped</source> -<target>Eşleştirme durduruldu</target> +<target>Eşitleme durduruldu</target> <source>Stopped</source> <target>Durduruldu</target> <source>Synchronization completed with errors</source> -<target>Eşleştirme bazı hatalarla tamamlandı</target> +<target>Eşitleme bazı hatalarla tamamlandı</target> <source>Synchronization completed with warnings</source> -<target>Eşleştirme işlemi bazı uyarılarla tamamlandı</target> +<target>Eşitleme bazı uyarılar ile tamamlandı</target> <source>Warning</source> <target>Uyarı</target> <source>Nothing to synchronize</source> -<target>Eşleştirilecek bir şey yok</target> +<target>Eşitlenecek bir şey yok</target> <source>Synchronization completed successfully</source> -<target>Eşleştirme tamamlandı</target> +<target>Eşitleme tamamlandı</target> <source>Cleaning up old log files...</source> <target>Eski günlük dosyaları temizleniyor...</target> @@ -650,7 +654,7 @@ Komut şu durumlarda yürütülür: <target>Bu &uyarı bir daha görüntülenmesin</target> <source>&Ignore</source> -<target>&Yoksayın</target> +<target>&Yoksay</target> <source>&Switch</source> <target>&Değiştirin</target> @@ -695,7 +699,7 @@ Komut şu durumlarda yürütülür: <target>www.freefilesync.org sitesine bağlanılamadı.</target> <source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> -<target>Geçerli FreeFileSync sürüm numarası çevrimiçi olarak bulunamıyor. El ile denetlemek ister misiniz?</target> +<target>Geçerli FreeFileSync sürüm numarası çevrimiçi olarak bulunamadı. El ile denetlemek ister misiniz?</target> <source>&Check</source> <target>&Denetleyin</target> @@ -737,7 +741,7 @@ Komut şu durumlarda yürütülür: <target>Yerel karşılaştırma ayarları</target> <source>Local synchronization settings</source> -<target>Yerel eşleştirme ayarları</target> +<target>Yerel eşitleme ayarları</target> <source>Local filter</source> <target>Yerel süzgeç</target> @@ -761,61 +765,61 @@ Komut şu durumlarda yürütülür: <target>Yapıştırın</target> <source>Local Synchronization Settings</source> -<target>Yerel Eşleştirme Ayarları</target> +<target>Yerel Eşitleme Ayarları</target> <source>The selected folder %x cannot be used with FreeFileSync. Please select a folder on a local file system, network or an MTP device.</source> -<target></target> +<target>Seçilmiş %x klasörü FreeFileSync tarafından kullanılamıyor. Lütfen yerel dosya sistemin, ağ ya da MTP aygıtı üzerinde bulunan bir klasör seçin.</target> <source>&New</source> -<target>Ye&ni</target> +<target>&Yeni</target> <source>&Save</source> -<target>&Kaydedin</target> +<target>&Kaydet</target> <source>Save as &batch job...</source> -<target>&Toplu iş olarak kaydedin...</target> +<target>&Toplu iş olarak kaydet...</target> <source>Start &comparison</source> -<target>&Karşılaştırmayı başlatın</target> +<target>&Karşılaştırmayı başlat</target> <source>C&omparison settings</source> -<target>Ka&rşılaştırma ayarları</target> +<target>K&arşılaştırma ayarları</target> <source>&Filter settings</source> <target>&Süzme ayarları</target> <source>S&ynchronization settings</source> -<target>Eşleştirme a&yarları</target> +<target>Eşi&tleme ayarları</target> <source>Start &synchronization</source> -<target>&Eşleştirmeyi başlatın</target> +<target>&Eşitlemeyi başlat</target> <source>&Actions</source> <target>İş&lemler</target> <source>&Options</source> -<target>A&yarlar</target> +<target>&Ayarlar</target> <source>&Language</source> -<target>Di&l</target> +<target>&Dil</target> <source>&Find...</source> -<target>A&rayın...</target> +<target>A&rama...</target> <source>&Reset layout</source> -<target>Düzeni sıfı&rlayın</target> +<target>Görünümü &sıfırla</target> <source>&Export file list...</source> -<target>Dosya list&esini verin...</target> +<target>&Dosya listesini ver...</target> <source>&Tools</source> -<target>A&raçlar</target> +<target>&Araçlar</target> <source>&Check for new version</source> -<target>Yeni &sürümü denetleyin</target> +<target>&Güncelleme denetimi</target> <source>&Check now</source> -<target>&Sürümü Denetleyin</target> +<target>Şimdi &Denetleyin</target> <source>Check &automatically once a week</source> <target>&Haftada bir kendiliğinden denetlensin</target> @@ -824,10 +828,10 @@ Komut şu durumlarda yürütülür: <target>İptal</target> <source>Compare</source> -<target>Karşılaştırın</target> +<target>Karşılaştır</target> <source>Synchronize</source> -<target>Eşleştirin</target> +<target>Eşitle</target> <source>Add folder pair</source> <target>Klasör çifti ekleyin</target> @@ -836,7 +840,7 @@ Komut şu durumlarda yürütülür: <target>Klasör çiftini silin</target> <source>Select SFTP folder</source> -<target></target> +<target>SFTP klasörünü seçin</target> <source>Swap sides</source> <target>Sağ ve sol tarafları değiştirin</target> @@ -845,28 +849,28 @@ Komut şu durumlarda yürütülür: <target>Arama çubuğunu kapatın</target> <source>Find:</source> -<target>Bulun:</target> +<target>Aranacak ifade:</target> <source>Match case</source> -<target>Büyük-küçük harf uysun</target> +<target>Büyük küçük harf uyumu göz önüne alınsın</target> <source>New</source> -<target>Ekleyin</target> +<target>Yeni</target> <source>Open...</source> -<target>Açın...</target> +<target>Aç...</target> <source>Save</source> -<target>Kaydedin</target> +<target>Kaydet</target> <source>Save as...</source> -<target>Farklı kaydedin...</target> +<target>Farklı kaydet...</target> <source>View type:</source> <target>Görünüm tipi:</target> <source>Select view:</source> -<target>Görünüm seçin:</target> +<target>Görüntülenecek ögeler:</target> <source>Statistics:</source> <target>İstatistikler:</target> @@ -890,40 +894,40 @@ Komut şu durumlarda yürütülür: <target>İşlem tipini seçin:</target> <source>Identify equal files by comparing modification time and size.</source> -<target>Dosyaların aynı olup olmadığı, son değişiklik zamanı ve boyuta göre belirlensin.</target> +<target>Dosyaların aynı olup olmadığı, son değişiklik zamanı ve boyuta göre belirlenir.</target> <source>Identify equal files by comparing the file content.</source> -<target>Dosyaların aynı olup olmadığı, içeriklerine göre belirlensin.</target> +<target>Dosyaların aynı olup olmadığı, içeriklerine göre belirlenir.</target> <source>&Ignore time shift (in hours)</source> -<target></target> +<target>Zaman farkı &yoksayılsın (saat)</target> <source>Consider file times with specified offset as equal</source> -<target>Belirtilen saat kadar zaman farkı yoksayılır</target> +<target>Belirtilen zaman farkı içindeki dosyalar eşit olarak algılansın</target> <source>Handle daylight saving time</source> -<target>Yaz saati uygulamasına bakın</target> +<target>Yaz saati bilgilerine bakın</target> <source>Include &symbolic links:</source> -<target></target> +<target>&Sembolik bağlantılar katılsın:</target> <source>&Follow</source> -<target></target> +<target>İ&zlensin</target> <source>&Direct</source> -<target></target> +<target>&Yönlendirilsin</target> <source>More information</source> -<target>Ek bilgilere bakın</target> +<target>Ayrıntılı bilgilere bakın</target> <source>Local settings:</source> <target>Yerel ayarlar:</target> <source>Include:</source> -<target>Katılacak ögeler:</target> +<target>Katılacak:</target> <source>Exclude:</source> -<target>Katılmayacak ögeler</target> +<target>Katılmayacak:</target> <source>Show examples</source> <target>Örneklere bakın</target> @@ -941,7 +945,7 @@ Komut şu durumlarda yürütülür: <target>En büyük:</target> <source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> -<target>Eşleştirilmeyecek dosyaları süzecek kuralları belirleyin. Dosya yollarını bulundukları klasör çiftine göre yazın.</target> +<target>Eşitlenmeyecek dosyaların süzülme kurallarını belirleyin. Dosya yollarını bulundukları klasör çiftine göre yazın.</target> <source>C&lear</source> <target>&Temizleyin</target> @@ -954,28 +958,32 @@ Komut şu durumlarda yürütülür: - Requires and creates database files - Detection not available for first sync </source> -<target></target> +<target> +- Tüm dosya sistemleri tarafından desteklenmez +- Veritabanı dosyalarının kullanılması gerekir +- Algılama, ilk eşitleme sırasında kullanılamaz +</target> <source>Detect synchronization directions with the help of database files</source> -<target>Eşleştirme yönü, veritabanı dosyaları yardımı ile belirlensin</target> +<target>Eşitleme yönleri veritabanı dosyalarının yardımı ile algılanır</target> <source>Delete files:</source> <target>Dosya silme işlemi:</target> <source>&Permanent</source> -<target>&Kalıcı</target> +<target>&Kalıcı olarak silinsin</target> <source>Delete or overwrite files permanently</source> <target>Dosyalar kalıcı olarak silinir ya da üzerine yazılır</target> <source>&Recycle bin</source> -<target>Ge&ri Dönüşüm Kutusu</target> +<target>Ge&ri Dönüşüm Kutusuna atılsın</target> <source>Back up deleted and overwritten files in the recycle bin</source> <target>Silinen ya da üzerine yazılan dosyalar geri dönüşüm kutusuna gönderilir</target> <source>&Versioning</source> -<target>&Sürümleme</target> +<target>Eski &sürüm olarak saklansın</target> <source>Move files to a user-defined folder</source> <target>Dosyalar kullanıcı tarafından belirtilen bir klasöre taşınır</target> @@ -990,42 +998,43 @@ 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>Açılan &Pencere</target> +<target>&Görüntüle</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> <source>On completion:</source> -<target>Eşleştirme tamamlandığında:</target> +<target>Tamamlandığında:</target> <source>OK</source> <target>Tamam</target> <source>Enter your SFTP login details:</source> -<target></target> +<target>SFTP oturum açma bilgilerinizi yazın:</target> <source>Server name or IP address:</source> -<target></target> +<target>Sunucu adı / IP adresi:</target> + +<source>Port:</source> +<target>Kapı:</target> <source>Examples:</source> -<target></target> +<target>Örnekler:</target> <source>User name:</source> -<target></target> +<target>Kullanıcı adı:</target> <source>Password:</source> -<target></target> +<target>Parola:</target> <source>&Show password</source> -<target></target> +<target>Parola &görüntülensin</target> <source>Directory on server:</source> -<target></target> - - +<target>Sunucudaki klasör:</target> <source>Start synchronization now?</source> -<target>Eşleştirme başlatılsın mı?</target> +<target>Eşitleme başlatılsın mı?</target> <source>Variant:</source> <target>İşlem tipi:</target> @@ -1034,7 +1043,7 @@ Komut şu durumlarda yürütülür: <target>Bu pencere bir daha &görüntülenmesin</target> <source>Arrange folder pair</source> -<target></target> +<target>Klasör çiftini belirleyin</target> <source>Items found:</source> <target>Bulunan öge:</target> @@ -1046,7 +1055,7 @@ Komut şu durumlarda yürütülür: <target>Geçen süre:</target> <source>Synchronizing...</source> -<target>Eşleştiriliyor...</target> +<target>Eşitleniyor...</target> <source>Minimize to notification area</source> <target>Bildirim alanına küçültün</target> @@ -1064,19 +1073,19 @@ Komut şu durumlarda yürütülür: <target>Durdurulsun</target> <source>Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x</source> -<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> +<target>Hiç bir soru sorulmadan eşitleme 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>&Durdurun</target> +<target>&Durdur</target> <source>Stop synchronization at first error</source> -<target>Oluşacak ilk hatada eşleştirme durdurulur</target> +<target>Oluşacak ilk hatada eşitleme durdurulur</target> <source>Run minimized</source> -<target>Küçültülmüş olarak çalışsın</target> +<target>Küçülterek çalıştır</target> <source>Save log:</source> -<target>Günlüğe kaydedilsin:</target> +<target>Günlük kaydet:</target> <source>Limit:</source> <target>Sınır:</target> @@ -1088,7 +1097,7 @@ Komut şu durumlarda yürütülür: <target>Bir toplu işlem nasıl zamanlanır?</target> <source>The following settings are used for all synchronization jobs.</source> -<target>Aşağıdaki ayarlar tüm eşleştirme işlemleri için geçerlidir.</target> +<target>Aşağıdaki ayarlar tüm eşitleme işlemleri için geçerlidir.</target> <source>Fail-safe file copy</source> <target>Dosyalar hatasız kopyalansın</target> @@ -1136,10 +1145,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>Gizlenen iletiler yeniden görüntülensin</target> +<target>Gizlenmiş pencereler yeniden görüntülensin</target> <source>Show all permanently hidden dialogs and warning messages again</source> -<target>Gizlenen tüm ileti ve uyarılar yeniden görüntülensin</target> +<target>Kalıcı olarak gizlenmiş tüm ileti ve uyarılar yeniden görüntülenir</target> <source>&Default</source> <target>&Varsayılan</target> @@ -1148,7 +1157,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Kaynak kodu C++ kullanılarak yazılmıştır:</target> <source>If you like FreeFileSync:</source> -<target>FreeFileSync’i beğendiyseniz:</target> +<target>FreeFileSync hoşunuza gittiyse:</target> <source>Donate with PayPal</source> <target>PayPal üzerinden bağış yapın</target> @@ -1169,10 +1178,10 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Çeviriler için çok teşekkürler:</target> <source>SSH File Transfer Protocol</source> -<target></target> +<target>SSH Dosya Aktarımı İletişim Kuralı</target> <source>Save as Batch Job</source> -<target>Toplu İş Olarak Kaydedin</target> +<target>Toplu İş Olarak Kaydet</target> <source>Delete Items</source> <target>Ögeleri Silin</target> @@ -1184,7 +1193,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Zaman Aralığı</target> <source>&Preferences</source> -<target></target> +<target>&Ayarlar</target> <source>Main Bar</source> <target>Ana Çubuk</target> @@ -1193,7 +1202,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Klasör Çiftleri</target> <source>Find</source> -<target>Arayın</target> +<target>Arama</target> <source>View Settings</source> <target>Görünüm Ayarları</target> @@ -1295,7 +1304,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Önceki oturum</target> <source>Folder Comparison and Synchronization</source> -<target>Klasör Karşılaştırma ve Eşleştirme</target> +<target>Klasör Karşılaştırma ve Eşitleme</target> <source>Configuration saved</source> <target>Ayarlar kaydedildi</target> @@ -1313,13 +1322,13 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Kaydedilmesi&n</target> <source>Remove entry from list</source> -<target></target> +<target>Kaydı listeden silin</target> <source>Synchronization Settings</source> -<target>Eşleştirme Ayarları</target> +<target>Eşitleme ayarları</target> <source>Clear filter</source> -<target>Süzgeci temizleyin</target> +<target>Süzgeci temizle</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> @@ -1343,46 +1352,46 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Uyuşmazlıkları görüntüler ya da gizler</target> <source>Show files that will be created on the left side</source> -<target>Sol tarafa eklenecek dosyalar görüntülensin</target> +<target>Sol tarafa eklenecek dosyaları görüntüler ya da gizler</target> <source>Show files that will be created on the right side</source> -<target>Sağ tarafa eklenecek dosyalar görüntülensin</target> +<target>Sağ tarafa eklenecek dosyaları görüntüler ya da gizler</target> <source>Show files that will be deleted on the left side</source> -<target>Sol tarafta silinecek dosyalar görüntülensin</target> +<target>Sol tarafta silinecek dosyaları görüntüler ya da gizler</target> <source>Show files that will be deleted on the right side</source> -<target>Sağ tarafta silinecek dosyalar görüntülensin</target> +<target>Sağ tarafta silinecek dosyaları görüntüler ya da gizler</target> <source>Show files that will be updated on the left side</source> -<target>Sol tarafta güncellenecek dosyalar görüntülensin</target> +<target>Sol tarafta güncellenecek dosyaları görüntüler ya da gizler</target> <source>Show files that will be updated on the right side</source> -<target>Sağ tarafta güncellenecek dosyalar görüntülensin</target> +<target>Sağ tarafta güncellenecek dosyaları görüntüler ya da gizler</target> <source>Show files that won't be copied</source> -<target>Kopyalanmayacak dosyalar görüntülensin</target> +<target>Kopyalanmayacak dosyaları görüntüler ya da gizler</target> <source>Show filtered or temporarily excluded files</source> -<target>Süzülmüş ya da geçici olarak katılmayan dosyalar görüntülensin</target> +<target>Süzülmüş ya da geçici olarak katılmayan dosyaları görüntüler ya da gizler</target> <source>Save as default</source> -<target></target> +<target>Varsayılan olarak kaydedin</target> <source>Filter</source> -<target>Süzgeç</target> +<target>Süzme</target> <source>All files are in sync</source> -<target>Tüm dosyalar eşleştirildi</target> +<target>Tüm dosyalar eşitlendi</target> <source>Cannot find %x</source> <target>%x bulunamadı</target> <source>Move up</source> -<target></target> +<target>Yukarı taşıyın</target> <source>Move down</source> -<target></target> +<target>Aşağı taşıyın</target> <source>Comma-separated values</source> <target>Virgül ile ayrılmış değerler</target> @@ -1457,10 +1466,10 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya </target> <source>Preferences</source> -<target>Tercihler</target> +<target>Ayarlar</target> <source>Copy DACL, SACL, Owner, Group</source> -<target></target> +<target>DACL, SACL, Sahip, Grup kopyalansın</target> <source>Integrate external applications into context menu. The following macros are available:</source> <target>Sağ tık menüsüne dış uygulamalar eklenebilir. Şu etiketler kullanılabilir:</target> @@ -1478,22 +1487,22 @@ 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>Gizlenen tüm ileti ve uyarılar yeniden görüntülensin mi?</target> +<target>Gizlenmiş ileti ve uyarılar yeniden görüntülensin mi?</target> <source>&Show</source> <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> +<target>İki taraftaki değişiklikler belirlenir ve kopyalanır. Silinme, taşınma ve çakışmalar, veritabanı kullanılarak kendiliğinden algılanır.</target> <source>Create a mirror backup of the left folder by adapting the right folder to match.</source> -<target>Sağ klasör, sola uyacak şekilde güncellenerek, sol klasörün yansı yedeği oluşturulur.</target> +<target>Sağ klasör, sola uyacak şekilde değiştirilerek, sol klasörün yansı yedeği oluşturulur.</target> <source>Copy new and updated files to the right folder.</source> -<target>Sol taraftaki yeni ya da güncellenmiş ögeler sağ tarafa kopyalanır.</target> +<target>Sol taraftaki yeni ya da güncellenmiş dosyalar sağ tarafa kopyalanır.</target> <source>Configure your own synchronization rules.</source> -<target>Eşleştirme kuralları kullanıcının isteğine göre belirlenir.</target> +<target>Eşitleme kuralları kullanıcının isteğine göre belirlenir.</target> <source>Today</source> <target>Bugün</target> @@ -1520,22 +1529,22 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>MB</target> <source>Replace</source> -<target>Değiştirin</target> +<target>Ad değiştirilsin</target> <source>Move files and replace if existing</source> -<target>Dosyalar taşınsın ve varsa üzerine yazılsın</target> +<target>Dosyalar taşınır ve varsa üzerine yazılır</target> <source>Time stamp</source> <target>Zaman damgası</target> <source>Append a time stamp to each file name</source> -<target>Her bir dosya adına bir zaman damgası eklensin</target> +<target>Dosya adlarına zaman damgası eklensin</target> <source>Comparison</source> <target>Karşılaştırma</target> <source>Synchronization</source> -<target>Eşleştirme</target> +<target>Eşitleme</target> <source>Leave as unresolved conflict</source> <target>Uyuşmazlık çözülmeden bırakılsın</target> @@ -1550,7 +1559,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Dosyalar</target> <source>Items</source> -<target>Ögeler</target> +<target>Öge</target> <source>Percentage</source> <target>Yüzde</target> @@ -1559,7 +1568,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Sistem iletilerini alabilmek için gerekli kayıt eklenemedi.</target> <source>Unable to register device notifications for %x.</source> -<target></target> +<target>%x aygıtının bildirimleri için gerekli kayıt eklenemedi.</target> <source>Cannot monitor directory %x.</source> <target>%x klasörü izlenemiyor.</target> @@ -1580,13 +1589,13 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>%x izinleri okunamadı.</target> <source>Cannot copy permissions from %x to %y.</source> -<target></target> +<target>İzinler %x üzerinden %y üzerine kopyalanamadı.</target> <source>Cannot find system function %x.</source> <target>%x sistem işlevi bulunamadı.</target> <source>Cannot copy attributes from %x to %y.</source> -<target></target> +<target>Öznitelikler %x üzerinden %y üzerine kopyalanamadı.</target> <source>Cannot copy file %x to %y.</source> <target>%x dosyası %y olarak kopyalanamadı.</target> @@ -1634,22 +1643,22 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Sistem uyku kipine geçirilemedi.</target> <source>Cannot change process I/O priorities.</source> -<target>Giriş/Çıkış işlemi öncelikleri değiştirilemedi</target> +<target>Giriş/Çıkış işlemi öncelikleri değiştirilemedi.</target> <source>Checking recycle bin failed for folder %x.</source> -<target>%x klasörü için geri dönüşüm kutusu denetlenemedi.</target> +<target>%x klasörü için Geri Dönüşüm Kutusu denetlenemedi.</target> <source>The following XML elements could not be read:</source> -<target></target> +<target>Şu XML bileşenleri okunamadı:</target> <source>Configuration file %x is incomplete. The missing elements will be set to their default values.</source> -<target></target> +<target>%x ayar dosyası tam değil. Eksik bileşenler için varsayılan değerler kullanılacak.</target> <source>Prepare installation</source> <target>Yüklemeye hazırlanılıyor</target> <source>Choose which components you want to install.</source> -<target>Yüklenecek bileşenleri seçin.</target> +<target>Yüklemek istediğiniz bileşenleri seçin.</target> <source>Select installation type:</source> <target>Yükleme tipini seçin:</target> @@ -1658,46 +1667,46 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Yerel</target> <source>Portable</source> -<target>Portable</target> +<target>Taşınabilir</target> <source>recommended</source> -<target>önerilir</target> +<target>önerilen</target> <source>Save settings to "%APPDATA%\FreeFileSync"</source> -<target>Ayarlar "%APPDATA%\FreeFileSync" klasörüne kaydedilir</target> +<target>Ayarlar "%APPDATA%\FreeFileSync" kalsörüne kaydedilsin</target> <source>Register FreeFileSync file extensions</source> -<target>Kayıt defterine FreeFileSync dosya eklentileri eklenir</target> +<target>FreeFileSync dosya uzantıları ilişkilendirilsin</target> <source>Create Explorer context menu entries</source> -<target>Dosya yöneticisi sağ tık menü kayıtları eklensin</target> +<target>Windows Gezgini sağ tık menü kayıtları eklensin</target> <source>Save settings in installation directory</source> -<target>Ayarlar yükleme klasörüne kaydedilir</target> +<target>Ayarlar yükleme klasörüne kaydedilsin</target> <source>Do not write to Registry</source> -<target>Kayıt defterine herhangi bir bilgi eklenmez</target> +<target>Kayıt Defterine yazılmasın</target> <source>Just copy the files</source> -<target>Yalnızca dosyalar kopyalanır</target> +<target>Yalnız dosyalar kopyalansın</target> <source>Choose a directory for installation:</source> -<target>Yükleme klasörünü seçin:</target> +<target>Yüklenecek klasörü seçin:</target> <source>Create shortcuts:</source> -<target>Şuralarda kısayollar oluşturulsun:</target> +<target>Kısayollar oluşturulsun:</target> <source>Desktop</source> <target>Masaüstü</target> <source>Start menu</source> -<target>Başlat menüsü</target> +<target>Başlat Menüsü</target> <source>Registering FreeFileSync file extensions</source> -<target>FreeFileSync eklentileri kayıt defterine ekleniyor</target> +<target>FreeFileSync dosya uzantıları kayıt defterine ekleniyor</target> <source>Unregistering FreeFileSync file extensions</source> -<target>FreeFileSync eklentileri kayıt defterinden siliniyor</target> +<target>FreeFileSync dosya uzantıları kayıt defterinden siliniyor</target> <source>FreeFileSync Configuration</source> <target>FreeFileSync Ayarları</target> @@ -1706,11 +1715,11 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>FreeFileSync Toplu İşlem Dosyası</target> <source>FreeFileSync Synchronization Database</source> -<target>FreeFileSync Eşleştirme Veritabanı</target> +<target>FreeFileSync Eşitleme Veritabanı</target> <source>RealtimeSync Configuration</source> -<target>Gerçek Zamanlı Eşleştirme Ayarları</target> +<target>Gerçek Zamanlı Eşitleme Ayarları</target> <source>Edit with FreeFileSync</source> -<target>FreeFileSync ile düzenleyin</target> +<target>FreeFileSync ile düzenlensin</target> diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip Binary files differindex c660cc0e..5a5a7344 100644 --- a/FreeFileSync/Build/Resources.zip +++ b/FreeFileSync/Build/Resources.zip diff --git a/FreeFileSync/Source/RealtimeSync/application.cpp b/FreeFileSync/Source/RealtimeSync/application.cpp index 5dd5fe91..90dd8b99 100644 --- a/FreeFileSync/Source/RealtimeSync/application.cpp +++ b/FreeFileSync/Source/RealtimeSync/application.cpp @@ -38,20 +38,6 @@ IMPLEMENT_APP(Application); namespace { -/* -boost::thread::id mainThreadId = boost::this_thread::get_id(); - -void onTerminationRequested() -{ -std::wstring msg = boost::this_thread::get_id() == mainThreadId ? - L"Termination requested in main thread!\n\n" : - L"Termination requested in worker thread!\n\n"; -msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; - -wxSafeShowMessage(_("An exception occurred"), msg); -std::abort(); -} -*/ #ifdef _MSC_VER void crtInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { assert(false); } #endif @@ -62,8 +48,6 @@ const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); bool Application::OnInit() { - //std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads - #ifdef ZEN_WIN #ifdef _MSC_VER _set_invalid_parameter_handler(crtInvalidParameterHandler); //see comment in <zen/time.h> diff --git a/FreeFileSync/Source/RealtimeSync/folder_selector2.cpp b/FreeFileSync/Source/RealtimeSync/folder_selector2.cpp index be9f505a..7bbed861 100644 --- a/FreeFileSync/Source/RealtimeSync/folder_selector2.cpp +++ b/FreeFileSync/Source/RealtimeSync/folder_selector2.cpp @@ -27,13 +27,13 @@ void setFolderPath(const Zstring& dirpath, wxTextCtrl* txtCtrl, wxWindow& toolti if (txtCtrl) txtCtrl->ChangeValue(toWx(dirpath)); - const Zstring displayPath = getResolvedDirectoryPath(dirpath); //may block when resolving [<volume name>] + const Zstring folderPathFmt = getResolvedDirectoryPath(dirpath); //may block when resolving [<volume name>] tooltipWnd.SetToolTip(nullptr); //workaround wxComboBox bug http://trac.wxwidgets.org/ticket/10512 / http://trac.wxwidgets.org/ticket/12659 - tooltipWnd.SetToolTip(toWx(displayPath)); //who knows when the real bugfix reaches mere mortals via an official release... + tooltipWnd.SetToolTip(toWx(folderPathFmt)); //who knows when the real bugfix reaches mere mortals via an official release... if (staticText) //change static box label only if there is a real difference to what is shown in wxTextCtrl anyway - staticText->SetLabel(EqualFilePath()(appendSeparator(trimCpy(dirpath)), appendSeparator(displayPath)) ? wxString(_("Drag && drop")) : toWx(displayPath)); + staticText->SetLabel(EqualFilePath()(appendSeparator(trimCpy(dirpath)), appendSeparator(folderPathFmt)) ? wxString(_("Drag && drop")) : toWx(folderPathFmt)); } } @@ -41,11 +41,11 @@ void setFolderPath(const Zstring& dirpath, wxTextCtrl* txtCtrl, wxWindow& toolti FolderSelector2::FolderSelector2(wxWindow& dropWindow, wxButton& selectButton, - wxTextCtrl& dirpath, + wxTextCtrl& folderPathCtrl, wxStaticText* staticText) : dropWindow_(dropWindow), selectButton_(selectButton), - dirpath_(dirpath), + folderPathCtrl_(folderPathCtrl), staticText_(staticText) { //prepare drag & drop @@ -53,9 +53,9 @@ FolderSelector2::FolderSelector2(wxWindow& dropWindow, dropWindow_.Connect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector2::onFilesDropped), nullptr, this); //keep dirPicker and dirpath synchronous - dirpath_ .Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector2::onMouseWheel ), nullptr, this); - dirpath_ .Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector2::onWriteDirManually), nullptr, this); - selectButton_.Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector2::onSelectDir ), nullptr, this); + folderPathCtrl_.Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector2::onMouseWheel ), nullptr, this); + folderPathCtrl_.Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector2::onEditFolderPath), nullptr, this); + selectButton_ .Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector2::onSelectDir ), nullptr, this); } @@ -63,9 +63,9 @@ FolderSelector2::~FolderSelector2() { dropWindow_.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector2::onFilesDropped), nullptr, this); - dirpath_ .Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector2::onMouseWheel ), nullptr, this); - dirpath_ .Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector2::onWriteDirManually), nullptr, this); - selectButton_.Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector2::onSelectDir ), nullptr, this); + folderPathCtrl_.Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector2::onMouseWheel ), nullptr, this); + folderPathCtrl_.Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector2::onEditFolderPath), nullptr, this); + selectButton_ .Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector2::onSelectDir ), nullptr, this); } @@ -75,7 +75,7 @@ void FolderSelector2::onMouseWheel(wxMouseEvent& event) //additionally this will delete manual entries, although all the users wanted is scroll the parent window! //redirect to parent scrolled window! - wxWindow* wnd = &dirpath_; + wxWindow* wnd = &folderPathCtrl_; while ((wnd = wnd->GetParent()) != nullptr) //silence MSVC warning if (dynamic_cast<wxScrolledWindow*>(wnd) != nullptr) if (wxEvtHandler* evtHandler = wnd->GetEventHandler()) @@ -89,32 +89,31 @@ void FolderSelector2::onMouseWheel(wxMouseEvent& event) void FolderSelector2::onFilesDropped(FileDropEvent& event) { - const auto& files = event.getFiles(); - if (files.empty()) + const auto& itemPaths = event.getPaths(); + if (itemPaths.empty()) return; - const Zstring filePath = files[0]; - if (dirExists(filePath)) - setFolderPath(filePath, &dirpath_, dirpath_, staticText_); - else + Zstring itemPath = itemPaths[0]; + if (!dirExists(itemPath)) { - Zstring parentName = beforeLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + Zstring parentPath = beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); #ifdef ZEN_WIN - if (endsWith(parentName, L":")) //volume root - parentName += FILE_NAME_SEPARATOR; + if (endsWith(parentPath, L":")) //volume root + parentPath += FILE_NAME_SEPARATOR; #endif - if (dirExists(parentName)) - setFolderPath(parentName, &dirpath_, dirpath_, staticText_); - else //set original name unconditionally: usecase: inactive mapped network shares - setFolderPath(filePath, &dirpath_, dirpath_, staticText_); + if (dirExists(parentPath)) + itemPath = parentPath; + //else: keep original name unconditionally: usecase: inactive mapped network shares } + setPath(itemPath); + //event.Skip(); } -void FolderSelector2::onWriteDirManually(wxCommandEvent& event) +void FolderSelector2::onEditFolderPath(wxCommandEvent& event) { - setFolderPath(toZ(event.GetString()), nullptr, dirpath_, staticText_); + setFolderPath(toZ(event.GetString()), nullptr, folderPathCtrl_, staticText_); event.Skip(); } @@ -143,7 +142,7 @@ void FolderSelector2::onSelectDir(wxCommandEvent& event) { auto ft = runAsync([folderPath] { return dirExists(folderPath); }); - if (ft.wait_for(boost::chrono::milliseconds(200)) == boost::future_status::ready && ft.get()) //potentially slow network access: wait 200ms at most + if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get()) //potentially slow network access: wait 200ms at most defaultFolderPath = folderPath; } } @@ -168,17 +167,17 @@ void FolderSelector2::onSelectDir(wxCommandEvent& event) const Zstring newFolder = toZ(dirPicker.GetPath()); #endif - setFolderPath(newFolder, &dirpath_, dirpath_, staticText_); + setFolderPath(newFolder, &folderPathCtrl_, folderPathCtrl_, staticText_); } Zstring FolderSelector2::getPath() const { - return toZ(dirpath_.GetValue()); + return toZ(folderPathCtrl_.GetValue()); } void FolderSelector2::setPath(const Zstring& dirpath) { - setFolderPath(dirpath, &dirpath_, dirpath_, staticText_); + setFolderPath(dirpath, &folderPathCtrl_, folderPathCtrl_, staticText_); } diff --git a/FreeFileSync/Source/RealtimeSync/folder_selector2.h b/FreeFileSync/Source/RealtimeSync/folder_selector2.h index 14093117..fb748197 100644 --- a/FreeFileSync/Source/RealtimeSync/folder_selector2.h +++ b/FreeFileSync/Source/RealtimeSync/folder_selector2.h @@ -22,8 +22,8 @@ class FolderSelector2 : public wxEvtHandler public: FolderSelector2(wxWindow& dropWindow, wxButton& selectButton, - wxTextCtrl& dirpath, - wxStaticText* staticText = nullptr); //optional + wxTextCtrl& folderPathCtrl, + wxStaticText* staticText); //optional ~FolderSelector2(); @@ -31,15 +31,15 @@ public: void setPath(const Zstring& dirpath); private: - void onMouseWheel (wxMouseEvent& event); - void onFilesDropped (FileDropEvent& event); - void onWriteDirManually(wxCommandEvent& event); - void onSelectDir (wxCommandEvent& event); + void onMouseWheel (wxMouseEvent& event); + void onFilesDropped (FileDropEvent& event); + void onEditFolderPath(wxCommandEvent& event); + void onSelectDir (wxCommandEvent& event); wxWindow& dropWindow_; wxButton& selectButton_; - wxTextCtrl& dirpath_; - wxStaticText* staticText_; //optional + wxTextCtrl& folderPathCtrl_; + wxStaticText* staticText_ = nullptr; //optional }; } diff --git a/FreeFileSync/Source/RealtimeSync/main_dlg.cpp b/FreeFileSync/Source/RealtimeSync/main_dlg.cpp index 1472a301..e51e6f73 100644 --- a/FreeFileSync/Source/RealtimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealtimeSync/main_dlg.cpp @@ -39,7 +39,7 @@ class DirectoryPanel : public FolderGenerated public: DirectoryPanel(wxWindow* parent) : FolderGenerated(parent), - dirpath_(*this, *m_buttonSelectDir, *m_txtCtrlDirectory) + folderSelector_(*this, *m_buttonSelectDir, *m_txtCtrlDirectory, nullptr /*staticText*/) { #ifdef ZEN_LINUX //file drag and drop directly into the text control unhelpfully inserts in format "file://..<cr><nl>"; see folder_history_box.cpp @@ -48,11 +48,11 @@ public: #endif } - void setPath(const Zstring& dirpath) { dirpath_.setPath(dirpath); } - Zstring getPath() const { return dirpath_.getPath(); } + void setPath(const Zstring& dirpath) { folderSelector_.setPath(dirpath); } + Zstring getPath() const { return folderSelector_.getPath(); } private: - zen::FolderSelector2 dirpath_; + zen::FolderSelector2 folderSelector_; }; @@ -339,9 +339,9 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event) void MainDialog::onFilesDropped(FileDropEvent& event) { - const auto& files = event.getFiles(); - if (!files.empty()) - loadConfig(utfCvrtTo<Zstring>(files[0])); + const auto& filePaths = event.getPaths(); + if (!filePaths.empty()) + loadConfig(utfCvrtTo<Zstring>(filePaths[0])); } diff --git a/FreeFileSync/Source/RealtimeSync/monitor.cpp b/FreeFileSync/Source/RealtimeSync/monitor.cpp index 42e74eef..b196f7bd 100644 --- a/FreeFileSync/Source/RealtimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealtimeSync/monitor.cpp @@ -25,14 +25,14 @@ namespace const int CHECK_DIR_INTERVAL = 1; //unit: [s] -std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& dirpathPhrases) //throw FileError +std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& folderPathPhrases) //throw FileError { - std::set<Zstring, LessFilePath> dirpaths; //make unique - for (const Zstring& phrase : std::set<Zstring, LessFilePath>(dirpathPhrases.begin(), dirpathPhrases.end())) + std::set<Zstring, LessFilePath> folderPaths; //make unique + for (const Zstring& phrase : std::set<Zstring, LessFilePath>(folderPathPhrases.begin(), folderPathPhrases.end())) //make unique: no need to resolve duplicate phrases more than once! (consider "[volume name]" syntax) -> shouldn't this be already buffered by OS? - dirpaths.insert(getResolvedDirectoryPath(phrase)); + folderPaths.insert(getResolvedDirectoryPath(phrase)); - return std::vector<Zstring>(dirpaths.begin(), dirpaths.end()); + return std::vector<Zstring>(folderPaths.begin(), folderPaths.end()); } @@ -46,42 +46,42 @@ struct WaitResult }; WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {} - WaitResult(const Zstring& dirpath) : type(CHANGE_DIR_MISSING), dirpath_(dirpath) {} + WaitResult(const Zstring& folderPath) : type(CHANGE_DIR_MISSING), folderPath_(folderPath) {} ChangeType type; zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory - Zstring dirpath_; //for type == CHANGE_DIR_MISSING + Zstring folderPath_; //for type == CHANGE_DIR_MISSING }; -WaitResult waitForChanges(const std::vector<Zstring>& dirpathPhrases, //throw FileError +WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw FileError const std::function<void(bool readyForSync)>& onRefreshGui) { - const std::vector<Zstring> dirpathsFmt = getFormattedDirs(dirpathPhrases); //throw FileError - if (dirpathsFmt.empty()) //pathological case, but we have to check else this function will wait endlessly + const std::vector<Zstring> folderPathsFmt = getFormattedDirs(folderPathPhrases); //throw FileError + if (folderPathsFmt.empty()) //pathological case, but we have to check else this function will wait endlessly throw zen::FileError(_("A folder input field is empty.")); //should have been checked by caller! //detect when volumes are removed/are not available anymore std::vector<std::pair<Zstring, std::shared_ptr<DirWatcher>>> watches; - for (const Zstring& dirpathFmt : dirpathsFmt) + for (const Zstring& folderPathFmt : folderPathsFmt) { try { //a non-existent network path may block, so check existence asynchronously! - auto ftDirExists = runAsync([=] { return zen::dirExists(dirpathFmt); }); + auto ftDirExists = runAsync([=] { return zen::dirExists(folderPathFmt); }); //we need to check dirExists(), not somethingExists(): it's not clear if DirWatcher detects a type clash (file instead of directory!) - while (ftDirExists.wait_for(boost::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)) != boost::future_status::ready) + while (ftDirExists.wait_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)) != std::future_status::ready) onRefreshGui(false); //may throw! if (!ftDirExists.get()) - return WaitResult(dirpathFmt); + return WaitResult(folderPathFmt); - watches.emplace_back(dirpathFmt, std::make_shared<DirWatcher>(dirpathFmt)); //throw FileError + watches.emplace_back(folderPathFmt, std::make_shared<DirWatcher>(folderPathFmt)); //throw FileError } catch (FileError&) { - if (!somethingExists(dirpathFmt)) //a benign(?) race condition with FileError - return WaitResult(dirpathFmt); + if (!somethingExists(folderPathFmt)) //a benign(?) race condition with FileError + return WaitResult(folderPathFmt); throw; } } @@ -104,13 +104,13 @@ WaitResult waitForChanges(const std::vector<Zstring>& dirpathPhrases, //throw Fi for (auto it = watches.begin(); it != watches.end(); ++it) { - const Zstring& dirpath = it->first; + const Zstring& folderPath = it->first; DirWatcher& watcher = *(it->second); //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories! if (checkDirExistNow) - if (!dirExists(dirpath)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED -> somethingExists() is NOT sufficient here! - return WaitResult(dirpath); + if (!dirExists(folderPath)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED -> somethingExists() is NOT sufficient here! + return WaitResult(folderPath); try { std::vector<DirWatcher::Entry> changedItems = watcher.getChanges([&] { onRefreshGui(false); /*may throw!*/ }); //throw FileError @@ -133,39 +133,39 @@ WaitResult waitForChanges(const std::vector<Zstring>& dirpathPhrases, //throw Fi } catch (FileError&) { - if (!somethingExists(dirpath)) //a benign(?) race condition with FileError - return WaitResult(dirpath); + if (!somethingExists(folderPath)) //a benign(?) race condition with FileError + return WaitResult(folderPath); throw; } } - boost::this_thread::sleep_for(boost::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)); onRefreshGui(true); //throw ?: may start sync at this presumably idle time } } //wait until all directories become available (again) + logs in network share -void waitForMissingDirs(const std::vector<Zstring>& dirpathPhrases, //throw FileError - const std::function<void(const Zstring& dirpath)>& onRefreshGui) +void waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw FileError + const std::function<void(const Zstring& folderPath)>& onRefreshGui) { for (;;) { bool allExisting = true; //support specifying volume by name => call getResolvedDirectoryPath() repeatedly - for (const Zstring& dirpathFmt : getFormattedDirs(dirpathPhrases)) //throw FileError + for (const Zstring& folderPathFmt : getFormattedDirs(folderPathPhrases)) //throw FileError { auto ftDirExisting = runAsync([=]() -> bool { #ifdef ZEN_WIN //1. login to network share, if necessary -> we probably do NOT want multiple concurrent runs: GUI!? - loginNetworkShare(dirpathFmt, false); //login networks shares, no PW prompt -> is this really RTS's job? + loginNetworkShare(folderPathFmt, false); //login networks shares, no PW prompt -> is this really RTS's job? #endif //2. check dir existence - return zen::dirExists(dirpathFmt); + return zen::dirExists(folderPathFmt); }); - while (ftDirExisting.wait_for(boost::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)) != boost::future_status::ready) - onRefreshGui(dirpathFmt); //may throw! + while (ftDirExisting.wait_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)) != std::future_status::ready) + onRefreshGui(folderPathFmt); //may throw! if (!ftDirExisting.get()) { @@ -175,8 +175,8 @@ void waitForMissingDirs(const std::vector<Zstring>& dirpathPhrases, //throw File static_assert(CHECK_DIR_INTERVAL * 1000 % refreshInterval == 0, ""); for (int i = 0; i < CHECK_DIR_INTERVAL * 1000 / refreshInterval; ++i) { - onRefreshGui(dirpathFmt); //may throw! - boost::this_thread::sleep_for(boost::chrono::milliseconds(refreshInterval)); //throw boost::thread_interrupted + onRefreshGui(folderPathFmt); //may throw! + std::this_thread::sleep_for(std::chrono::milliseconds(refreshInterval)); } break; } @@ -206,9 +206,9 @@ struct ExecCommandNowException {}; } -void rts::monitorDirectories(const std::vector<Zstring>& dirpathPhrases, unsigned int delay, rts::MonitorCallback& callback) +void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, unsigned int delay, rts::MonitorCallback& callback) { - if (dirpathPhrases.empty()) + if (folderPathPhrases.empty()) { assert(false); return; @@ -217,7 +217,7 @@ void rts::monitorDirectories(const std::vector<Zstring>& dirpathPhrases, unsigne auto execMonitoring = [&] //throw FileError { callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING); - waitForMissingDirs(dirpathPhrases, [&](const Zstring& dirpath) { callback.requestUiRefresh(); }); //throw FileError + waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { callback.requestUiRefresh(); }); //throw FileError callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE); //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include) @@ -231,7 +231,7 @@ void rts::monitorDirectories(const std::vector<Zstring>& dirpathPhrases, unsigne while (true) //loop over detected changes { //wait for changes (and for all directories to become available) - WaitResult res = waitForChanges(dirpathPhrases, [&](bool readyForSync) //throw FileError, ExecCommandNowException + WaitResult res = waitForChanges(folderPathPhrases, [&](bool readyForSync) //throw FileError, ExecCommandNowException { if (readyForSync) if (nextExecDate <= std::time(nullptr)) @@ -242,7 +242,7 @@ void rts::monitorDirectories(const std::vector<Zstring>& dirpathPhrases, unsigne { case WaitResult::CHANGE_DIR_MISSING: //don't execute the command before all directories are available! callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING); - waitForMissingDirs(dirpathPhrases, [&](const Zstring& dirpath) { callback.requestUiRefresh(); }); //throw FileError + waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { callback.requestUiRefresh(); }); //throw FileError callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE); break; diff --git a/FreeFileSync/Source/RealtimeSync/monitor.h b/FreeFileSync/Source/RealtimeSync/monitor.h index 87915b41..a5aee773 100644 --- a/FreeFileSync/Source/RealtimeSync/monitor.h +++ b/FreeFileSync/Source/RealtimeSync/monitor.h @@ -29,7 +29,7 @@ struct MonitorCallback virtual void requestUiRefresh () = 0; virtual void reportError(const std::wstring& msg) = 0; //automatically retries after return! }; -void monitorDirectories(const std::vector<Zstring>& dirpathPhrases, +void monitorDirectories(const std::vector<Zstring>& folderPathPhrases, //non-formatted dirnames that yet require call to getFormattedDirectoryName(); empty directories must be checked by caller! unsigned int delay, MonitorCallback& callback); diff --git a/FreeFileSync/Source/RealtimeSync/tray_menu.cpp b/FreeFileSync/Source/RealtimeSync/tray_menu.cpp index 6eb71946..7357e7fc 100644 --- a/FreeFileSync/Source/RealtimeSync/tray_menu.cpp +++ b/FreeFileSync/Source/RealtimeSync/tray_menu.cpp @@ -319,7 +319,7 @@ rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& conf case ConfirmationButton::CANCEL: throw AbortMonitoring(SHOW_GUI); } - boost::this_thread::sleep_for(boost::chrono::milliseconds(UI_UPDATE_INTERVAL)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); } } diff --git a/FreeFileSync/Source/RealtimeSync/xml_proc.cpp b/FreeFileSync/Source/RealtimeSync/xml_proc.cpp index fdd3b507..68bcf5ba 100644 --- a/FreeFileSync/Source/RealtimeSync/xml_proc.cpp +++ b/FreeFileSync/Source/RealtimeSync/xml_proc.cpp @@ -84,26 +84,26 @@ void xmlAccess::writeConfig(const XmlRealConfig& config, const Zstring& filepath namespace { -xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& batchCfg, const Zstring& filepath) +xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& batchCfg, const Zstring& batchFilePath) { std::set<Zstring, LessFilePath> uniqueFolders; //add main folders - uniqueFolders.insert(batchCfg.mainCfg.firstPair.dirpathPhraseLeft); - uniqueFolders.insert(batchCfg.mainCfg.firstPair.dirpathPhraseRight); + uniqueFolders.insert(batchCfg.mainCfg.firstPair.folderPathPhraseLeft_); + uniqueFolders.insert(batchCfg.mainCfg.firstPair.folderPathPhraseRight_); //additional folders for (const FolderPairEnh& fp : batchCfg.mainCfg.additionalPairs) { - uniqueFolders.insert(fp.dirpathPhraseLeft); - uniqueFolders.insert(fp.dirpathPhraseRight); + uniqueFolders.insert(fp.folderPathPhraseLeft_); + uniqueFolders.insert(fp.folderPathPhraseRight_); } uniqueFolders.erase(Zstring()); xmlAccess::XmlRealConfig output; output.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); - output.commandline = Zstr("\"") + zen::getFreeFileSyncLauncher() + Zstr("\" \"") + filepath + Zstr("\""); + output.commandline = Zstr("\"") + zen::getFreeFileSyncLauncher() + Zstr("\" \"") + batchFilePath + Zstr("\""); return output; } } diff --git a/FreeFileSync/Source/algorithm.cpp b/FreeFileSync/Source/algorithm.cpp index e49fd0a0..952fd0af 100644 --- a/FreeFileSync/Source/algorithm.cpp +++ b/FreeFileSync/Source/algorithm.cpp @@ -10,6 +10,8 @@ #include "lib/norm_filter.h" #include "lib/db_file.h" #include "lib/cmp_filetime.h" +#include "lib/status_handler_impl.h" +#include "fs/concrete.h" using namespace zen; //using namespace std::rel_ops; @@ -1081,10 +1083,10 @@ void zen::applyTimeSpanFilter(FolderComparison& folderCmp, std::int64_t timeFrom //############################################################################################################ -std::pair<std::wstring, int> zen::deleteFromGridAndHDPreview(const std::vector<FileSystemObject*>& selectionLeft, - const std::vector<FileSystemObject*>& selectionRight) +std::pair<std::wstring, int> zen::getSelectedItemsAsString(const std::vector<FileSystemObject*>& selectionLeft, + const std::vector<FileSystemObject*>& selectionRight) { - //don't use wxString! its imprudent linear allocation strategy brings perf down to a crawl! + //don't use wxString! its rather dumb linear allocation strategy brings perf down to a crawl! std::wstring fileList; // int totalDelCount = 0; @@ -1108,170 +1110,316 @@ std::pair<std::wstring, int> zen::deleteFromGridAndHDPreview(const std::vector<F namespace { -template <typename Function> inline -bool tryReportingError(Function cmd, DeleteFilesHandler& handler) //throw X?; return "true" on success, "false" if error was ignored +struct FSObjectLambdaVisitor : public FSObjectVisitor { - for (;;) + static void visit(FileSystemObject& fsObj, + const std::function<void(const DirPair& dirObj )>& onDir, + const std::function<void(const FilePair& fileObj)>& onFile, + const std::function<void(const SymlinkPair& linkObj)>& onSymlink) + { + FSObjectLambdaVisitor visitor(onDir, onFile, onSymlink); + fsObj.accept(visitor); + } + +private: + FSObjectLambdaVisitor(const std::function<void(const DirPair& dirObj )>& onDir, + const std::function<void(const FilePair& fileObj)>& onFile, + const std::function<void(const SymlinkPair& linkObj)>& onSymlink) : onDir_(onDir), onFile_(onFile), onSymlink_(onSymlink) {} + + void visit(const DirPair& dirObj ) override { if (onDir_) onDir_ (dirObj ); } + void visit(const FilePair& fileObj) override { if (onFile_) onFile_ (fileObj); } + void visit(const SymlinkPair& linkObj) override { if (onSymlink_) onSymlink_(linkObj); } + + const std::function<void(const DirPair& dirObj )> onDir_; + const std::function<void(const FilePair& fileObj)> onFile_; + const std::function<void(const SymlinkPair& linkObj)> onSymlink_; +}; + + +template <SelectedSide side> +void copyToAlternateFolderFrom(const std::vector<FileSystemObject*>& rowsToCopy, + ABF& abfTarget, + bool keepRelPaths, + bool overwriteIfExists, + ProcessCallback& callback) +{ + auto notifyItemCopy = [&](const std::wstring& statusText, const std::wstring& displayPath) + { + callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); + }; + + const std::wstring txtCreatingFolder(_("Creating folder %x" )); + const std::wstring txtCreatingFile (_("Creating file %x" )); + const std::wstring txtCreatingLink (_("Creating symbolic link %x")); + + auto copyItem = [&](FileSystemObject& fsObj, const Zstring& relPath) //throw FileError + { + const AbstractPathRef targetPath = abfTarget.getAbstractPath(relPath); + + const std::function<void()> deleteTargetItem = [&] + { + if (overwriteIfExists) + try + { + //file or (broken) file-symlink: + ABF::removeFile(targetPath); //throw FileError + } + catch (FileError&) + { + //folder or folder-symlink: + if (ABF::folderExists(targetPath)) //directory or dir-symlink + ABF::removeFolderRecursively(targetPath, nullptr /*onBeforeFileDeletion*/, nullptr /*onBeforeFolderDeletion*/); //throw FileError + else + throw; + } + }; + + FSObjectLambdaVisitor::visit(fsObj, + [&](const DirPair& dirObj) + { + StatisticsReporter statReporter(1, 0, callback); + notifyItemCopy(txtCreatingFolder, ABF::getDisplayPath(targetPath)); + + try + { + //deleteTargetItem(); -> never delete pre-existing folders!!! => might delete child items we just copied! + ABF::copyNewFolder(dirObj.getAbstractPath<side>(), targetPath, false /*copyFilePermissions*/); //throw FileError + } + catch (const FileError&) { if (!ABF::folderExists(targetPath)) throw; } //might already exist: see creation of intermediate directories below + statReporter.reportDelta(1, 0); + + statReporter.reportFinished(); + }, + + [&](const FilePair& fileObj) + { + StatisticsReporter statReporter(1, fileObj.getFileSize<side>(), callback); + notifyItemCopy(txtCreatingFile, ABF::getDisplayPath(targetPath)); + + auto onNotifyCopyStatus = [&](std::int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; + ABF::copyFileTransactional(fileObj.getAbstractPath<side>(), targetPath, //throw FileError, ErrorFileLocked + false /*copyFilePermissions*/, true /*transactionalCopy*/, deleteTargetItem, onNotifyCopyStatus); + statReporter.reportDelta(1, 0); + + statReporter.reportFinished(); + }, + + [&](const SymlinkPair& linkObj) + { + StatisticsReporter statReporter(1, 0, callback); + notifyItemCopy(txtCreatingLink, ABF::getDisplayPath(targetPath)); + + deleteTargetItem(); + ABF::copySymlink(linkObj.getAbstractPath<side>(), targetPath, false /*copyFilePermissions*/); //throw FileError + statReporter.reportDelta(1, 0); + + statReporter.reportFinished(); + }); + }; + + for (FileSystemObject* fsObj : rowsToCopy) + tryReportingError([&] + { + const Zstring& relPath = keepRelPaths ? fsObj->getRelativePath<side>() : fsObj->getItemName<side>(); try { - cmd(); //throw FileError - return true; + copyItem(*fsObj, relPath); //throw FileError } - catch (FileError& error) + catch (FileError&) { - switch (handler.reportError(error.toString())) //throw X? + //create intermediate directories if missing + const AbstractPathRef targetParentPath = abfTarget.getAbstractPath(beforeLast(relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); + if (!ABF::somethingExists(targetParentPath)) //->(minor) file system race condition! { - case DeleteFilesHandler::IGNORE_ERROR: - return false; - case DeleteFilesHandler::RETRY: - break; //continue with loop - default: - assert(false); - break; + ABF::createFolderRecursively(targetParentPath); //throw FileError + //retry: this should work now! + copyItem(*fsObj, relPath); //throw FileError } + else + throw; } + }, callback); //throw X? +} } -template <SelectedSide side> -void categorize(const std::set<FileSystemObject*>& rowsIn, - std::vector<FileSystemObject*>& deletePermanent, - std::vector<FileSystemObject*>& deleteRecyler, - bool useRecycleBin, - std::map<const ABF*, bool, ABF::LessItemPath>& recyclerSupported, - DeleteFilesHandler& callback) +void zen::copyToAlternateFolder(const std::vector<FileSystemObject*>& rowsToCopyOnLeft, + const std::vector<FileSystemObject*>& rowsToCopyOnRight, + const Zstring& targetFolderPathPhrase, + bool keepRelPaths, + bool overwriteIfExists, + ProcessCallback& callback) { - auto hasRecycler = [&](const ABF& baseFolder) -> bool - { - auto it = recyclerSupported.find(&baseFolder); //perf: avoid duplicate checks! - if (it != recyclerSupported.end()) - return it->second; + std::vector<FileSystemObject*> itemSelectionLeft = rowsToCopyOnLeft; + std::vector<FileSystemObject*> itemSelectionRight = rowsToCopyOnRight; + vector_remove_if(itemSelectionLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<LEFT_SIDE >(); }); + vector_remove_if(itemSelectionRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); }); - const std::wstring msg = replaceCpy(_("Checking recycle bin availability for folder %x..."), L"%x", - fmtPath(ABF::getDisplayPath(baseFolder.getAbstractPath()))); - bool recSupported = false; - tryReportingError([&]{ - recSupported = baseFolder.supportsRecycleBin([&] { callback.reportStatus(msg); /*may throw*/ }); //throw FileError - }, callback); //throw X? + const int itemCount = static_cast<int>(itemSelectionLeft.size() + itemSelectionRight.size()); + std::int64_t dataToProcess = 0; - recyclerSupported.emplace(&baseFolder, recSupported); - return recSupported; - }; + for (FileSystemObject* fsObj : itemSelectionLeft) + FSObjectLambdaVisitor::visit(*fsObj, nullptr /*onDir*/, + [&](const FilePair& fileObj) {dataToProcess += static_cast<std::int64_t>(fileObj.getFileSize<LEFT_SIDE>()); }, nullptr /*onSymlink*/); - for (FileSystemObject* row : rowsIn) - if (!row->isEmpty<side>()) - { - if (useRecycleBin && hasRecycler(row->root().getABF<side>())) //Windows' ::SHFileOperation() will delete permanently anyway, but we have a superior deletion routine - deleteRecyler.push_back(row); - else - deletePermanent.push_back(row); - } + for (FileSystemObject* fsObj : itemSelectionRight) + FSObjectLambdaVisitor::visit(*fsObj, nullptr /*onDir*/, + [&](const FilePair& fileObj) {dataToProcess += static_cast<std::int64_t>(fileObj.getFileSize<RIGHT_SIDE>()); }, nullptr /*onSymlink*/); + + callback.initNewPhase(itemCount, dataToProcess, ProcessCallback::PHASE_SYNCHRONIZING); //throw X + + std::unique_ptr<ABF> abfTarget = createAbstractBaseFolder(targetFolderPathPhrase); + + copyToAlternateFolderFrom<LEFT_SIDE >(itemSelectionLeft, *abfTarget, keepRelPaths, overwriteIfExists, callback); + copyToAlternateFolderFrom<RIGHT_SIDE>(itemSelectionRight, *abfTarget, keepRelPaths, overwriteIfExists, callback); } +//############################################################################################################ +namespace +{ template <SelectedSide side> -struct ItemDeleter : public FSObjectVisitor //throw FileError, but nothrow constructor!!! +void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, + bool useRecycleBin, + ProcessCallback& callback) { - ItemDeleter(bool useRecycleBin, DeleteFilesHandler& handler) : - handler_(handler), useRecycleBin_(useRecycleBin) + auto notifyItemDeletion = [&](const std::wstring& statusText, const std::wstring& displayPath) { - if (useRecycleBin_) - { - txtRemovingFile = _("Moving file %x to the recycle bin" ); - txtRemovingDirectory = _("Moving folder %x to the recycle bin" ); - txtRemovingSymlink = _("Moving symbolic link %x to the recycle bin"); - } - else - { - txtRemovingFile = _("Deleting file %x" ); - txtRemovingDirectory = _("Deleting folder %x" ); - txtRemovingSymlink = _("Deleting symbolic link %x"); - } - } + callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); + }; - void visit(const FilePair& fileObj) override - { - notifyFileDeletion(ABF::getDisplayPath(fileObj.getAbstractPath<side>())); + std::wstring txtRemovingFile; + std::wstring txtRemovingDirectory; + std::wstring txtRemovingSymlink; - if (useRecycleBin_) - ABF::recycleItemDirectly(fileObj.getAbstractPath<side>()); //throw FileError - else - ABF::removeFile(fileObj.getAbstractPath<side>()); //throw FileError + if (useRecycleBin) + { + txtRemovingFile = _("Moving file %x to the recycle bin"); + txtRemovingDirectory = _("Moving folder %x to the recycle bin"); + txtRemovingSymlink = _("Moving symbolic link %x to the recycle bin"); } - - void visit(const SymlinkPair& linkObj) override + else { - notifySymlinkDeletion(ABF::getDisplayPath(linkObj.getAbstractPath<side>())); - - if (useRecycleBin_) - ABF::recycleItemDirectly(linkObj.getAbstractPath<side>()); //throw FileError - else - { - if (ABF::dirExists(linkObj.getAbstractPath<side>())) //dir symlink - ABF::removeFolderSimple(linkObj.getAbstractPath<side>()); //throw FileError - else //file symlink, broken symlink - ABF::removeFile(linkObj.getAbstractPath<side>()); //throw FileError - } + txtRemovingFile = _("Deleting file %x"); + txtRemovingDirectory = _("Deleting folder %x"); + txtRemovingSymlink = _("Deleting symbolic link %x"); } - void visit(const DirPair& dirObj) override + + for (FileSystemObject* fsObj : rowsToDelete) //all pointers are required(!) to be bound + tryReportingError([&] { - notifyDirectoryDeletion(ABF::getDisplayPath(dirObj.getAbstractPath<side>())); //notfied twice; see below -> no big deal + StatisticsReporter statReporter(1, 0, callback); - if (useRecycleBin_) - ABF::recycleItemDirectly(dirObj.getAbstractPath<side>()); //throw FileError - else + if (!fsObj->isEmpty<side>()) //element may be implicitly deleted, e.g. if parent folder was deleted first { - auto onBeforeFileDeletion = [&](const std::wstring& displayPath) { this->notifyFileDeletion (displayPath); }; //without "this->" GCC 4.7.2 runtime crash on Debian - auto onBeforeDirDeletion = [&](const std::wstring& displayPath) { this->notifyDirectoryDeletion(displayPath); }; + FSObjectLambdaVisitor::visit(*fsObj, + [&](const DirPair& dirObj) + { + if (useRecycleBin) + { + notifyItemDeletion(txtRemovingDirectory, ABF::getDisplayPath(dirObj.getAbstractPath<side>())); + ABF::recycleItemDirectly(dirObj.getAbstractPath<side>()); //throw FileError + statReporter.reportDelta(1, 0); + } + else + { + auto onBeforeFileDeletion = [&](const std::wstring& displayPath) + { + statReporter.reportDelta(1, 0); + notifyItemDeletion(txtRemovingFile, displayPath); + }; + auto onBeforeDirDeletion = [&](const std::wstring& displayPath) + { + statReporter.reportDelta(1, 0); + notifyItemDeletion(txtRemovingDirectory, displayPath); + }; - ABF::removeFolderRecursively(dirObj.getAbstractPath<side>(), onBeforeFileDeletion, onBeforeDirDeletion); //throw FileError - } - } + ABF::removeFolderRecursively(dirObj.getAbstractPath<side>(), onBeforeFileDeletion, onBeforeDirDeletion); //throw FileError + } + }, -private: - void notifyFileDeletion (const std::wstring& displayPath) { notifyItemDeletion(txtRemovingFile , displayPath); } - void notifyDirectoryDeletion(const std::wstring& displayPath) { notifyItemDeletion(txtRemovingDirectory, displayPath); } - void notifySymlinkDeletion (const std::wstring& displayPath) { notifyItemDeletion(txtRemovingSymlink , displayPath); } + [&](const FilePair& fileObj) + { + notifyItemDeletion(txtRemovingFile, ABF::getDisplayPath(fileObj.getAbstractPath<side>())); - void notifyItemDeletion(const std::wstring& statusText, const std::wstring& displayPath) - { - handler_.reportStatus(replaceCpy(statusText, L"%x", fmtPath(displayPath))); - } + if (useRecycleBin) + ABF::recycleItemDirectly(fileObj.getAbstractPath<side>()); //throw FileError + else + ABF::removeFile(fileObj.getAbstractPath<side>()); //throw FileError + statReporter.reportDelta(1, 0); + }, - DeleteFilesHandler& handler_; - const bool useRecycleBin_; + [&](const SymlinkPair& linkObj) + { + notifyItemDeletion(txtRemovingSymlink, ABF::getDisplayPath(linkObj.getAbstractPath<side>())); - std::wstring txtRemovingFile; - std::wstring txtRemovingDirectory; - std::wstring txtRemovingSymlink; -}; + if (useRecycleBin) + ABF::recycleItemDirectly(linkObj.getAbstractPath<side>()); //throw FileError + else + { + if (ABF::folderExists(linkObj.getAbstractPath<side>())) //dir symlink + ABF::removeFolderSimple(linkObj.getAbstractPath<side>()); //throw FileError + else //file symlink, broken symlink + ABF::removeFile(linkObj.getAbstractPath<side>()); //throw FileError + } + statReporter.reportDelta(1, 0); + }); + + fsObj->removeObject<side>(); //if directory: removes recursively! + } + + statReporter.reportFinished(); + + }, callback); //throw X? +} template <SelectedSide side> -void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& ptrList, - bool useRecycleBin, - DeleteFilesHandler& handler) +void categorize(const std::vector<FileSystemObject*>& rows, + std::vector<FileSystemObject*>& deletePermanent, + std::vector<FileSystemObject*>& deleteRecyler, + bool useRecycleBin, + std::map<const ABF*, bool, ABF::LessItemPath>& recyclerSupported, + ProcessCallback& callback) { - ItemDeleter<side> deleter(useRecycleBin, handler); + auto hasRecycler = [&](const ABF& baseFolder) -> bool + { + auto it = recyclerSupported.find(&baseFolder); //perf: avoid duplicate checks! + if (it != recyclerSupported.end()) + return it->second; - for (FileSystemObject* fsObj : ptrList) //all pointers are required(!) to be bound - if (!fsObj->isEmpty<side>()) //element may be implicitly deleted, e.g. if parent folder was deleted first - tryReportingError([&] + const std::wstring msg = replaceCpy(_("Checking recycle bin availability for folder %x..."), L"%x", + fmtPath(ABF::getDisplayPath(baseFolder.getAbstractPath()))); + + bool recSupported = false; + tryReportingError([&]{ + recSupported = baseFolder.supportsRecycleBin([&] { callback.reportStatus(msg); /*may throw*/ }); //throw FileError + }, callback); //throw X? + + recyclerSupported.emplace(&baseFolder, recSupported); + return recSupported; + }; + + for (FileSystemObject* row : rows) + if (!row->isEmpty<side>()) { - fsObj->accept(deleter); //throw FileError - fsObj->removeObject<side>(); //if directory: removes recursively! - }, handler); //throw X? + if (useRecycleBin && hasRecycler(row->root().getABF<side>())) //Windows' ::SHFileOperation() will delete permanently anyway, but we have a superior deletion routine + deleteRecyler.push_back(row); + else + deletePermanent.push_back(row); + } } } + void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows const std::vector<FileSystemObject*>& rowsToDeleteOnRight, //all pointers need to be bound! FolderComparison& folderCmp, //attention: rows will be physically deleted! const std::vector<DirectionConfig>& directCfgs, bool useRecycleBin, - DeleteFilesHandler& statusHandler, - bool& warningRecyclerMissing) + bool& warningRecyclerMissing, + ProcessCallback& callback) { if (folderCmp.empty()) return; @@ -1283,26 +1431,32 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete for (auto it = folderCmp.begin(); it != folderCmp.end(); ++it) baseDirCfgs[&** it] = directCfgs[it - folderCmp.begin()]; - std::set<FileSystemObject*> deleteLeft (rowsToDeleteOnLeft .begin(), rowsToDeleteOnLeft .end()); - std::set<FileSystemObject*> deleteRight(rowsToDeleteOnRight.begin(), rowsToDeleteOnRight.end()); + std::vector<FileSystemObject*> deleteLeft = rowsToDeleteOnLeft; + std::vector<FileSystemObject*> deleteRight = rowsToDeleteOnRight; - set_remove_if(deleteLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<LEFT_SIDE >(); }); //still needed? - set_remove_if(deleteRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); }); // + vector_remove_if(deleteLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<LEFT_SIDE >(); }); //needed? + vector_remove_if(deleteRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); }); //yes, for correct stats: + + const int itemCount = static_cast<int>(deleteLeft.size() + deleteRight.size()); + callback.initNewPhase(itemCount, 0, ProcessCallback::PHASE_SYNCHRONIZING); //throw X //ensure cleanup: redetermination of sync-directions and removal of invalid rows - auto updateDirection = [&]() + auto updateDirection = [&] { //update sync direction: we cannot do a full redetermination since the user may already have entered manual changes - std::set<FileSystemObject*> deletedTotal = deleteLeft; - deletedTotal.insert(deleteRight.begin(), deleteRight.end()); + std::vector<FileSystemObject*> rowsToDelete; + vector_append(rowsToDelete, deleteLeft); + vector_append(rowsToDelete, deleteRight); + removeDuplicates(rowsToDelete); - for (auto it = deletedTotal.begin(); it != deletedTotal.end(); ++it) + for (auto it = rowsToDelete.begin(); it != rowsToDelete.end(); ++it) { FileSystemObject& fsObj = **it; //all pointers are required(!) to be bound if (fsObj.isEmpty<LEFT_SIDE>() != fsObj.isEmpty<RIGHT_SIDE>()) //make sure objects exists on one side only { auto cfgIter = baseDirCfgs.find(&fsObj.root()); + assert(cfgIter != baseDirCfgs.end()); if (cfgIter != baseDirCfgs.end()) { SyncDirection newDir = SyncDirection::NONE; @@ -1317,8 +1471,6 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete setSyncDirectionRec(newDir, fsObj); //set new direction (recursively) } - else - assert(!"this should not happen!"); } } @@ -1334,8 +1486,8 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete std::vector<FileSystemObject*> deleteRecylerRight; std::map<const ABF*, bool, ABF::LessItemPath> recyclerSupported; - categorize<LEFT_SIDE >(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, recyclerSupported, statusHandler); - categorize<RIGHT_SIDE>(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, recyclerSupported, statusHandler); + categorize<LEFT_SIDE >(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, recyclerSupported, callback); + categorize<RIGHT_SIDE>(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, recyclerSupported, callback); //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong if (useRecycleBin && @@ -1347,12 +1499,12 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete if (!item.second) msg += L"\n" + ABF::getDisplayPath(item.first->getAbstractPath()); - statusHandler.reportWarning(msg, warningRecyclerMissing); //throw? + callback.reportWarning(msg, warningRecyclerMissing); //throw? } - deleteFromGridAndHDOneSide<LEFT_SIDE>(deleteRecylerLeft, true, statusHandler); - deleteFromGridAndHDOneSide<LEFT_SIDE>(deletePermanentLeft, false, statusHandler); + deleteFromGridAndHDOneSide<LEFT_SIDE>(deleteRecylerLeft, true, callback); + deleteFromGridAndHDOneSide<LEFT_SIDE>(deletePermanentLeft, false, callback); - deleteFromGridAndHDOneSide<RIGHT_SIDE>(deleteRecylerRight, true, statusHandler); - deleteFromGridAndHDOneSide<RIGHT_SIDE>(deletePermanentRight, false, statusHandler); + deleteFromGridAndHDOneSide<RIGHT_SIDE>(deleteRecylerRight, true, callback); + deleteFromGridAndHDOneSide<RIGHT_SIDE>(deletePermanentRight, false, callback); } diff --git a/FreeFileSync/Source/algorithm.h b/FreeFileSync/Source/algorithm.h index aab41ae1..3f1235f3 100644 --- a/FreeFileSync/Source/algorithm.h +++ b/FreeFileSync/Source/algorithm.h @@ -10,6 +10,7 @@ #include <functional> #include "file_hierarchy.h" #include "lib/soft_filter.h" +#include "process_callback.h" namespace zen { @@ -41,33 +42,27 @@ void applyTimeSpanFilter(FolderComparison& folderCmp, std::int64_t timeFrom, std void setActiveStatus(bool newStatus, FolderComparison& folderCmp); //activate or deactivate all rows void setActiveStatus(bool newStatus, FileSystemObject& fsObj); //activate or deactivate row: (not recursively anymore) - -//manual deletion of files on main grid -std::pair<std::wstring, int> deleteFromGridAndHDPreview( //returns string with elements to be deleted and total count of selected(!) objects, NOT total files/dirs! +std::pair<std::wstring, int> getSelectedItemsAsString( //returns string with item names and total count of selected(!) items, NOT total files/dirs! const std::vector<FileSystemObject*>& selectionLeft, //all pointers need to be bound! const std::vector<FileSystemObject*>& selectionRight); // -struct DeleteFilesHandler -{ - virtual ~DeleteFilesHandler() {} +//manual copy to alternate folder: +void copyToAlternateFolder(const std::vector<FileSystemObject*>& rowsToCopyOnLeft, //all pointers need to be bound! + const std::vector<FileSystemObject*>& rowsToCopyOnRight, // + const Zstring& targetFolderPathPhrase, + bool keepRelPaths, + bool overwriteIfExists, + ProcessCallback& callback); - enum Response - { - IGNORE_ERROR = 10, - RETRY - }; - virtual Response reportError (const std::wstring& msg) = 0; - virtual void reportWarning(const std::wstring& msg, bool& warningActive) = 0; - virtual void reportStatus (const std::wstring& msg) = 0; -}; +//manual deletion of files on main grid void deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows const std::vector<FileSystemObject*>& rowsToDeleteOnRight, //all pointers need to be bound! FolderComparison& folderCmp, //attention: rows will be physically deleted! const std::vector<DirectionConfig>& directCfgs, bool useRecycleBin, - DeleteFilesHandler& statusHandler, //global warnings: - bool& warningRecyclerMissing); + bool& warningRecyclerMissing, + ProcessCallback& callback); } #endif //ALGORITHM_H_34218518475321452548 diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 14aadc03..a69c07ce 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -50,20 +50,6 @@ int _matherr(struct _exception* except) namespace { -/* -boost::thread::id mainThreadId = boost::this_thread::get_id(); - -void onTerminationRequested() -{ -std::wstring msg = boost::this_thread::get_id() == mainThreadId ? - L"Termination requested in main thread!\n\n" : - L"Termination requested in worker thread!\n\n"; -msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; - -wxSafeShowMessage(_("An exception occurred"), msg); -std::abort(); -} -*/ #ifdef ZEN_WIN void enableCrashingOnCrashes() //should be needed for 32-bit code only: http://randomascii.wordpress.com/2012/07/05/when-even-crashing-doesnt-work { @@ -144,9 +130,6 @@ const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); bool Application::OnInit() { - //-> this seems rather useless: - //std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads - #ifdef ZEN_WIN enableCrashingOnCrashes(); #ifdef _MSC_VER @@ -396,8 +379,8 @@ void Application::launch(const std::vector<Zstring>& commandArgs) auto hasNonDefaultConfig = [](const FolderPairEnh& fp) { - return !(fp == FolderPairEnh(fp.dirpathPhraseLeft, - fp.dirpathPhraseRight, + return !(fp == FolderPairEnh(fp.folderPathPhraseLeft_, + fp.folderPathPhraseRight_, nullptr, nullptr, FilterConfig())); }; @@ -416,8 +399,8 @@ void Application::launch(const std::vector<Zstring>& commandArgs) for (size_t i = 0; i < leftDirs.size(); ++i) if (i == 0) { - mainCfg.firstPair.dirpathPhraseLeft = leftDirs [0]; - mainCfg.firstPair.dirpathPhraseRight = rightDirs[0]; + mainCfg.firstPair.folderPathPhraseLeft_ = leftDirs [0]; + mainCfg.firstPair.folderPathPhraseRight_ = rightDirs[0]; } else mainCfg.additionalPairs.emplace_back(leftDirs[i], rightDirs[i], diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index 1d554449..9ab31b8c 100644 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -31,7 +31,7 @@ std::vector<FolderPairCfg> zen::extractCompareCfg(const MainConfiguration& mainC std::transform(allPairs.begin(), allPairs.end(), std::back_inserter(output), [&](const FolderPairEnh& enhPair) -> FolderPairCfg { - return FolderPairCfg(enhPair.dirpathPhraseLeft, enhPair.dirpathPhraseRight, + return FolderPairCfg(enhPair.folderPathPhraseLeft_, enhPair.folderPathPhraseRight_, enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->compareVar : mainCfg.cmpConfig.compareVar, enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->handleSymlinks : mainCfg.cmpConfig.handleSymlinks, fileTimeTolerance, @@ -80,8 +80,8 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector<FolderPairCfg>& cfgL output.resolvedPairs.clear(); for (const FolderPairCfg& fpCfg : cfgList) { - std::shared_ptr<ABF> abfLeft = createAbstractBaseFolder(fpCfg.dirpathPhraseLeft); - std::shared_ptr<ABF> abfRight = createAbstractBaseFolder(fpCfg.dirpathPhraseRight); + std::shared_ptr<ABF> abfLeft = createAbstractBaseFolder(fpCfg.folderPathPhraseLeft_); + std::shared_ptr<ABF> abfRight = createAbstractBaseFolder(fpCfg.folderPathPhraseRight_); uniqueBaseFolders.insert(abfLeft .get()); uniqueBaseFolders.insert(abfRight.get()); @@ -392,8 +392,8 @@ void categorizeSymlinkByContent(SymlinkPair& linkObj, int fileTimeTolerance, uns if (targetPathRawL == targetPathRawR #ifdef ZEN_WIN //type of symbolic link is relevant for Windows only && - ABF::dirExists(linkObj.getAbstractPath<LEFT_SIDE >()) == //check if dir-symlink - ABF::dirExists(linkObj.getAbstractPath<RIGHT_SIDE>()) // + ABF::folderExists(linkObj.getAbstractPath<LEFT_SIDE >()) == //check if dir-symlink + ABF::folderExists(linkObj.getAbstractPath<RIGHT_SIDE>()) // #endif ) { diff --git a/FreeFileSync/Source/comparison.h b/FreeFileSync/Source/comparison.h index 0aa5dd38..c96137de 100644 --- a/FreeFileSync/Source/comparison.h +++ b/FreeFileSync/Source/comparison.h @@ -18,16 +18,16 @@ namespace zen { struct FolderPairCfg { - FolderPairCfg(const Zstring& dirPhraseLeft, - const Zstring& dirPhraseRight, + FolderPairCfg(const Zstring& folderPathPhraseLeft, + const Zstring& folderPathPhraseRight, CompareVariant cmpVar, SymLinkHandling handleSymlinksIn, int fileTimeToleranceIn, unsigned int optTimeShiftHoursIn, const NormalizedFilter& filterIn, const DirectionConfig& directCfg) : - dirpathPhraseLeft(dirPhraseLeft), - dirpathPhraseRight(dirPhraseRight), + folderPathPhraseLeft_ (folderPathPhraseLeft), + folderPathPhraseRight_(folderPathPhraseRight), compareVar(cmpVar), handleSymlinks(handleSymlinksIn), fileTimeTolerance(fileTimeToleranceIn), @@ -35,8 +35,8 @@ struct FolderPairCfg filter(filterIn), directionCfg(directCfg) {} - Zstring dirpathPhraseLeft; //unresolved directory names as entered by user! - Zstring dirpathPhraseRight; // + Zstring folderPathPhraseLeft_; //unresolved directory names as entered by user! + Zstring folderPathPhraseRight_; // CompareVariant compareVar; SymLinkHandling handleSymlinks; diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/file_hierarchy.h index 249346ca..130c6420 100644 --- a/FreeFileSync/Source/file_hierarchy.h +++ b/FreeFileSync/Source/file_hierarchy.h @@ -238,9 +238,6 @@ public: CompareVariant cmpVar, int fileTimeTolerance, unsigned int optTimeShiftHours) : -#ifdef _MSC_VER -#pragma warning(suppress: 4355) //"The this pointer is valid only within nonstatic member functions. It cannot be used in the initializer list for a base class." -#endif HierarchyObject(Zstring(), *this), filter_(filter), cmpVar_(cmpVar), fileTimeTolerance_(fileTimeTolerance), optTimeShiftHours_(optTimeShiftHours), dirExistsLeft_ (dirExistsLeft), diff --git a/FreeFileSync/Source/fs/abstract.cpp b/FreeFileSync/Source/fs/abstract.cpp index ffdf286a..79ba889f 100644 --- a/FreeFileSync/Source/fs/abstract.cpp +++ b/FreeFileSync/Source/fs/abstract.cpp @@ -76,7 +76,7 @@ ABF::FileAttribAfterCopy ABF::copyFileTransactional(const AbstractPathRef& apSou //fall back to stream-based file copy: if (copyFilePermissions) - throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(getDisplayPath(apTargetTmp))), + throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(ABF::getDisplayPath(apTargetTmp))), _("Operation not supported for different base folder types.")); return copyFileAsStream(apSource, apTargetTmp, onNotifyCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked @@ -100,7 +100,7 @@ ABF::FileAttribAfterCopy ABF::copyFileTransactional(const AbstractPathRef& apSou } //transactional behavior: ensure cleanup; not needed before copyFileBestEffort() which is already transactional - zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(apTargetTmp); } catch (FileError&) {} }); + zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { ABF::removeFile(apTargetTmp); } catch (FileError&) {} }); //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite if (onDeleteTargetFile) @@ -146,10 +146,10 @@ void ABF::createFolderRecursively(const AbstractPathRef& ap) //throw FileError catch (ErrorTargetExisting&) {} catch (ErrorTargetPathMissing&) { - if (const Opt<Zstring> parentPathImpl = ap.abf->getParentFolderPathImpl(ap.itemPathImpl)) + if (std::unique_ptr<AbstractPathRef> parentPath = ABF::getParentFolderPath(ap)) { //recurse... - createFolderRecursively(AbstractPathRef(*ap.abf, *parentPathImpl)); //throw FileError + createFolderRecursively(*parentPath); //throw FileError //now try again... ABF::createFolderSimple(ap); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) @@ -170,7 +170,7 @@ struct FlatTraverserCallback: public ABF::TraverserCallback std::unique_ptr<TraverserCallback> onDir (const DirInfo& di) override { folderNames_.push_back(di.shortName); return nullptr; } HandleLink onSymlink(const SymlinkInfo& si) override { - if (ABF::dirExists(ABF::appendRelPath(folderPath_, si.shortName))) //dir symlink + if (ABF::folderExists(ABF::appendRelPath(folderPath_, si.shortName))) //dir symlink folderLinkNames_.push_back(si.shortName); else //file symlink, broken symlink fileNames_.push_back(si.shortName); @@ -196,7 +196,7 @@ void removeFolderRecursivelyImpl(const AbstractPathRef& folderPath, //throw File const std::function<void (const std::wstring& displayPath)>& onBeforeFolderDeletion) //one call for each *existing* object! { assert(!ABF::symlinkExists(folderPath)); //[!] no symlinks in this context!!! - assert(ABF::dirExists(folderPath)); //Do NOT traverse into it deleting contained files!!! + assert(ABF::folderExists(folderPath)); //Do NOT traverse into it deleting contained files!!! FlatTraverserCallback ft(folderPath); //traverse source directory one level deep ABF::traverseFolder(folderPath, ft); //throw FileError diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h index 1b951089..566604bf 100644 --- a/FreeFileSync/Source/fs/abstract.h +++ b/FreeFileSync/Source/fs/abstract.h @@ -52,9 +52,17 @@ struct AbstractBaseFolder static Opt<Zstring> getNativeItemPath(const AbstractPathRef& ap) { return ap.abf->isNativeFileSystem() ? Opt<Zstring>(ap.itemPathImpl) : NoValue(); } + static std::unique_ptr<AbstractPathRef> getParentFolderPath(const AbstractPathRef& ap) + { + if (const Opt<Zstring> parentPathImpl = ap.abf->getParentFolderPathImpl(ap.itemPathImpl)) + return std::unique_ptr<AbstractPathRef>(new AbstractPathRef(*ap.abf, *parentPathImpl)); + return nullptr; + } + //limitation: zen::Opt requires default-constructibility => we need to use std::unique_ptr: + //---------------------------------------------------------------------------------------------------------------- static bool fileExists (const AbstractPathRef& ap) { return ap.abf->fileExists (ap.itemPathImpl); } //noexcept; check whether file or file-symlink exists - static bool dirExists (const AbstractPathRef& ap) { return ap.abf->dirExists (ap.itemPathImpl); } //noexcept; check whether directory or dir-symlink exists + static bool folderExists (const AbstractPathRef& ap) { return ap.abf->folderExists (ap.itemPathImpl); } //noexcept; check whether directory or dir-symlink exists static bool symlinkExists (const AbstractPathRef& ap) { return ap.abf->symlinkExists (ap.itemPathImpl); } //noexcept; check whether a symbolic link exists static bool somethingExists(const AbstractPathRef& ap) { return ap.abf->somethingExists(ap.itemPathImpl); } //noexcept; check whether any object with this name exists //---------------------------------------------------------------------------------------------------------------- @@ -95,7 +103,7 @@ struct AbstractBaseFolder //- THREAD-SAFETY: must be thread-safe like an int! => no dangling references to this instance! static IconLoader getAsyncIconLoader(const AbstractPathRef& ap) { return ap.abf->getAsyncIconLoader(ap.itemPathImpl); } //noexcept! virtual std::function<void()> /*throw FileError*/ getAsyncConnectFolder(bool allowUserInteraction) const = 0; //noexcept, optional return value - static std::function<bool()> /*throw FileError*/ getAsyncCheckDirExists(const AbstractPathRef& ap) { return ap.abf->getAsyncCheckDirExists(ap.itemPathImpl); } //noexcept + static std::function<bool()> /*throw FileError*/ getAsyncCheckFolderExists(const AbstractPathRef& ap) { return ap.abf->getAsyncCheckFolderExists(ap.itemPathImpl); } //noexcept //---------------------------------------------------------------------------------------------------------------- using FileId = Zbase<char>; @@ -161,10 +169,10 @@ struct AbstractBaseFolder struct FileInfo { - const Zchar* shortName; - std::uint64_t fileSize; //unit: bytes! - std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC - const FileId& id; //optional: empty if not supported! + const Zchar* shortName; + std::uint64_t fileSize; //unit: bytes! + std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC + const FileId id; //optional: empty if not supported! const SymlinkInfo* symlinkInfo; //only filled if file is a followed symlink }; @@ -294,7 +302,7 @@ private: //---------------------------------------------------------------------------------------------------------------- virtual bool fileExists (const Zstring& itemPathImpl) const = 0; //noexcept - virtual bool dirExists (const Zstring& itemPathImpl) const = 0; //noexcept + virtual bool folderExists (const Zstring& itemPathImpl) const = 0; //noexcept virtual bool symlinkExists (const Zstring& itemPathImpl) const = 0; //noexcept virtual bool somethingExists(const Zstring& itemPathImpl) const = 0; //noexcept //---------------------------------------------------------------------------------------------------------------- @@ -320,7 +328,7 @@ private: //---------------------------------------------------------------------------------------------------------------- //- THREAD-SAFETY: must be thread-safe like an int! => no dangling references to this instance! virtual IconLoader getAsyncIconLoader(const Zstring& itemPathImpl) const = 0; //noexcept! - virtual std::function<bool()> /*throw FileError*/ getAsyncCheckDirExists(const Zstring& itemPathImpl) const = 0; //noexcept + virtual std::function<bool()> /*throw FileError*/ getAsyncCheckFolderExists(const Zstring& itemPathImpl) const = 0; //noexcept //---------------------------------------------------------------------------------------------------------------- virtual std::unique_ptr<InputStream > getInputStream (const Zstring& itemPathImpl) const = 0; //throw FileError, ErrorFileLocked virtual std::unique_ptr<OutputStream> getOutputStream(const Zstring& itemPathImpl, //throw FileError, ErrorTargetExisting diff --git a/FreeFileSync/Source/fs/native.cpp b/FreeFileSync/Source/fs/native.cpp index 3ddc397b..7bbe4c06 100644 --- a/FreeFileSync/Source/fs/native.cpp +++ b/FreeFileSync/Source/fs/native.cpp @@ -304,7 +304,7 @@ private: //---------------------------------------------------------------------------------------------------------------- bool fileExists (const Zstring& itemPathImpl) const override { return zen::fileExists (itemPathImpl); } //noexcept - bool dirExists (const Zstring& itemPathImpl) const override { return zen::dirExists (itemPathImpl); } //noexcept + bool folderExists (const Zstring& itemPathImpl) const override { return zen::dirExists (itemPathImpl); } //noexcept bool symlinkExists (const Zstring& itemPathImpl) const override { return zen::symlinkExists (itemPathImpl); } //noexcept bool somethingExists(const Zstring& itemPathImpl) const override { return zen::somethingExists(itemPathImpl); } //noexcept //---------------------------------------------------------------------------------------------------------------- @@ -357,9 +357,9 @@ private: } //- THREAD-SAFETY: must be thread-safe like an int! => no dangling references to this instance! - std::function<bool()> /*throw FileError*/ getAsyncCheckDirExists(const Zstring& itemPathImpl) const override //noexcept + std::function<bool()> /*throw FileError*/ getAsyncCheckFolderExists(const Zstring& itemPathImpl) const override //noexcept { - warn_static("finish file error handling") + warn_static("finish file error detection") return [itemPathImpl] { return zen::dirExists(itemPathImpl); }; } @@ -476,43 +476,61 @@ bool RecycleSessionNative::recycleItem(const AbstractPathRef& ap, const Zstring& assert(!startsWith(logicalRelPath, FILE_NAME_SEPARATOR)); #ifdef ZEN_WIN + const bool remnantRecyclerItem = [&itemPath] //clean-up of recycler temp directory failed during last sync + { + //search for path component named "RecycleBin.ffs_tmp" or "RecycleBin_<num>.ffs_tmp": + const size_t pos = itemPath.find(L"\\RecycleBin"); + if (pos == Zstring::npos) + return false; + + const size_t pos2 = itemPath.find(L'\\', pos + 1); + return endsWith(StringRef<Zchar>(itemPath.begin(), pos2 == Zstring::npos ? itemPath.end() : itemPath.begin() + pos2), ABF::TEMP_FILE_ENDING); + }(); + + //do not create RecycleBin.ffs_tmp directories recursively if recycling a particular item fails forever! + //=> 1. stack overflow crashes 2. paths longer than 260 chars, undeletable/viewable with Explorer + if (remnantRecyclerItem) + return recycleOrDelete(itemPath); //throw FileError + const Zstring tmpPath = getOrCreateRecyclerTempDirPf() + logicalRelPath; //throw FileError bool deleted = false; auto moveToTempDir = [&] { - try - { - //performance optimization: Instead of moving each object into recycle bin separately, - //we rename them one by one into a temporary directory and batch-recycle this directory after sync - renameFile(itemPath, tmpPath); //throw FileError, ErrorDifferentVolume - this->toBeRecycled.push_back(tmpPath); - deleted = true; - } - catch (ErrorDifferentVolume&) //MoveFileEx() returns ERROR_PATH_NOT_FOUND *before* considering ERROR_NOT_SAME_DEVICE! => we have to create tmpParentDir anyway to find out! - { - deleted = recycleOrDelete(itemPath); //throw FileError - } + //perf: Instead of recycling each object separately, we rename them one by one + // into a temporary directory and batch-recycle all at once after sync + renameFile(itemPath, tmpPath); //throw FileError, ErrorDifferentVolume + this->toBeRecycled.push_back(tmpPath); + deleted = true; }; try { - moveToTempDir(); //throw FileError, ErrorDifferentVolume - } - catch (FileError&) - { - if (somethingExists(itemPath)) + try + { + moveToTempDir(); //throw FileError, ErrorDifferentVolume + } + catch (ErrorDifferentVolume&) { throw; } + catch (FileError&) { - const Zstring tmpParentDir = beforeLast(tmpPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); //what if C:\ ? - if (!somethingExists(tmpParentDir)) + if (somethingExists(itemPath)) { - makeDirectoryRecursively(tmpParentDir); //throw FileError - moveToTempDir(); //throw FileError -> this should work now! + const Zstring tmpParentDir = beforeLast(tmpPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); //what if C:\ ? + if (!somethingExists(tmpParentDir)) + { + makeDirectoryRecursively(tmpParentDir); //throw FileError + moveToTempDir(); //throw FileError, ErrorDifferentVolume -> this should work now! + } + else + throw; } - else - throw; } } + catch (ErrorDifferentVolume&) //MoveFileEx() returns ERROR_PATH_NOT_FOUND *before* considering ERROR_NOT_SAME_DEVICE! => we have to create tmpParentDir to find out! + { + return recycleOrDelete(itemPath); //throw FileError + } + return deleted; #elif defined ZEN_LINUX || defined ZEN_MAC diff --git a/FreeFileSync/Source/lib/binary.cpp b/FreeFileSync/Source/lib/binary.cpp index d1f6e1b4..f55a99a9 100644 --- a/FreeFileSync/Source/lib/binary.cpp +++ b/FreeFileSync/Source/lib/binary.cpp @@ -8,7 +8,6 @@ #include <zen/tick_count.h> #include <vector> #include <zen/file_io.h> -#include <boost/thread/tss.hpp> using namespace zen; using ABF = AbstractBaseFolder; @@ -75,16 +74,6 @@ const std::int64_t TICKS_PER_SEC = ticksPerSec(); bool zen::filesHaveSameContent(const AbstractPathRef& filePath1, const AbstractPathRef& filePath2, const std::function<void(std::int64_t bytesDelta)>& onUpdateStatus) //throw FileError { - static boost::thread_specific_ptr<std::vector<char>> cpyBuf1; - static boost::thread_specific_ptr<std::vector<char>> cpyBuf2; - if (!cpyBuf1.get()) - cpyBuf1.reset(new std::vector<char>()); - if (!cpyBuf2.get()) - cpyBuf2.reset(new std::vector<char>()); - - std::vector<char>& memory1 = *cpyBuf1; - std::vector<char>& memory2 = *cpyBuf2; - const std::unique_ptr<ABF::InputStream> inStream1 = ABF::getInputStream(filePath1); //throw FileError, (ErrorFileLocked) const std::unique_ptr<ABF::InputStream> inStream2 = ABF::getInputStream(filePath2); // @@ -92,22 +81,24 @@ bool zen::filesHaveSameContent(const AbstractPathRef& filePath1, const AbstractP inStream2->optimalBlockSize())); TickVal lastDelayViolation = getTicks(); + std::vector<char> buf; //make this thread-local? => on noticeable perf advantage! for (;;) { const size_t bufSize = dynamicBufSize.get(); //save for reliable eof check below!!! - setMinSize(memory1, bufSize); - setMinSize(memory2, bufSize); + setMinSize(buf, 2 * bufSize); + char* buf1 = &buf[0]; + char* buf2 = &buf[bufSize]; const TickVal startTime = getTicks(); - const size_t length1 = inStream1->read(&memory1[0], bufSize); //throw FileError - const size_t length2 = inStream2->read(&memory2[0], bufSize); //returns actual number of bytes read + const size_t length1 = inStream1->read(buf1, bufSize); //throw FileError + const size_t length2 = inStream2->read(buf2, bufSize); //returns actual number of bytes read //send progress updates immediately after reading to reliably allow speed calculations for our clients! if (onUpdateStatus) onUpdateStatus(std::max(length1, length2)); - if (length1 != length2 || ::memcmp(&memory1[0], &memory2[0], length1) != 0) + if (length1 != length2 || ::memcmp(buf1, buf2, length1) != 0) return false; //-------- dynamically set buffer size to keep callback interval between 100 - 500ms --------------------- diff --git a/FreeFileSync/Source/lib/cmp_filetime.h b/FreeFileSync/Source/lib/cmp_filetime.h index 088537ac..4d47a63b 100644 --- a/FreeFileSync/Source/lib/cmp_filetime.h +++ b/FreeFileSync/Source/lib/cmp_filetime.h @@ -34,11 +34,8 @@ bool sameFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, unsigned in return false; } -//--------------------------------------------------------------------------------------------------------------- -//number of seconds since Jan 1st 1970 + 1 year (needn't be too precise) -const std::int64_t oneYearFromNow = std::time(nullptr) + 365 * 24 * 3600; //init at program startup in *each* compilation unit -> avoid MT issues -//refactor when C++11 thread-safe static initialization is availalbe in VS (already in GCC) +//--------------------------------------------------------------------------------------------------------------- enum class TimeResult { @@ -53,6 +50,13 @@ enum class TimeResult inline TimeResult compareFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, unsigned int optTimeShiftHours) { +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif + + //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise) + static const std::int64_t oneYearFromNow = std::time(nullptr) + 365 * 24 * 3600; + if (sameFileTime(lhs, rhs, tolerance, optTimeShiftHours)) //last write time may differ by up to 2 seconds (NTFS vs FAT32) return TimeResult::EQUAL; diff --git a/FreeFileSync/Source/lib/db_file.cpp b/FreeFileSync/Source/lib/db_file.cpp index b4441604..a924e3e2 100644 --- a/FreeFileSync/Source/lib/db_file.cpp +++ b/FreeFileSync/Source/lib/db_file.cpp @@ -656,9 +656,10 @@ private: //if directory is not included in "currentDirs", it is either not existing anymore, in which case it should be deleted from database //or it was excluded via filter and the database entry should be preserved - warn_static("insufficient for *.txt-include filters! -> e.g. 1. *.txt-include, both sides in sync, txt-fiels in subfolder") - warn_static("2. delete all subfolders externally ") - warn_static("3. sync => db should be updated == entries removed for .txt; mabye even for deleted subfolders!?!") + warn_static("insufficient for *.txt-include filters! -> e.g. " + "1. *.txt-include, both sides in sync, txt-fiels in subfolder" + "2. delete all subfolders externally " + "3. sync => db should be updated == entries removed for .txt; mabye even for deleted subfolders!?!") }); } @@ -720,7 +721,7 @@ void zen::saveLastSynchronousState(const BaseDirPair& baseDirObj, const std::fun ABF::removeFile(dbPathRightTmp); //throw FileError //(try to) load old database files... - DbStreams streamsLeft; //list of session ID + DirInfo-stream + DbStreams streamsLeft; //list of session ID + DirInfo-stream DbStreams streamsRight; //std::function<void(std::int64_t bytesDelta)> onUpdateLoadStatus; diff --git a/FreeFileSync/Source/lib/dir_exist_async.h b/FreeFileSync/Source/lib/dir_exist_async.h index 4e376d8b..0e7e8f02 100644 --- a/FreeFileSync/Source/lib/dir_exist_async.h +++ b/FreeFileSync/Source/lib/dir_exist_async.h @@ -7,6 +7,7 @@ #ifndef DIR_EXIST_HEADER_08173281673432158067342132467183267 #define DIR_EXIST_HEADER_08173281673432158067342132467183267 +#include <list> #include <zen/thread.h> #include <zen/file_error.h> #include "../fs/abstract.h" @@ -28,30 +29,13 @@ struct DirectoryStatus }; -struct DirCheckResult -{ - bool exists; - std::unique_ptr<FileError> error; - - DirCheckResult(bool existsIn, std::unique_ptr<FileError>&& errorIn) : exists(existsIn), error(std::move(errorIn)) {} - - DirCheckResult (DirCheckResult&& tmp) : exists(tmp.exists), error(std::move(tmp.error)) {} //= default with C++14 - DirCheckResult& operator=(DirCheckResult&& tmp) //= default with C++14 - { - exists = tmp.exists; - error = std::move(tmp.error); - return *this; - } -}; - - DirectoryStatus checkFolderExistenceUpdating(const std::set<const ABF*, ABF::LessItemPath>& baseFolders, bool allowUserInteraction, ProcessCallback& procCallback) { using namespace zen; DirectoryStatus output; - std::list<std::pair<const ABF*, boost::unique_future<DirCheckResult>>> futureInfo; + std::list<std::pair<const ABF*, std::future<bool>>> futureInfo; for (const ABF* baseFolder : baseFolders) if (!baseFolder->emptyBaseFolderPath()) //skip empty dirs @@ -59,28 +43,21 @@ DirectoryStatus checkFolderExistenceUpdating(const std::set<const ABF*, ABF::Les AbstractPathRef folderPath = baseFolder->getAbstractPath(Zstring()); std::function<void()> connectFolder /*throw FileError*/ = baseFolder->getAsyncConnectFolder(allowUserInteraction); //noexcept - std::function<bool()> dirExists /*throw FileError*/ = ABF::getAsyncCheckDirExists(folderPath); //noexcept + std::function<bool()> dirExists /*throw FileError*/ = ABF::getAsyncCheckFolderExists(folderPath); //noexcept futureInfo.emplace_back(baseFolder, runAsync([connectFolder, dirExists] { - try - { - //1. login to network share, open FTP connection, ect. - if (connectFolder) - connectFolder(); //throw FileError - - //2. check dir existence - return DirCheckResult(dirExists(), nullptr); //throw FileError - } - catch (const FileError& e) - { - return DirCheckResult(false, make_unique<FileError>(e)); - } + //1. login to network share, open FTP connection, ect. + if (connectFolder) + connectFolder(); //throw FileError + + //2. check dir existence + return dirExists(); //throw FileError })); } //don't wait (almost) endlessly like win32 would on non-existing network shares: - boost::chrono::steady_clock::time_point endTime = boost::chrono::steady_clock::now() + boost::chrono::seconds(20); //consider CD-rom insert or hard disk spin up time from sleep + std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now() + std::chrono::seconds(20); //consider CD-rom insert or hard disk spin up time from sleep for (auto& fi : futureInfo) { @@ -88,19 +65,21 @@ DirectoryStatus checkFolderExistenceUpdating(const std::set<const ABF*, ABF::Les procCallback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", displayPathFmt)); //may throw! - while (boost::chrono::steady_clock::now() < endTime && - fi.second.wait_for(boost::chrono::milliseconds(UI_UPDATE_INTERVAL / 2)) != boost::future_status::ready) + while (std::chrono::steady_clock::now() < endTime && + fi.second.wait_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL / 2)) != std::future_status::ready) procCallback.requestUiRefresh(); //may throw! - if (fi.second.is_ready()) + if (isReady(fi.second)) { - const DirCheckResult result = fi.second.get(); //call future::get() only *once*! otherwise: undefined behavior! - if (result.error) - output.failedChecks.emplace(fi.first, *result.error); - else if (result.exists) - output.existingBaseFolder.insert(fi.first); - else - output.missingBaseFolder.insert(fi.first); + try + { + //call future::get() only *once*! otherwise: undefined behavior! + if (fi.second.get()) //throw FileError + output.existingBaseFolder.insert(fi.first); + else + output.missingBaseFolder.insert(fi.first); + } + catch (const FileError& e) { output.failedChecks.emplace(fi.first, e); } } else output.failedChecks.emplace(fi.first, FileError(replaceCpy(_("Time out while searching for folder %x."), L"%x", displayPathFmt))); diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp index 1dc7b8df..d994ecae 100644 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/lib/dir_lock.cpp @@ -4,11 +4,11 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "dir_lock.h" -#include <utility> +#include <map> #include <wx/log.h> #include <memory> #include <zen/sys_error.h> -#include <zen/thread.h> //includes <boost/thread.hpp> +#include <zen/thread.h> #include <zen/scope_guard.h> #include <zen/guid.h> #include <zen/tick_count.h> @@ -54,16 +54,15 @@ using MemStreamIn = MemoryStreamIn <ByteArray>; class LifeSigns { public: - LifeSigns(const Zstring& lockfilepath) : //throw()!!! siehe SharedDirLock() - lockfilepath_(lockfilepath) {} //thread safety: make deep copy! + LifeSigns(const Zstring& lockfilepath) : lockfilepath_(lockfilepath) {} - void operator()() const //thread entry + void operator()() const //throw ThreadInterruption { try { for (;;) { - boost::this_thread::sleep_for(boost::chrono::seconds(EMIT_LIFE_SIGN_INTERVAL)); //throw boost::thread_interrupted + interruptibleSleep(std::chrono::seconds(EMIT_LIFE_SIGN_INTERVAL)); //throw ThreadInterruption //actual work emitLifeSign(); //throw () @@ -327,7 +326,7 @@ struct LockInformation //throw FileError { char tmp[sizeof(LOCK_FORMAT_DESCR)] = {}; readArray(stream, &tmp, sizeof(tmp)); //file format header - const int lockFileVersion = readNumber<boost::int32_t>(stream); // + const int lockFileVersion = readNumber<std::int32_t>(stream); // if (!std::equal(std::begin(tmp), std::end(tmp), std::begin(LOCK_FORMAT_DESCR)) || lockFileVersion != LOCK_FORMAT_VER) @@ -343,7 +342,7 @@ struct LockInformation //throw FileError void toStream(MemStreamOut& stream) const //throw () { writeArray(stream, LOCK_FORMAT_DESCR, sizeof(LOCK_FORMAT_DESCR)); - writeNumber<boost::int32_t>(stream, LOCK_FORMAT_VER); + writeNumber<std::int32_t>(stream, LOCK_FORMAT_VER); static_assert(sizeof(processId) <= sizeof(std::uint64_t), ""); //ensure cross-platform compatibility! static_assert(sizeof(sessionId) <= sizeof(std::uint64_t), ""); // @@ -458,7 +457,7 @@ void waitOnDirLock(const Zstring& lockfilepath, DirLockCallback* callback) //thr for (;;) { const TickVal now = getTicks(); - const std::uint64_t fileSizeNew = ::getLockFileSize(lockfilepath); //throw FileError + const std::uint64_t fileSizeNew = getLockFileSize(lockfilepath); //throw FileError if (TICKS_PER_SEC <= 0 || !lastLifeSign.isValid() || !now.isValid()) throw FileError(L"System timer failed."); //no i18n: "should" never throw ;) @@ -480,7 +479,7 @@ void waitOnDirLock(const Zstring& lockfilepath, DirLockCallback* callback) //thr if (retrieveLockId(lockfilepath) != originalLockId) //throw FileError -> since originalLockId is filled, we are not expecting errors! return; //another process has placed a new lock, leave scope: the wait for the old lock is technically over... - if (::getLockFileSize(lockfilepath) != fileSizeOld) //throw FileError + if (getLockFileSize(lockfilepath) != fileSizeOld) //throw FileError continue; //late life sign removeFile(lockfilepath); //throw FileError @@ -492,7 +491,7 @@ void waitOnDirLock(const Zstring& lockfilepath, DirLockCallback* callback) //thr for (size_t i = 0; i < 1000 * POLL_LIFE_SIGN_INTERVAL / GUI_CALLBACK_INTERVAL; ++i) { if (callback) callback->requestUiRefresh(); - boost::this_thread::sleep_for(boost::chrono::milliseconds(GUI_CALLBACK_INTERVAL)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(GUI_CALLBACK_INTERVAL)); if (callback) { @@ -603,13 +602,13 @@ public: while (!::tryLock(lockfilepath)) //throw FileError ::waitOnDirLock(lockfilepath, callback); // - threadObj = boost::thread(LifeSigns(lockfilepath)); + lifeSignthread = InterruptibleThread(LifeSigns(lockfilepath)); } ~SharedDirLock() { - threadObj.interrupt(); //thread lifetime is subset of this instances's life - threadObj.join(); //throw boost::thread_interrupted -> not expected => main thread! + lifeSignthread.interrupt(); //thread lifetime is subset of this instances's life + lifeSignthread.join(); ::releaseLock(lockfilepath_); //throw () } @@ -619,7 +618,7 @@ private: SharedDirLock& operator=(const DirLock&) = delete; const Zstring lockfilepath_; - boost::thread threadObj; + InterruptibleThread lifeSignthread; }; diff --git a/FreeFileSync/Source/lib/dir_lock.h b/FreeFileSync/Source/lib/dir_lock.h index 8fd43fcf..00634b5d 100644 --- a/FreeFileSync/Source/lib/dir_lock.h +++ b/FreeFileSync/Source/lib/dir_lock.h @@ -16,7 +16,7 @@ const size_t GUI_CALLBACK_INTERVAL = 100; struct DirLockCallback //while waiting for the lock { virtual ~DirLockCallback() {} - virtual void requestUiRefresh() = 0; //allowed to throw exceptions + virtual void requestUiRefresh() = 0; //allowed to throw exceptions virtual void reportStatus(const std::wstring& text) = 0; }; diff --git a/FreeFileSync/Source/lib/generate_logfile.h b/FreeFileSync/Source/lib/generate_logfile.h index 60f33126..608e0289 100644 --- a/FreeFileSync/Source/lib/generate_logfile.h +++ b/FreeFileSync/Source/lib/generate_logfile.h @@ -8,7 +8,6 @@ #define GEN_LOGFILE_H_93172643216748973216458732165415 #include <zen/error_log.h> -//#include <zen/file_io.h> #include <zen/serialize.h> #include <zen/format_unit.h> #include "ffs_paths.h" diff --git a/FreeFileSync/Source/lib/hard_filter.cpp b/FreeFileSync/Source/lib/hard_filter.cpp index d2313f88..4d786483 100644 --- a/FreeFileSync/Source/lib/hard_filter.cpp +++ b/FreeFileSync/Source/lib/hard_filter.cpp @@ -218,14 +218,6 @@ bool matchesMaskBegin(const Zstring& name, const std::vector<Zstring>& masks) { return std::any_of(masks.begin(), masks.end(), [&](const Zstring& mask) { return matchesMaskBegin(name.c_str(), mask.c_str()); }); } - - -inline -void removeDuplicates(std::vector<Zstring>& v) -{ - std::sort(v.begin(), v.end()); - v.erase(std::unique(v.begin(), v.end()), v.end()); -} } diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/lib/icon_buffer.cpp index 01acd175..98426049 100644 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/lib/icon_buffer.cpp @@ -5,8 +5,9 @@ // ************************************************************************** #include "icon_buffer.h" +#include <map> #include <set> -#include <zen/thread.h> //includes <boost/thread.hpp> +#include <zen/thread.h> //includes <std/thread.hpp> #include <zen/scope_guard.h> #include <wx+/image_resources.h> #include "icon_loader.h" @@ -24,7 +25,7 @@ namespace const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to hold in buffer: must be big enough to hold visible icons + preload buffer! Consider OS limit on GDI resources (wxBitmap)!!! #ifndef NDEBUG - const boost::thread::id mainThreadId = boost::this_thread::get_id(); + const std::thread::id mainThreadId = std::this_thread::get_id(); #endif #ifdef ZEN_WIN @@ -35,7 +36,7 @@ const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to hold in buffer: //destroys raw icon! Call from GUI thread only! wxBitmap extractWxBitmap(ImageHolder&& ih) { - assert(boost::this_thread::get_id() == mainThreadId); + assert(std::this_thread::get_id() == mainThreadId); #ifndef NDEBUG auto check = [&] { assert(!ih); }; //work around V120_XP compilation issue ZEN_ON_SCOPE_EXIT(check()); @@ -132,11 +133,13 @@ struct WorkItem class WorkLoad { public: - WorkItem extractNextFile() //context of worker thread, blocking + //context of worker thread, blocking: + WorkItem extractNextFile() //throw ThreadInterruption { - assert(boost::this_thread::get_id() != mainThreadId); - boost::unique_lock<boost::mutex> dummy(lockFiles); - conditionNewWork.wait(dummy, [this] { return !workLoad.empty(); }); //throw boost::thread_interrupted + assert(std::this_thread::get_id() != mainThreadId); + std::unique_lock<std::mutex> dummy(lockFiles); + + interruptibleWait(conditionNewWork, dummy, [this] { return !workLoad.empty(); }); //throw ThreadInterruption WorkItem workItem = workLoad.back(); // workLoad.pop_back(); //yes, not std::bad_alloc exception-safe, but bad_alloc is not relevant for us @@ -145,9 +148,9 @@ public: void setWorkload(const std::vector<AbstractPathRef>& newLoad) //context of main thread { - assert(boost::this_thread::get_id() == mainThreadId); + assert(std::this_thread::get_id() == mainThreadId); { - boost::lock_guard<boost::mutex> dummy(lockFiles); + std::lock_guard<std::mutex> dummy(lockFiles); workLoad.clear(); for (const AbstractPathRef& filePath : newLoad) @@ -161,9 +164,9 @@ public: void addToWorkload(const AbstractPathRef& filePath) //context of main thread { - assert(boost::this_thread::get_id() == mainThreadId); + assert(std::this_thread::get_id() == mainThreadId); { - boost::lock_guard<boost::mutex> dummy(lockFiles); + std::lock_guard<std::mutex> dummy(lockFiles); workLoad.emplace_back(filePath.getUniqueId(), //set as next item to retrieve ABF::getAsyncIconLoader(filePath), //noexcept! @@ -173,9 +176,9 @@ public: } private: - std::vector<WorkItem> workLoad; //processes last elements of vector first! - boost::mutex lockFiles; - boost::condition_variable conditionNewWork; //signal event: data for processing available + std::vector<WorkItem> workLoad; //processes last elements of vector first! + std::mutex lockFiles; + std::condition_variable conditionNewWork; //signal event: data for processing available }; @@ -187,15 +190,15 @@ public: //called by main and worker thread: bool hasIcon(const AbstractPathRef::ItemId& id) const { - boost::lock_guard<boost::mutex> dummy(lockIconList); + std::lock_guard<std::mutex> dummy(lockIconList); return iconList.find(id) != iconList.end(); } //must be called by main thread only! => wxBitmap is NOT thread-safe like an int (non-atomic ref-count!!!) Opt<wxBitmap> retrieve(const AbstractPathRef::ItemId& id) { - assert(boost::this_thread::get_id() == mainThreadId); - boost::lock_guard<boost::mutex> dummy(lockIconList); + assert(std::this_thread::get_id() == mainThreadId); + std::lock_guard<std::mutex> dummy(lockIconList); auto it = iconList.find(id); if (it == iconList.end()) @@ -215,7 +218,7 @@ public: //called by main and worker thread: void insert(const AbstractPathRef::ItemId& id, ImageHolder&& icon) { - boost::lock_guard<boost::mutex> dummy(lockIconList); + std::lock_guard<std::mutex> dummy(lockIconList); //thread safety: moving ImageHolder is free from side effects, but ~wxBitmap() is NOT! => do NOT delete items from iconList here! auto rc = iconList.emplace(id, makeValueObject()); @@ -231,8 +234,8 @@ public: //call at an appropriate time, e.g. after Workload::setWorkload() void limitSize() { - assert(boost::this_thread::get_id() == mainThreadId); - boost::lock_guard<boost::mutex> dummy(lockIconList); + assert(std::this_thread::get_id() == mainThreadId); + std::lock_guard<std::mutex> dummy(lockIconList); while (iconList.size() > BUFFER_SIZE_MAX) { @@ -329,7 +332,7 @@ private: FileIconMap::iterator next_; // }; - mutable boost::mutex lockIconList; + mutable std::mutex lockIconList; FileIconMap iconList; //shared resource; Zstring is thread-safe like an int FileIconMap::iterator firstInsertPos; FileIconMap::iterator lastInsertPos; @@ -347,7 +350,7 @@ public: buffer_(buffer), iconSizeType(st) {} - void operator()(); //thread entry + void operator()() const; //thread entry private: std::shared_ptr<WorkLoad> workload_; //main/worker thread may access different shared_ptr instances safely (even though they have the same target!) @@ -379,7 +382,7 @@ public: } dummy; -void WorkerThread::operator()() //thread entry +void WorkerThread::operator()() const //thread entry { #ifdef ZEN_WIN //1. Initialize COM @@ -393,9 +396,10 @@ void WorkerThread::operator()() //thread entry for (;;) { - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption - const WorkItem workItem = workload_->extractNextFile(); //start work: blocks until next icon to load is retrieved + //start work: blocks until next icon to load is retrieved: + const WorkItem workItem = workload_->extractNextFile(); //throw ThreadInterruption if (!buffer_->hasIcon(workItem.id_)) //perf: workload may contain duplicate entries? buffer_->insert(workItem.id_, getDisplayIcon(workItem.iconLoader_, workItem.fileName_, iconSizeType)); @@ -413,13 +417,13 @@ struct IconBuffer::Pimpl std::shared_ptr<WorkLoad> workload; std::shared_ptr<Buffer> buffer; - boost::thread worker; + InterruptibleThread worker; }; IconBuffer::IconBuffer(IconSize sz) : pimpl(make_unique<Pimpl>()), iconSizeType(sz) { - pimpl->worker = boost::thread(WorkerThread(pimpl->workload, pimpl->buffer, sz)); + pimpl->worker = InterruptibleThread(WorkerThread(pimpl->workload, pimpl->buffer, sz)); } @@ -427,7 +431,7 @@ IconBuffer::~IconBuffer() { setWorkload({}); //make sure interruption point is always reached! pimpl->worker.interrupt(); - pimpl->worker.join(); //throw boost::thread_interrupted -> not expected => main thread! + pimpl->worker.join(); } @@ -492,7 +496,7 @@ void IconBuffer::setWorkload(const std::vector<AbstractPathRef>& load) assert(load.size() < BUFFER_SIZE_MAX / 2); pimpl->workload->setWorkload(load); //since buffer can only increase due to new workload, - pimpl->buffer->limitSize(); //this is the place to impose the limit from main thread! + pimpl->buffer->limitSize(); //this is the place to impose the limit from main thread! } diff --git a/FreeFileSync/Source/lib/icon_holder.h b/FreeFileSync/Source/lib/icon_holder.h index 7f1719f3..b26deb1e 100644 --- a/FreeFileSync/Source/lib/icon_holder.h +++ b/FreeFileSync/Source/lib/icon_holder.h @@ -22,23 +22,11 @@ struct ImageHolder //prepare conversion to wxImage as much as possible while sta rgb(static_cast<unsigned char*>(::malloc(width * height * 3))), alpha(withAlpha ? static_cast<unsigned char*>(::malloc(width * height)) : nullptr) {} - ImageHolder (const ImageHolder&) = delete; //move semantics only! + ImageHolder (ImageHolder&& tmp) = default; // + ImageHolder& operator=(ImageHolder&& tmp) = default; //move semantics only! + ImageHolder (const ImageHolder&) = delete; // ImageHolder& operator=(const ImageHolder&) = delete; // - ImageHolder(ImageHolder&& tmp) : width(tmp.width), //= default in C++14 - height(tmp.height), - rgb(tmp.rgb.release()), - alpha(tmp.alpha.release()) {} - - ImageHolder& operator=(ImageHolder&& tmp) //= default in C++14 - { - std::swap(width, tmp.width); - std::swap(height, tmp.height); - std::swap(rgb, tmp.rgb); - std::swap(alpha, tmp.alpha); - return *this; - } - explicit operator bool() const { return rgb.get() != nullptr; } int getWidth () const { return width; } diff --git a/FreeFileSync/Source/lib/parallel_scan.cpp b/FreeFileSync/Source/lib/parallel_scan.cpp index 8421821d..fc4f9af8 100644 --- a/FreeFileSync/Source/lib/parallel_scan.cpp +++ b/FreeFileSync/Source/lib/parallel_scan.cpp @@ -6,7 +6,7 @@ #include "parallel_scan.h" #include <zen/file_error.h> -#include <zen/thread.h> //includes <boost/thread.hpp> +#include <zen/thread.h> #include <zen/scope_guard.h> #include <zen/fixed_list.h> #include "db_file.h" @@ -156,19 +156,15 @@ typedef Zbase<wchar_t, StorageRefCountThreadSafe> BasicWString; //thread-safe st class AsyncCallback //actor pattern { public: - AsyncCallback() : notifyingThreadID(0), - textScanning(_("Scanning:")), - itemsScanned(0), - activeWorker(0) {} - - FillBufferCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //blocking call: context of worker thread + //blocking call: context of worker thread + FillBufferCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption { - boost::unique_lock<boost::mutex> dummy(lockErrorInfo); - conditionCanReportError.wait(dummy, [this] { return !errorInfo && !errorResponse; }); //throw boost::thread_interrupted + std::unique_lock<std::mutex> dummy(lockErrorInfo); + interruptibleWait(conditionCanReportError, dummy, [this] { return !errorInfo && !errorResponse; }); //throw ThreadInterruption - errorInfo = make_unique<std::pair<BasicWString, size_t>>(BasicWString(msg), retryNumber); + errorInfo = make_unique<std::pair<BasicWString, size_t>>(copyStringTo<BasicWString>(msg), retryNumber); - conditionGotResponse.wait(dummy, [this] { return static_cast<bool>(errorResponse); }); //throw boost::thread_interrupted + interruptibleWait(conditionGotResponse, dummy, [this] { return static_cast<bool>(errorResponse); }); //throw ThreadInterruption FillBufferCallback::HandleError rv = *errorResponse; @@ -183,7 +179,7 @@ public: void processErrors(FillBufferCallback& callback) //context of main thread, call repreatedly { - boost::unique_lock<boost::mutex> dummy(lockErrorInfo); + std::unique_lock<std::mutex> dummy(lockErrorInfo); if (errorInfo.get() && !errorResponse.get()) { FillBufferCallback::HandleError rv = callback.reportError(copyStringTo<std::wstring>(errorInfo->first), errorInfo->second); //throw! @@ -203,7 +199,7 @@ public: { if (threadID != notifyingThreadID) return; //only one thread at a time may report status - boost::lock_guard<boost::mutex> dummy(lockCurrentStatus); + std::lock_guard<std::mutex> dummy(lockCurrentStatus); currentFile = copyStringTo<BasicWString>(filepath); } @@ -211,7 +207,7 @@ public: { std::wstring filepath; { - boost::lock_guard<boost::mutex> dummy(lockCurrentStatus); + std::lock_guard<std::mutex> dummy(lockCurrentStatus); filepath = copyStringTo<std::wstring>(currentFile); } @@ -238,23 +234,23 @@ public: private: //---- error handling ---- - boost::mutex lockErrorInfo; - boost::condition_variable conditionCanReportError; - boost::condition_variable conditionGotResponse; + std::mutex lockErrorInfo; + std::condition_variable conditionCanReportError; + std::condition_variable conditionGotResponse; std::unique_ptr<std::pair<BasicWString, size_t>> errorInfo; //error message + retry number std::unique_ptr<FillBufferCallback::HandleError> errorResponse; //---- status updates ---- - std::atomic<int> notifyingThreadID; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 + std::atomic<int> notifyingThreadID { 0 }; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 - boost::mutex lockCurrentStatus; //use a different lock for current file: continue traversing while some thread may process an error + std::mutex lockCurrentStatus; //use a different lock for current file: continue traversing while some thread may process an error BasicWString currentFile; - const BasicWString textScanning; //this one is (currently) not shared and could be made a std::wstring, but we stay consistent and use thread-safe variables in this class only! + const BasicWString textScanning { copyStringTo<BasicWString>(_("Scanning:")) }; //this one is (currently) not shared and could be made a std::wstring, but we stay consistent and use thread-safe variables in this class only! //---- status updates II (lock free) ---- - std::atomic<int> itemsScanned; - std::atomic<int> activeWorker; + std::atomic<int> itemsScanned{ 0 }; //std:atomic is uninitialized by default! + std::atomic<int> activeWorker{ 0 }; // }; //------------------------------------------------------------------------------------------------- @@ -299,12 +295,12 @@ public: relNameParentPf_(relNameParentPf), output_(output) {} - virtual void onFile (const FileInfo& fi) override; - virtual std::unique_ptr<TraverserCallback> onDir (const DirInfo& di) override; - virtual HandleLink onSymlink(const SymlinkInfo& li) override; + virtual void onFile (const FileInfo& fi) override; // + virtual std::unique_ptr<TraverserCallback> onDir (const DirInfo& di) override; //throw ThreadInterruption + virtual HandleLink onSymlink(const SymlinkInfo& li) override; // - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override; - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override; + HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override; //throw ThreadInterruption + HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override; // private: TraverserShared& cfg; @@ -313,9 +309,9 @@ private: }; -void DirCallback::onFile(const FileInfo& fi) +void DirCallback::onFile(const FileInfo& fi) //throw ThreadInterruption { - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption const Zstring fileNameShort(fi.shortName); @@ -352,9 +348,9 @@ void DirCallback::onFile(const FileInfo& fi) } -std::unique_ptr<ABF::TraverserCallback> DirCallback::onDir(const DirInfo& di) +std::unique_ptr<ABF::TraverserCallback> DirCallback::onDir(const DirInfo& di) //throw ThreadInterruption { - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption const Zstring& relDirPath = relNameParentPf_ + di.shortName; @@ -378,9 +374,9 @@ std::unique_ptr<ABF::TraverserCallback> DirCallback::onDir(const DirInfo& di) } -DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) +DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) //throw ThreadInterruption { - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption const Zstring& relLinkPath = relNameParentPf_ + si.shortName; @@ -419,10 +415,9 @@ DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) } -DirCallback::HandleError DirCallback::reportDirError(const std::wstring& msg, size_t retryNumber) +DirCallback::HandleError DirCallback::reportDirError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption { - //AsyncCallback::reportError() blocks while implementing boost::this_thread::interruption_point() - switch (cfg.acb_.reportError(msg, retryNumber)) + switch (cfg.acb_.reportError(msg, retryNumber)) //throw ThreadInterruption { case FillBufferCallback::ON_ERROR_IGNORE: cfg.failedDirReads_[beforeLast(relNameParentPf_, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)] = msg; @@ -436,10 +431,9 @@ DirCallback::HandleError DirCallback::reportDirError(const std::wstring& msg, si } -DirCallback::HandleError DirCallback::reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) +DirCallback::HandleError DirCallback::reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) //throw ThreadInterruption { - //AsyncCallback::reportError() blocks while implementing boost::this_thread::interruption_point() - switch (cfg.acb_.reportError(msg, retryNumber)) + switch (cfg.acb_.reportError(msg, retryNumber)) //throw ThreadInterruption { case FillBufferCallback::ON_ERROR_IGNORE: cfg.failedItemReads_[relNameParentPf_ + shortName] = msg; @@ -466,7 +460,7 @@ public: dirKey_(dirKey), dirOutput_(dirOutput) {} - void operator()() //thread entry + void operator()() const //thread entry { acb_->incActiveWorker(); ZEN_ON_SCOPE_EXIT(acb_->decActiveWorker();); @@ -507,13 +501,13 @@ void zen::fillBuffer(const std::set<DirectoryKey>& keysToRead, //in { buf.clear(); - FixedList<boost::thread> worker; //note: we cannot use std::vector<boost::thread>: compiler error on GCC 4.7, probably a boost screw-up + FixedList<InterruptibleThread> worker; zen::ScopeGuard guardWorker = zen::makeGuard([&] { - for (boost::thread& wt : worker) + for (InterruptibleThread& wt : worker) wt.interrupt(); //interrupt all at once first, then join - for (boost::thread& wt : worker) + for (InterruptibleThread& wt : worker) if (wt.joinable()) //= precondition of thread::join(), which throws an exception if violated! wt.join(); //in this context it is possible a thread is *not* joinable anymore due to the thread::try_join_for() below! }); @@ -531,7 +525,7 @@ void zen::fillBuffer(const std::set<DirectoryKey>& keysToRead, //in } //wait until done - for (boost::thread& wt : worker) + for (InterruptibleThread& wt : worker) { do { @@ -541,7 +535,7 @@ void zen::fillBuffer(const std::set<DirectoryKey>& keysToRead, //in //process errors acb->processErrors(callback); } - while (!wt.try_join_for(boost::chrono::milliseconds(updateInterval))); + while (!wt.tryJoinFor(std::chrono::milliseconds(updateInterval))); acb->incrementNotifyingThreadId(); //process info messages of one thread at a time only } diff --git a/FreeFileSync/Source/lib/parallel_scan.h b/FreeFileSync/Source/lib/parallel_scan.h index 5a93b804..8a05522d 100644 --- a/FreeFileSync/Source/lib/parallel_scan.h +++ b/FreeFileSync/Source/lib/parallel_scan.h @@ -48,7 +48,7 @@ struct DirectoryValue { DirContainer dirCont; //relative names (or empty string for root) for directories that could not be read (completely), e.g. access denied, or temporal network drop - std::map<Zstring, std::wstring, LessFilePath> failedDirReads; //with corresponding error message + std::map<Zstring, std::wstring, LessFilePath> failedDirReads; //with corresponding error message //relative names (never empty) for failure to read single file/dir/symlink with corresponding error message std::map<Zstring, std::wstring, LessFilePath> failedItemReads; diff --git a/FreeFileSync/Source/lib/parse_lng.h b/FreeFileSync/Source/lib/parse_lng.h index 92ad6929..a54d4683 100644 --- a/FreeFileSync/Source/lib/parse_lng.h +++ b/FreeFileSync/Source/lib/parse_lng.h @@ -25,7 +25,7 @@ namespace lngfile { //singular forms -typedef std::map <std::string, std::string> TranslationMap; //orig |-> translation +typedef std::map <std::string, std::string> TranslationMap; //orig |-> translation //plural forms typedef std::pair<std::string, std::string> SingularPluralPair; //1 house| n houses diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index f90395f2..f303ecd0 100644 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -801,8 +801,8 @@ void readConfig(const XmlIn& in, FilterConfig& filter) void readConfig(const XmlIn& in, FolderPairEnh& enhPair) { //read folder pairs - in["Left" ](enhPair.dirpathPhraseLeft); - in["Right"](enhPair.dirpathPhraseRight); + in["Left" ](enhPair.folderPathPhraseLeft_); + in["Right"](enhPair.folderPathPhraseRight_); //########################################################### //alternate comp configuration (optional) @@ -939,9 +939,17 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) inWnd.attribute("PosY", config.gui.dlgPos.y); inWnd.attribute("Maximized", config.gui.isMaximized); + XmlIn inCopyTo = inWnd["ManualCopyTo"]; + inCopyTo.attribute("KeepRelativePaths", config.gui.copyToCfg.keepRelPaths); + inCopyTo.attribute("OverwriteIfExists", config.gui.copyToCfg.overwriteIfExists); + + XmlIn inCopyToHistory = inCopyTo["FolderHistory"]; + inCopyToHistory(config.gui.copyToCfg.folderHistory); + inCopyToHistory.attribute("LastUsedPath" , config.gui.copyToCfg.lastUsedPath); + inCopyToHistory.attribute("MaxSize" , config.gui.copyToCfg.historySizeMax); + XmlIn inManualDel = inWnd["ManualDeletion"]; - //inManualDel.attribute("DeleteOnBothSides", config.gui.deleteOnBothSides); - inManualDel.attribute("UseRecycler" , config.gui.useRecyclerForManualDeletion); + inManualDel.attribute("UseRecycler", config.gui.manualDeletionUseRecycler); inWnd["CaseSensitiveSearch"].attribute("Enabled", config.gui.textSearchRespectCase); inWnd["FolderPairsVisible" ].attribute("Max", config.gui.maxFolderPairsVisible); @@ -992,11 +1000,6 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) //external applications inGui["ExternalApplications"](config.gui.externelApplications); - warn_static("remove after migration: cleanup the old placeholder syntax") //26.10.2013 - if (std::any_of(config.gui.externelApplications.begin(), config.gui.externelApplications.end(), - [](const std::pair<Description, Commandline>& ea) { return contains(ea.second, L"%name") || contains(ea.second, L"%nameCo") || contains(ea.second, L"%dir") || contains(ea.second, L"%dirCo"); })) - config.gui.externelApplications = XmlGlobalSettings().gui.externelApplications; - //last update check inGui["LastOnlineCheck" ](config.gui.lastUpdateCheck); inGui["LastOnlineVersion"](config.gui.lastOnlineVersion); @@ -1191,8 +1194,8 @@ void writeConfigFolderPair(const FolderPairEnh& enhPair, XmlOut& out) XmlOut outPair = out.ref().addChild("Pair"); //read folder pairs - outPair["Left" ](enhPair.dirpathPhraseLeft); - outPair["Right"](enhPair.dirpathPhraseRight); + outPair["Left" ](enhPair.folderPathPhraseLeft_); + outPair["Right"](enhPair.folderPathPhraseRight_); //########################################################### //alternate comp configuration (optional) @@ -1321,9 +1324,17 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outWnd.attribute("PosY", config.gui.dlgPos.y); outWnd.attribute("Maximized", config.gui.isMaximized); + XmlOut outCopyTo = outWnd["ManualCopyTo"]; + outCopyTo.attribute("KeepRelativePaths", config.gui.copyToCfg.keepRelPaths); + outCopyTo.attribute("OverwriteIfExists", config.gui.copyToCfg.overwriteIfExists); + + XmlOut outCopyToHistory = outCopyTo["FolderHistory"]; + outCopyToHistory(config.gui.copyToCfg.folderHistory); + outCopyToHistory.attribute("LastUsedPath" , config.gui.copyToCfg.lastUsedPath); + outCopyToHistory.attribute("MaxSize" , config.gui.copyToCfg.historySizeMax); + XmlOut outManualDel = outWnd["ManualDeletion"]; - //outManualDel.attribute("DeleteOnBothSides", config.gui.deleteOnBothSides); - outManualDel.attribute("UseRecycler" , config.gui.useRecyclerForManualDeletion); + outManualDel.attribute("UseRecycler", config.gui.manualDeletionUseRecycler); outWnd["CaseSensitiveSearch"].attribute("Enabled", config.gui.textSearchRespectCase); outWnd["FolderPairsVisible" ].attribute("Max", config.gui.maxFolderPairsVisible); diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/lib/process_xml.h index d37ca956..12d6ac44 100644 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/lib/process_xml.h @@ -156,7 +156,7 @@ struct XmlGlobalSettings int fileTimeTolerance; //max. allowed file time deviation; < 0 means unlimited tolerance bool runWithBackgroundPriority; bool createLockFile; - bool verifyFileCopy; //verify copied files + bool verifyFileCopy; //verify copied files size_t lastSyncsLogFileSizeMax; OptionalDialogs optDialogs; @@ -164,48 +164,7 @@ struct XmlGlobalSettings //--------------------------------------------------------------------- struct Gui { - Gui() : - dlgPos(wxDefaultCoord, wxDefaultCoord), - dlgSize(wxDefaultCoord, wxDefaultCoord), - isMaximized(false), - sashOffset(0), - maxFolderPairsVisible(6), - columnAttribNavi (zen::getDefaultColumnAttributesNavi()), - columnAttribLeft (zen::getDefaultColumnAttributesLeft()), - columnAttribRight(zen::getDefaultColumnAttributesRight()), - naviLastSortColumn(zen::defaultValueLastSortColumn), - naviLastSortAscending(zen::defaultValueLastSortAscending), - showPercentBar(zen::defaultValueShowPercentage), - cfgFileHistMax(30), - folderHistMax(15), - onCompletionHistoryMax(8), -#ifdef ZEN_WIN - defaultExclusionFilter(Zstr("\\System Volume Information\\") Zstr("\n") - Zstr("\\$Recycle.Bin\\") Zstr("\n") - Zstr("\\RECYCLER\\") Zstr("\n") - Zstr("\\RECYCLED\\") Zstr("\n") - Zstr("*\\desktop.ini") Zstr("\n") - Zstr("*\\thumbs.db")), -#elif defined ZEN_LINUX - defaultExclusionFilter(Zstr("/.Trash-*/") Zstr("\n") - Zstr("/.recycle/")), -#elif defined ZEN_MAC - defaultExclusionFilter(Zstr("/.fseventsd/") Zstr("\n") - Zstr("/.Spotlight-V100/") Zstr("\n") - Zstr("/.Trashes/") Zstr("\n") - Zstr("*/.DS_Store") Zstr("\n") - Zstr("*/._.*")), -#endif - //deleteOnBothSides(false), - useRecyclerForManualDeletion(true), //enable if OS supports it; else user will have to activate first and then get an error message -#if defined ZEN_WIN || defined ZEN_MAC - textSearchRespectCase(false), -#elif defined ZEN_LINUX - textSearchRespectCase(true), -#endif - showIcons(true), - iconSize(ICON_SIZE_SMALL), - lastUpdateCheck(0) + Gui() { //default external apps will be translated "on the fly"!!! //CONTRACT: first entry will be used for [Enter] or mouse double-click, second for open with default app! @@ -224,46 +183,73 @@ struct XmlGlobalSettings #endif } - wxPoint dlgPos; - wxSize dlgSize; - bool isMaximized; - int sashOffset; + wxPoint dlgPos { wxDefaultCoord, wxDefaultCoord }; + wxSize dlgSize { wxDefaultCoord, wxDefaultCoord }; + bool isMaximized = false; + int sashOffset = 0; - int maxFolderPairsVisible; + int maxFolderPairsVisible = 6; - std::vector<zen::ColumnAttributeNavi> columnAttribNavi; //compressed view/navigation - std::vector<zen::ColumnAttributeRim> columnAttribLeft; - std::vector<zen::ColumnAttributeRim> columnAttribRight; + std::vector<zen::ColumnAttributeNavi> columnAttribNavi = zen::getDefaultColumnAttributesNavi(); //compressed view/navigation + std::vector<zen::ColumnAttributeRim> columnAttribLeft = zen::getDefaultColumnAttributesLeft(); + std::vector<zen::ColumnAttributeRim> columnAttribRight = zen::getDefaultColumnAttributesRight(); - zen::ColumnTypeNavi naviLastSortColumn; //remember sort on navigation panel - bool naviLastSortAscending; // + zen::ColumnTypeNavi naviLastSortColumn = zen::defaultValueLastSortColumn; //remember sort on navigation panel + bool naviLastSortAscending = zen::defaultValueLastSortAscending; // - bool showPercentBar; //in navigation panel + bool showPercentBar = zen::defaultValueShowPercentage; //in navigation panel ExternalApps externelApplications; std::vector<zen::ConfigHistoryItem> cfgFileHistory; - size_t cfgFileHistMax; + size_t cfgFileHistMax = 30; std::vector<Zstring> lastUsedConfigFiles; std::vector<Zstring> folderHistoryLeft; std::vector<Zstring> folderHistoryRight; - size_t folderHistMax; + size_t folderHistMax = 15; std::vector<Zstring> onCompletionHistory; - size_t onCompletionHistoryMax; + size_t onCompletionHistoryMax = 8; - Zstring defaultExclusionFilter; +#ifdef ZEN_WIN + Zstring defaultExclusionFilter = Zstr("\\System Volume Information\\") Zstr("\n") + Zstr("\\$Recycle.Bin\\") Zstr("\n") + Zstr("\\RECYCLER\\") Zstr("\n") + Zstr("\\RECYCLED\\") Zstr("\n") + Zstr("*\\desktop.ini") Zstr("\n") + Zstr("*\\thumbs.db"); +#elif defined ZEN_LINUX + Zstring defaultExclusionFilter = Zstr("/.Trash-*/") Zstr("\n") + Zstr("/.recycle/"); +#elif defined ZEN_MAC + Zstring defaultExclusionFilter = Zstr("/.fseventsd/") Zstr("\n") + Zstr("/.Spotlight-V100/") Zstr("\n") + Zstr("/.Trashes/") Zstr("\n") + Zstr("*/.DS_Store") Zstr("\n") + Zstr("*/._.*"); +#endif + struct + { + bool keepRelPaths = true; + bool overwriteIfExists = false; + Zstring lastUsedPath; + std::vector<Zstring> folderHistory; + size_t historySizeMax = 15; + } copyToCfg; - //bool deleteOnBothSides; - bool useRecyclerForManualDeletion; - bool textSearchRespectCase; + bool manualDeletionUseRecycler = true; - bool showIcons; - FileIconSize iconSize; +#if defined ZEN_WIN || defined ZEN_MAC + bool textSearchRespectCase = false; +#elif defined ZEN_LINUX + bool textSearchRespectCase = true; +#endif + bool showIcons = true; + FileIconSize iconSize = ICON_SIZE_SMALL; - long lastUpdateCheck; //time of last update check + long lastUpdateCheck = 0; //time of last update check wxString lastOnlineVersion; ViewFilterDefault viewFilterDefault; diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp index 50f2b260..5cc51236 100644 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/lib/resolve_path.cpp @@ -90,6 +90,9 @@ public: static const CsidlToDirMap& get() { +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif //function scope static initialization: avoid static initialization order problem in global namespace! static const CsidlToDirMap inst = createCsidlMapping(); return inst; @@ -117,9 +120,6 @@ private: //================================================================================================ //SHGetKnownFolderPath: API available only with Windows Vista and later: -#ifdef __MINGW32__ -#define KF_FLAG_DONT_VERIFY 0x00004000 -#endif typedef HRESULT (STDAPICALLTYPE* SHGetKnownFolderPathFunc)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath); const SysDllFun<SHGetKnownFolderPathFunc> shGetKnownFolderPath(L"Shell32.dll", "SHGetKnownFolderPath"); @@ -202,12 +202,6 @@ private: return output; } }; - -//caveat: function scope static initialization is not thread-safe in VS 2010! -#if defined _MSC_VER && _MSC_VER > 1800 - #error get rid! -#endif -auto& dummy = CsidlConstants::get(); #endif @@ -511,9 +505,9 @@ void getDirectoryAliasesRecursive(const Zstring& dirpath, std::set<Zstring, Less } -std::vector<Zstring> zen::getDirectoryAliases(const Zstring& dirpathPhrase) +std::vector<Zstring> zen::getDirectoryAliases(const Zstring& folderPathPhrase) { - const Zstring dirpath = trimCpy(dirpathPhrase, true, false); + const Zstring dirpath = trimCpy(folderPathPhrase, true, false); if (dirpath.empty()) return std::vector<Zstring>(); @@ -528,9 +522,9 @@ std::vector<Zstring> zen::getDirectoryAliases(const Zstring& dirpathPhrase) //coordinate changes with acceptsFolderPathPhraseNative()! -Zstring zen::getResolvedDirectoryPath(const Zstring& dirpassPhrase) //noexcept +Zstring zen::getResolvedDirectoryPath(const Zstring& folderPathPhrase) //noexcept { - Zstring dirpath = dirpassPhrase; + Zstring dirpath = folderPathPhrase; dirpath = expandMacros(dirpath); //expand before trimming! diff --git a/FreeFileSync/Source/lib/resolve_path.h b/FreeFileSync/Source/lib/resolve_path.h index 7bfcfd1e..d7e65b00 100644 --- a/FreeFileSync/Source/lib/resolve_path.h +++ b/FreeFileSync/Source/lib/resolve_path.h @@ -21,12 +21,12 @@ namespace zen => may block for slow USB sticks and idle HDDs => not thread-safe, see ::GetFullPathName()! */ -Zstring getResolvedDirectoryPath(const Zstring& dirpassPhrase); //noexcept +Zstring getResolvedDirectoryPath(const Zstring& folderPathPhrase); //noexcept //macro substitution only Zstring expandMacros(const Zstring& text); -std::vector<Zstring> getDirectoryAliases(const Zstring& dirpassPhrase); //may block for slow USB sticks when resolving [<volume name>] +std::vector<Zstring> getDirectoryAliases(const Zstring& folderPathPhrase); //may block for slow USB sticks when resolving [<volume name>] #ifdef ZEN_WIN //*blocks* if network is not reachable or when showing login prompt dialog! diff --git a/FreeFileSync/Source/lib/status_handler.h b/FreeFileSync/Source/lib/status_handler.h index 6f90506c..554d8226 100644 --- a/FreeFileSync/Source/lib/status_handler.h +++ b/FreeFileSync/Source/lib/status_handler.h @@ -87,7 +87,7 @@ protected: { abortRequested = true; statusText_ = _("Stop requested: Waiting for current operation to finish..."); - } //called from GUI code: this does NOT call abortProcessNow() immediately, but when we're out of the C GUI call stack + } //called from GUI code: this does NOT call abortProcessNow() immediately, but later when we're out of the C GUI call stack //implement Statistics Phase currentPhase() const override { return currentPhase_; } diff --git a/FreeFileSync/Source/lib/status_handler_impl.h b/FreeFileSync/Source/lib/status_handler_impl.h index 145d02f8..18465a71 100644 --- a/FreeFileSync/Source/lib/status_handler_impl.h +++ b/FreeFileSync/Source/lib/status_handler_impl.h @@ -40,9 +40,6 @@ class StatisticsReporter { public: StatisticsReporter(int itemsExpected, std::int64_t bytesExpected, ProcessCallback& cb) : - taskCancelled(true), - itemsReported(), - bytesReported(), itemsExpected_(itemsExpected), bytesExpected_(bytesExpected), cb_(cb) {} @@ -51,6 +48,10 @@ public: { if (taskCancelled) cb_.updateTotalData(itemsReported, bytesReported); //=> unexpected increase of total workload + else + //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams, + //less for sparse and compressed files, or file changed in the meantime! + cb_.updateTotalData(itemsReported - itemsExpected_, bytesReported - bytesExpected_); //noexcept! } void reportDelta(int itemsDelta, std::int64_t bytesDelta) //may throw! @@ -77,16 +78,13 @@ public: void reportFinished() //nothrow! { assert(taskCancelled); - //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams, - //less for sparse and compressed files, or file changed in the meantime! - cb_.updateTotalData(itemsReported - itemsExpected_, bytesReported - bytesExpected_); //noexcept! taskCancelled = false; } private: - bool taskCancelled; - int itemsReported; - std::int64_t bytesReported; + bool taskCancelled = true; + int itemsReported = 0; + std::int64_t bytesReported = 0; const int itemsExpected_; const std::int64_t bytesExpected_; ProcessCallback& cb_; diff --git a/FreeFileSync/Source/lib/versioning.cpp b/FreeFileSync/Source/lib/versioning.cpp index 44a6e288..ea3ce43b 100644 --- a/FreeFileSync/Source/lib/versioning.cpp +++ b/FreeFileSync/Source/lib/versioning.cpp @@ -127,14 +127,22 @@ void moveItem(const AbstractPathRef& sourcePath, //throw FileError auto removeTarget = [&] { - //remove target object - if (ABF::dirExists(targetPath)) //directory or dir-symlink + try { - assert(false); //we do not expect targetPath to be a directory in general (but possible!) - ABF::removeFolderRecursively(targetPath, nullptr /*onBeforeFileDeletion*/, nullptr /*onBeforeFolderDeletion*/); //throw FileError - } - else //file or (broken) file-symlink + //file or (broken) file-symlink: ABF::removeFile(targetPath); //throw FileError + } + catch (FileError&) + { + //folder or folder-symlink: + if (ABF::folderExists(targetPath)) //directory or dir-symlink + { + assert(ABF::symlinkExists(targetPath)); //we do not expect targetPath to be a directory in general (but possible!) + ABF::removeFolderRecursively(targetPath, nullptr /*onBeforeFileDeletion*/, nullptr /*onBeforeFolderDeletion*/); //throw FileError + } + else + throw; + } }; //first try to move directly without copying @@ -143,7 +151,7 @@ void moveItem(const AbstractPathRef& sourcePath, //throw FileError ABF::renameItem(sourcePath, targetPath); //throw FileError, ErrorTargetExisting, ErrorDifferentVolume return; //great, we get away cheaply! } - //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) + //if moving failed, treat as error (except when it tried to move to a different volume: in this case we will copy the file) catch (const ErrorDifferentVolume&) { removeTarget(); //throw FileError @@ -174,8 +182,8 @@ void moveFileOrSymlink(const AbstractPathRef& sourcePath, //throw FileError if (ABF::symlinkExists(sourcePath)) ABF::copySymlink(sourcePath, targetPath, false /*copy filesystem permissions*/); //throw FileError else - ABF::copyFileTransactional(sourcePath, targetPath, //throw FileError, (ErrorFileLocked) - false /*copyFilePermissions*/, true /*transactionalCopy*/, nullptr, onNotifyCopyStatus); + ABF::copyFileTransactional(sourcePath, targetPath, //throw FileError, ErrorFileLocked + false /*copyFilePermissions*/, true /*transactionalCopy*/, nullptr /*onDeleteTargetFile*/, onNotifyCopyStatus); ABF::removeFile(sourcePath); //throw FileError; newly copied file is NOT deleted if exception is thrown here! }; @@ -191,7 +199,7 @@ void moveFile(const AbstractPathRef& sourcePath, //throw FileError auto copyDelete = [&] { assert(!ABF::somethingExists(targetPath)); - ABF::copyFileTransactional(sourcePath, targetPath, //throw FileError, (ErrorFileLocked) + ABF::copyFileTransactional(sourcePath, targetPath, //throw FileError, ErrorFileLocked false /*copyFilePermissions*/, true /*transactionalCopy*/, nullptr /*onDeleteTargetFile*/, onNotifyCopyStatus); ABF::removeFile(sourcePath); //throw FileError; newly copied file is NOT deleted if exception is thrown here! }; @@ -231,7 +239,7 @@ struct FlatTraverserCallback: public ABF::TraverserCallback std::unique_ptr<TraverserCallback> onDir (const DirInfo& di) override { folderNames_.push_back(di.shortName); return nullptr; } HandleLink onSymlink(const SymlinkInfo& si) override { - if (ABF::dirExists(ABF::appendRelPath(folderPath_, si.shortName))) //dir symlink + if (ABF::folderExists(ABF::appendRelPath(folderPath_, si.shortName))) //dir symlink folderLinkNames_.push_back(si.shortName); else //file symlink, broken symlink fileLinkNames_.push_back(si.shortName); @@ -299,7 +307,7 @@ void FileVersioner::revisionFolderImpl(const AbstractPathRef& folderPath, const const std::function<void(std::int64_t bytesDelta)>& onNotifyCopyStatus) { assert(!ABF::symlinkExists(folderPath)); //[!] no symlinks in this context!!! - assert(ABF::dirExists(folderPath)); //Do NOT traverse into it deleting contained files!!! + assert(ABF::folderExists(folderPath)); //Do NOT traverse into it deleting contained files!!! //create target directories only when needed in moveFileToVersioning(): avoid empty directories! diff --git a/FreeFileSync/Source/structures.cpp b/FreeFileSync/Source/structures.cpp index 8db6bb00..28c2495e 100644 --- a/FreeFileSync/Source/structures.cpp +++ b/FreeFileSync/Source/structures.cpp @@ -391,8 +391,8 @@ FilterConfig mergeFilterConfig(const FilterConfig& global, const FilterConfig& l inline bool effectivelyEmpty(const FolderPairEnh& fp) { - return trimCpy(fp.dirpathPhraseLeft ).empty() && - trimCpy(fp.dirpathPhraseRight).empty(); + return trimCpy(fp.folderPathPhraseLeft_ ).empty() && + trimCpy(fp.folderPathPhraseRight_).empty(); } } diff --git a/FreeFileSync/Source/structures.h b/FreeFileSync/Source/structures.h index ac07c11a..f3cff19f 100644 --- a/FreeFileSync/Source/structures.h +++ b/FreeFileSync/Source/structures.h @@ -112,7 +112,7 @@ struct DirectionSet SyncDirection exLeftSideOnly; SyncDirection exRightSideOnly; - SyncDirection leftNewer; //CMP_BY_TIME_SIZE only! + SyncDirection leftNewer; //CMP_BY_TIME_SIZE only! SyncDirection rightNewer; // SyncDirection different; //CMP_BY_CONTENT only! SyncDirection conflict; @@ -335,19 +335,19 @@ struct FolderPairEnh //enhanced folder pairs with (optional) alternate configura { FolderPairEnh() {} - FolderPairEnh(const Zstring& phraseLeft, - const Zstring& phraseRight, + FolderPairEnh(const Zstring& folderPathPhraseLeft, + const Zstring& folderPathPhraseRight, const std::shared_ptr<const CompConfig>& cmpConfig, const std::shared_ptr<const SyncConfig>& syncConfig, const FilterConfig& filter) : - dirpathPhraseLeft (phraseLeft), - dirpathPhraseRight(phraseRight), + folderPathPhraseLeft_ (folderPathPhraseLeft), + folderPathPhraseRight_(folderPathPhraseRight), altCmpConfig(cmpConfig), altSyncConfig(syncConfig), localFilter(filter) {} - Zstring dirpathPhraseLeft; //unresolved directory names as entered by user! - Zstring dirpathPhraseRight; // + Zstring folderPathPhraseLeft_; //unresolved directory names as entered by user! + Zstring folderPathPhraseRight_; // std::shared_ptr<const CompConfig> altCmpConfig; //optional std::shared_ptr<const SyncConfig> altSyncConfig; // @@ -358,8 +358,8 @@ struct FolderPairEnh //enhanced folder pairs with (optional) alternate configura inline bool operator==(const FolderPairEnh& lhs, const FolderPairEnh& rhs) { - return lhs.dirpathPhraseLeft == rhs.dirpathPhraseLeft && - lhs.dirpathPhraseRight == rhs.dirpathPhraseRight && + return lhs.folderPathPhraseLeft_ == rhs.folderPathPhraseLeft_ && + lhs.folderPathPhraseRight_ == rhs.folderPathPhraseRight_ && (lhs.altCmpConfig.get() && rhs.altCmpConfig.get() ? *lhs.altCmpConfig == *rhs.altCmpConfig : diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp index 501ef39f..0593294c 100644 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/synchronization.cpp @@ -19,7 +19,8 @@ #include "lib/shadow.h" #elif defined ZEN_LINUX || defined ZEN_MAC - #include <fcntl.h> //open, close + #include <unistd.h> //fsync + #include <fcntl.h> //open #endif using namespace zen; @@ -538,7 +539,7 @@ void DeletionHandling::removeFileWithCallback(const AbstractPathRef& filePath, template <class Function> inline void DeletionHandling::removeLinkWithCallback(const AbstractPathRef& linkPath, const Zstring& relativePath, Function onNotifyItemDeletion, const std::function<void(std::int64_t bytesDelta)>& onNotifyCopyStatus) //throw FileError { - if (ABF::dirExists(linkPath)) //dir symlink + if (ABF::folderExists(linkPath)) //dir symlink return removeDirWithCallback(linkPath, relativePath, onNotifyItemDeletion, onNotifyCopyStatus); //throw FileError else //file symlink, broken symlink return removeFileWithCallback(linkPath, relativePath, onNotifyItemDeletion, onNotifyCopyStatus); //throw FileError @@ -1531,7 +1532,7 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirPair& dirObj, SyncOperation if (parentDir->isEmpty<sideTrg>()) //BaseDirPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize() return; //if parent directory creation failed, there's no reason to show more errors! - warn_static("save this file access:") + warn_static("save this file access?") if (ABF::somethingExists(dirObj.getAbstractPath<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should error out! { const AbstractPathRef targetPath = dirObj.getABF<sideTrg>().getAbstractPath(dirObj.getRelativePath<sideSrc>()); @@ -1541,7 +1542,7 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirPair& dirObj, SyncOperation { ABF::copyNewFolder(dirObj.getAbstractPath<sideSrc>(), targetPath, copyFilePermissions_); //throw FileError } - catch (const FileError&) { if (!ABF::dirExists(targetPath)) throw; } + catch (const FileError&) { if (!ABF::folderExists(targetPath)) throw; } //update DirPair dirObj.setSyncedTo(dirObj.getItemName<sideSrc>()); diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index 420e25db..4f65bd1e 100644 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -83,7 +83,7 @@ BatchDialog::BatchDialog(wxWindow* parent, m_bitmapBatchJob->SetBitmap(getResourceImage(L"batch")); - logfileDir = make_unique<FolderSelector>(*m_panelLogfile, *m_buttonSelectLogfileDir, *m_bpButtonSelectSftp, *m_logfileDir); + logfileDir = make_unique<FolderSelector>(*m_panelLogfile, *m_buttonSelectLogfileDir, *m_bpButtonSelectSftp, *m_logfileDir, nullptr /*staticText*/, nullptr /*wxWindow*/); setConfig(batchCfg); diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index 0872e9c6..a58da82e 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -288,7 +288,7 @@ BatchStatusHandler::~BatchStatusHandler() //notify to progressDlg that current process has ended if (abortIsRequested()) - progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events + progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events else if (totalErrors > 0) progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog); else if (totalWarnings > 0) @@ -305,7 +305,7 @@ BatchStatusHandler::~BatchStatusHandler() while (progressDlg) { wxTheApp->Yield(); //*first* refresh GUI (removing flicker) before sleeping! - boost::this_thread::sleep_for(boost::chrono::milliseconds(UI_UPDATE_INTERVAL)); //throw boost::thread_interrupted -> not expected => main thread! + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); } } } @@ -399,7 +399,7 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er { reportStatus(_("Error") + L": " + _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", (1000 * automaticRetryDelay_ - i * UI_UPDATE_INTERVAL + 999) / 1000)); //integer round up - boost::this_thread::sleep_for(boost::chrono::milliseconds(UI_UPDATE_INTERVAL)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); } return ProcessCallback::RETRY; } @@ -502,7 +502,7 @@ void BatchStatusHandler::forceUiRefresh() void BatchStatusHandler::abortProcessNow() { requestAbortion(); //just make sure... - throw BatchAbortProcess(); //abort can be triggered by progressDlg + throw BatchAbortProcess(); //abort can be triggered by progressDlg } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index 251875e4..b2ac7227 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -8,11 +8,9 @@ #define BATCH_STATUS_HANDLER_857390451451234566 #include <zen/error_log.h> -//#include <zen/file_io.h> #include <zen/time.h> #include "progress_indicator.h" #include "switch_to_gui.h" -//#include "../fs/concrete.h" #include "../lib/status_handler.h" #include "../lib/process_xml.h" #include "../lib/return_codes.h" diff --git a/FreeFileSync/Source/ui/check_version.cpp b/FreeFileSync/Source/ui/check_version.cpp index 716b8fdf..0cb63d6c 100644 --- a/FreeFileSync/Source/ui/check_version.cpp +++ b/FreeFileSync/Source/ui/check_version.cpp @@ -45,7 +45,7 @@ std::wstring getIso639Language() bufSize); //_In_ int cchData if (0 < rv && rv < bufSize) return buf; //MSDN: "This can be a 3-letter code for languages that don't have a 2-letter code"! - else assert(false); + assert(false); #endif const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); if (localeName.empty()) @@ -66,7 +66,7 @@ std::wstring getIso3166Country() bufSize); //_In_ int cchData if (0 < rv && rv < bufSize) return buf; //MSDN: "This can also return a number, such as "029" for Caribbean."! - else assert(false); + assert(false); #endif const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); if (localeName.empty()) diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp index 32a84b96..11de7272 100644 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/custom_grid.cpp @@ -5,6 +5,7 @@ // ************************************************************************** #include "custom_grid.h" +#include <set> #include <wx/dc.h> #include <wx/settings.h> #include <zen/i18n.h> @@ -517,28 +518,29 @@ private: const IconInfo ii = getIconInfo(row); - wxBitmap fileIcon = [&] + wxBitmap fileIcon; + switch (ii.type) { - switch (ii.type) - { - case IconInfo::FOLDER: - return iconMgr_->getGenericDirIcon(); - - case IconInfo::ICON_PATH: - if (Opt<wxBitmap> tmpIco = iconMgr_->refIconBuffer().retrieveFileIcon(ii.fsObj->template getAbstractPath<side>())) - return *tmpIco; + case IconInfo::FOLDER: + fileIcon = iconMgr_->getGenericDirIcon(); + break; + case IconInfo::ICON_PATH: + if (Opt<wxBitmap> tmpIco = iconMgr_->refIconBuffer().retrieveFileIcon(ii.fsObj->template getAbstractPath<side>())) + fileIcon = *tmpIco; + else + { setFailedLoad(row); //save status of failed icon load -> used for async. icon loading //falsify only! we want to avoid writing incorrect success values when only partially updating the DC, e.g. when scrolling, //see repaint behavior of ::ScrollWindow() function! - return iconMgr_->refIconBuffer().getIconByExtension(ii.fsObj->template getItemName<side>()); //better than nothing - //return iconMgr_->getGenericFileIcon(); + fileIcon = iconMgr_->refIconBuffer().getIconByExtension(ii.fsObj->template getItemName<side>()); //better than nothing + } + break; //return iconMgr_->getGenericFileIcon(); + + case IconInfo::EMPTY: + break; + } - case IconInfo::EMPTY: - break; - } - return wxBitmap(); - }(); if (fileIcon.IsOk()) { @@ -655,11 +657,11 @@ private: ICON_PATH, }; IconType type; - const FileSystemObject* fsObj; //only set if type != EMPTY + const FileSystemObject* fsObj; //only set if type != EMPTY bool drawAsLink; }; - IconInfo getIconInfo(size_t row) const //return ICON_FILE_FOLDER if row points to a folder + IconInfo getIconInfo(size_t row) const //return ICON_FILE_FOLDER if row points to a folder { IconInfo out = {}; diff --git a/FreeFileSync/Source/ui/folder_history_types.h b/FreeFileSync/Source/ui/folder_history_types.h index 768bc25c..ce7e5446 100644 --- a/FreeFileSync/Source/ui/folder_history_types.h +++ b/FreeFileSync/Source/ui/folder_history_types.h @@ -7,7 +7,6 @@ #ifndef FOLDER_HIST_TYPES_32481457137432143214 #define FOLDER_HIST_TYPES_32481457137432143214 -//#include <ctime> #include <zen/zstring.h> diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 09fd1c4d..3e0632af 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -26,23 +26,22 @@ using ABF = AbstractBaseFolder; namespace { -void setFolderPathPhrase(const Zstring& dirpath, FolderHistoryBox* comboBox, wxWindow& tooltipWnd, wxStaticText* staticText) //pointers are optional +void setFolderPathPhrase(const Zstring& folderPathPhrase, FolderHistoryBox* comboBox, wxWindow& tooltipWnd, wxStaticText* staticText) //pointers are optional { if (comboBox) - comboBox->setValue(toWx(dirpath)); + comboBox->setValue(toWx(folderPathPhrase)); - const Zstring folderPathPhrase = createAbstractBaseFolder(dirpath)->getInitPathPhrase(); //noexcept + const Zstring folderPathPhraseFmt = createAbstractBaseFolder(folderPathPhrase)->getInitPathPhrase(); //noexcept //may block when resolving [<volume name>] tooltipWnd.SetToolTip(nullptr); //workaround wxComboBox bug http://trac.wxwidgets.org/ticket/10512 / http://trac.wxwidgets.org/ticket/12659 - tooltipWnd.SetToolTip(toWx(folderPathPhrase)); //who knows when the real bugfix reaches mere mortals via an official release... + tooltipWnd.SetToolTip(toWx(folderPathPhraseFmt)); //who knows when the real bugfix reaches mere mortals via an official release... if (staticText) { //change static box label only if there is a real difference to what is shown in wxTextCtrl anyway - const Zstring dirpathFmt = trimCpy(dirpath); - - staticText->SetLabel(EqualFilePath()(appendSeparator(dirpathFmt), appendSeparator(folderPathPhrase)) ? wxString(_("Drag && drop")) : toWx(folderPathPhrase)); + staticText->SetLabel(EqualFilePath()(appendSeparator(trimCpy(folderPathPhrase)), appendSeparator(folderPathPhraseFmt)) ? + wxString(_("Drag && drop")) : toWx(folderPathPhraseFmt)); } } @@ -74,22 +73,21 @@ bool onIFileDialogAcceptFolder(HWND wnd, const Zstring& shellFolderPath) //############################################################################################################## - -const wxEventType zen::EVENT_ON_DIR_SELECTED = wxNewEventType(); -const wxEventType zen::EVENT_ON_DIR_MANUAL_CORRECTION = wxNewEventType(); +const wxEventType zen::EVENT_ON_FOLDER_SELECTED = wxNewEventType(); +const wxEventType zen::EVENT_ON_FOLDER_MANUAL_EDIT = wxNewEventType(); -FolderSelector::FolderSelector(wxWindow& dropWindow, - wxButton& selectFolderButton, - wxButton& selectSftpButton, - FolderHistoryBox& dirpath, - wxStaticText* staticText, - wxWindow* dropWindow2) : +FolderSelector::FolderSelector(wxWindow& dropWindow, + wxButton& selectFolderButton, + wxButton& selectSftpButton, + FolderHistoryBox& folderComboBox, + wxStaticText* staticText, + wxWindow* dropWindow2) : dropWindow_(dropWindow), dropWindow2_(dropWindow2), selectFolderButton_(selectFolderButton), selectSftpButton_(selectSftpButton), - dirpath_(dirpath), + folderComboBox_(folderComboBox), staticText_(staticText) { auto setupDragDrop = [&](wxWindow& dropWin) @@ -105,7 +103,6 @@ FolderSelector::FolderSelector(wxWindow& dropWindow, setupDragDrop(dropWindow_); if (dropWindow2_) setupDragDrop(*dropWindow2_); - #ifdef ZEN_WIN_VISTA_AND_LATER selectSftpButton.SetBitmapLabel(getResourceImage(L"sftp_small")); #else @@ -113,10 +110,10 @@ FolderSelector::FolderSelector(wxWindow& dropWindow, #endif //keep dirPicker and dirpath synchronous - dirpath_ .Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector::onMouseWheel ), nullptr, this); - dirpath_ .Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector::onWriteDirManually), nullptr, this); - selectFolderButton_.Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectFolder ), nullptr, this); - selectSftpButton_ .Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectSftp ), nullptr, this); + folderComboBox_ .Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector::onMouseWheel ), nullptr, this); + folderComboBox_ .Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector::onEditFolderPath), nullptr, this); + selectFolderButton_.Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectFolder ), nullptr, this); + selectSftpButton_ .Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectSftp ), nullptr, this); } @@ -127,10 +124,10 @@ FolderSelector::~FolderSelector() if (dropWindow2_) dropWindow2_->Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onFilesDropped), nullptr, this); - dirpath_ .Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector::onMouseWheel ), nullptr, this); - dirpath_ .Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector::onWriteDirManually), nullptr, this); - selectFolderButton_.Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectFolder ), nullptr, this); - selectSftpButton_ .Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectSftp ), nullptr, this); + folderComboBox_ .Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector::onMouseWheel ), nullptr, this); + folderComboBox_ .Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector::onEditFolderPath), nullptr, this); + selectFolderButton_.Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectFolder ), nullptr, this); + selectSftpButton_ .Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectSftp ), nullptr, this); } @@ -140,7 +137,7 @@ void FolderSelector::onMouseWheel(wxMouseEvent& event) //additionally this will delete manual entries, although all the users wanted is scroll the parent window! //redirect to parent scrolled window! - wxWindow* wnd = &dirpath_; + wxWindow* wnd = &folderComboBox_; while ((wnd = wnd->GetParent()) != nullptr) //silence MSVC warning if (dynamic_cast<wxScrolledWindow*>(wnd) != nullptr) if (wxEvtHandler* evtHandler = wnd->GetEventHandler()) @@ -154,36 +151,43 @@ void FolderSelector::onMouseWheel(wxMouseEvent& event) void FolderSelector::onFilesDropped(FileDropEvent& event) { - const auto& files = event.getFiles(); - if (files.empty()) + const auto& itemPaths = event.getPaths(); + if (itemPaths.empty()) return; - if (canSetDroppedShellPaths(files)) + if (canSetDroppedShellPaths(itemPaths)) { - Zstring newFolderPathPhrase = files[0]; - - if (!ABF::dirExists(createAbstractBaseFolder(files[0])->getAbstractPath())) + auto fmtShellPath = [](const Zstring& shellItemPath) { - Zstring parentPathPhrase = beforeLast(files[0], FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - if (!parentPathPhrase.empty()) + std::unique_ptr<ABF> abf = createAbstractBaseFolder(shellItemPath); + + if (!ABF::folderExists(abf->getAbstractPath())) { + Zstring parentShellPath = beforeLast(shellItemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + if (!parentShellPath.empty()) + { #ifdef ZEN_WIN - if (endsWith(parentPathPhrase, L":")) //volume root - parentPathPhrase += FILE_NAME_SEPARATOR; + if (endsWith(parentShellPath, L":")) //volume root + parentShellPath += FILE_NAME_SEPARATOR; #endif - if (ABF::dirExists(createAbstractBaseFolder(parentPathPhrase)->getAbstractPath())) - newFolderPathPhrase = parentPathPhrase; - //else: keep original name unconditionally: usecase: inactive mapped network shares - } - } + std::unique_ptr<ABF> abfParent = createAbstractBaseFolder(parentShellPath); - //make sure FFS-specific explicit MTP-syntax is applied! - newFolderPathPhrase = createAbstractBaseFolder(newFolderPathPhrase)->getInitPathPhrase(); + if (ABF::folderExists(abfParent->getAbstractPath())) + return abfParent->getInitPathPhrase(); + //else: keep original name unconditionally: usecase: inactive mapped network shares + } + } + //make sure FFS-specific explicit MTP-syntax is applied! + return abf->getInitPathPhrase(); + }; - setFolderPathPhrase(newFolderPathPhrase, &dirpath_, dirpath_, staticText_); + setPath(fmtShellPath(itemPaths[0])); + //drop two folder paths at once: + if (siblingSelector && itemPaths.size() >= 2) + siblingSelector->setPath(fmtShellPath(itemPaths[1])); //notify action invoked by user - wxCommandEvent dummy(EVENT_ON_DIR_SELECTED); + wxCommandEvent dummy(EVENT_ON_FOLDER_SELECTED); ProcessEvent(dummy); } else @@ -191,11 +195,11 @@ void FolderSelector::onFilesDropped(FileDropEvent& event) } -void FolderSelector::onWriteDirManually(wxCommandEvent& event) +void FolderSelector::onEditFolderPath(wxCommandEvent& event) { - setFolderPathPhrase(toZ(event.GetString()), nullptr, dirpath_, staticText_); + setFolderPathPhrase(toZ(event.GetString()), nullptr, folderComboBox_, staticText_); - wxCommandEvent dummy(EVENT_ON_DIR_MANUAL_CORRECTION); + wxCommandEvent dummy(EVENT_ON_FOLDER_MANUAL_EDIT); ProcessEvent(dummy); event.Skip(); } @@ -211,7 +215,8 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event) { auto baseFolderExisting = [](const ABF& abf) { - std::function<bool()> /*throw FileError*/ dirExists = ABF::getAsyncCheckDirExists(abf.getAbstractPath()); //noexcept + std::function<bool()> /*throw FileError*/ dirExists = ABF::getAsyncCheckFolderExists(abf.getAbstractPath()); //noexcept + auto ft = runAsync([dirExists] { try @@ -220,7 +225,7 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event) } catch (FileError&) { return false; } }); - return ft.wait_for(boost::chrono::milliseconds(200)) == boost::future_status::ready && ft.get(); //potentially slow network access: wait 200ms at most + return ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get(); //potentially slow network access: wait 200ms at most }; const Zstring folderPathPhrase = getPath(); @@ -268,10 +273,10 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event) const Zstring newFolderPathPhrase = toZ(dirPicker.GetPath()); #endif - setFolderPathPhrase(newFolderPathPhrase, &dirpath_, dirpath_, staticText_); + setFolderPathPhrase(newFolderPathPhrase, &folderComboBox_, folderComboBox_, staticText_); //notify action invoked by user - wxCommandEvent dummy(EVENT_ON_DIR_SELECTED); + wxCommandEvent dummy(EVENT_ON_FOLDER_SELECTED); ProcessEvent(dummy); } @@ -283,10 +288,10 @@ void FolderSelector::onSelectSftp(wxCommandEvent& event) if (showSftpSetupDialog(&selectSftpButton_, folderPathPhrase) != ReturnSmallDlg::BUTTON_OKAY) return; - setFolderPathPhrase(folderPathPhrase, &dirpath_, dirpath_, staticText_); + setFolderPathPhrase(folderPathPhrase, &folderComboBox_, folderComboBox_, staticText_); //notify action invoked by user - wxCommandEvent dummy(EVENT_ON_DIR_SELECTED); + wxCommandEvent dummy(EVENT_ON_FOLDER_SELECTED); ProcessEvent(dummy); #endif } @@ -294,11 +299,11 @@ void FolderSelector::onSelectSftp(wxCommandEvent& event) Zstring FolderSelector::getPath() const { - return toZ(dirpath_.GetValue()); + return toZ(folderComboBox_.GetValue()); } -void FolderSelector::setPath(const Zstring& dirpath) +void FolderSelector::setPath(const Zstring& folderPathPhrase) { - setFolderPathPhrase(dirpath, &dirpath_, dirpath_, staticText_); + setFolderPathPhrase(folderPathPhrase, &folderComboBox_, folderComboBox_, staticText_); } diff --git a/FreeFileSync/Source/ui/folder_selector.h b/FreeFileSync/Source/ui/folder_selector.h index 243a498a..3cb0b986 100644 --- a/FreeFileSync/Source/ui/folder_selector.h +++ b/FreeFileSync/Source/ui/folder_selector.h @@ -24,40 +24,43 @@ Reasons NOT to use wxDirPickerCtrl, but wxButton instead: - hard-codes "Browse" button label */ -extern const wxEventType EVENT_ON_DIR_SELECTED; //directory is changed by the user (except manual type-in) -extern const wxEventType EVENT_ON_DIR_MANUAL_CORRECTION; //manual type-in -//example: wnd.Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MyDlg::OnDirSelected), nullptr, this); +extern const wxEventType EVENT_ON_FOLDER_SELECTED; //directory is changed by the user (except manual type-in) +extern const wxEventType EVENT_ON_FOLDER_MANUAL_EDIT; //manual type-in +//example: wnd.Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MyDlg::OnDirSelected), nullptr, this); class FolderSelector: public wxEvtHandler { public: - FolderSelector(wxWindow& dropWindow, - wxButton& selectFolderButton, - wxButton& selectSftpButton, - FolderHistoryBox& dirpath, - wxStaticText* staticText = nullptr, //optional - wxWindow* dropWindow2 = nullptr); // + FolderSelector(wxWindow& dropWindow, + wxButton& selectFolderButton, + wxButton& selectSftpButton, + FolderHistoryBox& folderComboBox, + wxStaticText* staticText, //optional + wxWindow* dropWindow2); // ~FolderSelector(); + void setSiblingSelector(FolderSelector* selector) { siblingSelector = selector; } + Zstring getPath() const; - void setPath(const Zstring& dirpath); + void setPath(const Zstring& folderPathPhrase); private: virtual bool canSetDroppedShellPaths(const std::vector<Zstring>& shellItemPaths) { return true; }; //return true if drop should be processed - void onMouseWheel (wxMouseEvent& event); - void onFilesDropped (FileDropEvent& event); - void onWriteDirManually(wxCommandEvent& event); - void onSelectFolder (wxCommandEvent& event); - void onSelectSftp (wxCommandEvent& event); + void onMouseWheel (wxMouseEvent& event); + void onFilesDropped (FileDropEvent& event); + void onEditFolderPath(wxCommandEvent& event); + void onSelectFolder (wxCommandEvent& event); + void onSelectSftp (wxCommandEvent& event); - wxWindow& dropWindow_; - wxWindow* dropWindow2_; - wxButton& selectFolderButton_; - wxButton& selectSftpButton_; - FolderHistoryBox& dirpath_; - wxStaticText* staticText_; //optional + wxWindow& dropWindow_; + wxWindow* dropWindow2_ = nullptr; + wxButton& selectFolderButton_; + wxButton& selectSftpButton_; + FolderHistoryBox& folderComboBox_; + wxStaticText* staticText_ = nullptr; //optional + FolderSelector* siblingSelector = nullptr; }; } diff --git a/FreeFileSync/Source/ui/grid_view.cpp b/FreeFileSync/Source/ui/grid_view.cpp index a34d42c4..51c7c711 100644 --- a/FreeFileSync/Source/ui/grid_view.cpp +++ b/FreeFileSync/Source/ui/grid_view.cpp @@ -281,17 +281,17 @@ GridView::StatusSyncPreview GridView::updateSyncPreview(bool showExcluded, //map } -std::vector<FileSystemObject*> GridView::getAllFileRef(const std::set<size_t>& rows) +std::vector<FileSystemObject*> GridView::getAllFileRef(const std::vector<size_t>& rows) { + const size_t viewSize = viewRef.size(); + std::vector<FileSystemObject*> output; - auto iterLast = rows.lower_bound(rowsOnView()); //loop over valid rows only! - std::for_each(rows.begin(), iterLast, - [&](size_t pos) - { - if (FileSystemObject* fsObj = FileSystemObject::retrieve(viewRef[pos])) - output.push_back(fsObj); - }); + for (size_t pos : rows) + if (pos < viewSize) + if (FileSystemObject* fsObj = FileSystemObject::retrieve(viewRef[pos])) + output.push_back(fsObj); + return output; } diff --git a/FreeFileSync/Source/ui/grid_view.h b/FreeFileSync/Source/ui/grid_view.h index 3ee2f642..5a2fb293 100644 --- a/FreeFileSync/Source/ui/grid_view.h +++ b/FreeFileSync/Source/ui/grid_view.h @@ -7,7 +7,7 @@ #ifndef GRIDVIEW_H_INCLUDED #define GRIDVIEW_H_INCLUDED -#include <set> +#include <vector> #include <unordered_map> #include "column_attr.h" #include "../file_hierarchy.h" @@ -22,14 +22,14 @@ public: GridView() : folderPairCount(0) {} //direct data access via row number - const FileSystemObject* getObject(size_t row) const; //returns nullptr if object is not found; complexity: constant! + const FileSystemObject* getObject(size_t row) const; //returns nullptr if object is not found; complexity: constant! /**/ FileSystemObject* getObject(size_t row); // size_t rowsOnView() const { return viewRef .size(); } //only visible elements size_t rowsTotal () const { return sortedRef.size(); } //total rows available //get references to FileSystemObject: no nullptr-check needed! Everything's bound. - std::vector<FileSystemObject*> getAllFileRef(const std::set<size_t>& rows); + std::vector<FileSystemObject*> getAllFileRef(const std::vector<size_t>& rows); struct StatusCmpResult { @@ -141,7 +141,7 @@ private: std::unordered_map<const void*, size_t> rowPositionsFirstChild; //find first child on sortedRef of a hierarchy object //void* instead of HierarchyObject*: these are weak pointers and should *never be dereferenced*! - std::vector<FileSystemObject::ObjectId> viewRef; //partial view on sortedRef + std::vector<FileSystemObject::ObjectId> viewRef; //partial view on sortedRef /* /|\ | (update...) | */ diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index a5b0f856..5cd7df3a 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -71,7 +71,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menubar1->Append( m_menu4, _("&Actions") ); m_menuTools = new wxMenu(); - m_menuItemOptions = new wxMenuItem( m_menuTools, wxID_PREFERENCES, wxString( _("&Options") ) , wxEmptyString, wxITEM_NORMAL ); + m_menuItemOptions = new wxMenuItem( m_menuTools, wxID_PREFERENCES, wxString( _("&Preferences") ) + wxT('\t') + wxT("Ctrl+,"), wxEmptyString, wxITEM_NORMAL ); m_menuTools->Append( m_menuItemOptions ); m_menuLanguages = new wxMenu(); @@ -1858,48 +1858,61 @@ SftpSetupDlgGenerated::SftpSetupDlgGenerated( wxWindow* parent, wxWindowID id, c m_staticText12311 = new wxStaticText( m_panel41, wxID_ANY, _("Server name or IP address:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText12311->Wrap( -1 ); - fgSizer16->Add( m_staticText12311, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + fgSizer16->Add( m_staticText12311, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxTOP|wxBOTTOM|wxLEFT, 5 ); - m_textCtrlServer = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 300,-1 ), 0 ); - fgSizer16->Add( m_textCtrlServer, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + wxBoxSizer* bSizer183; + bSizer183 = new wxBoxSizer( wxHORIZONTAL ); - m_staticText1381 = new wxStaticText( m_panel41, wxID_ANY, _("Examples:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText1381->Wrap( -1 ); - m_staticText1381->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + m_textCtrlServer = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 260,-1 ), 0 ); + bSizer183->Add( m_textCtrlServer, 0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticText1233 = new wxStaticText( m_panel41, wxID_ANY, _("Port:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1233->Wrap( -1 ); + bSizer183->Add( m_staticText1233, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxTOP|wxBOTTOM|wxLEFT, 5 ); - fgSizer16->Add( m_staticText1381, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + m_textCtrlPort = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 60,-1 ), 0 ); + bSizer183->Add( m_textCtrlPort, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + + fgSizer16->Add( bSizer183, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + + fgSizer16->Add( 0, 0, 0, 0, 5 ); wxBoxSizer* bSizer181; bSizer181 = new wxBoxSizer( wxHORIZONTAL ); + m_staticText1381 = new wxStaticText( m_panel41, wxID_ANY, _("Examples:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1381->Wrap( -1 ); + m_staticText1381->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer181->Add( m_staticText1381, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxBOTTOM|wxRIGHT, 5 ); + m_staticText1382 = new wxStaticText( m_panel41, wxID_ANY, _("www.website.com"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1382->Wrap( -1 ); m_staticText1382->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - bSizer181->Add( m_staticText1382, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - - bSizer181->Add( 0, 0, 1, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer181->Add( m_staticText1382, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); m_staticText138 = new wxStaticText( m_panel41, wxID_ANY, _("123.123.123"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText138->Wrap( -1 ); m_staticText138->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - bSizer181->Add( m_staticText138, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer181->Add( m_staticText138, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - fgSizer16->Add( bSizer181, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); + fgSizer16->Add( bSizer181, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); m_staticText123 = new wxStaticText( m_panel41, wxID_ANY, _("User name:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText123->Wrap( -1 ); - fgSizer16->Add( m_staticText123, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + fgSizer16->Add( m_staticText123, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_textCtrlUserName = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); fgSizer16->Add( m_textCtrlUserName, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticText1231 = new wxStaticText( m_panel41, wxID_ANY, _("Password:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1231->Wrap( -1 ); - fgSizer16->Add( m_staticText1231, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxTOP|wxRIGHT|wxLEFT, 5 ); + fgSizer16->Add( m_staticText1231, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxTOP|wxLEFT, 5 ); wxBoxSizer* bSizer182; bSizer182 = new wxBoxSizer( wxVERTICAL ); @@ -1921,13 +1934,13 @@ SftpSetupDlgGenerated::SftpSetupDlgGenerated( wxWindow* parent, wxWindowID id, c m_staticText1232 = new wxStaticText( m_panel41, wxID_ANY, _("Directory on server:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1232->Wrap( -1 ); - fgSizer16->Add( m_staticText1232, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + fgSizer16->Add( m_staticText1232, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_textCtrlServerPath = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); fgSizer16->Add( m_textCtrlServerPath, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer185->Add( fgSizer16, 1, wxALL|wxEXPAND, 5 ); + bSizer185->Add( fgSizer16, 0, wxALL, 5 ); m_panel41->SetSizer( bSizer185 ); @@ -3048,6 +3061,120 @@ DeleteDlgGenerated::~DeleteDlgGenerated() { } +CopyToDlgGenerated::CopyToDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxSize( -1,-1 ), wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer24; + bSizer24 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSizer72; + bSizer72 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapCopyTo = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer72->Add( m_bitmapCopyTo, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 10 ); + + m_staticTextHeader = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0|wxNO_BORDER ); + m_staticTextHeader->Wrap( 460 ); + bSizer72->Add( m_staticTextHeader, 0, wxALIGN_CENTER_VERTICAL|wxALL, 10 ); + + + bSizer24->Add( bSizer72, 0, 0, 5 ); + + m_staticline91 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer24->Add( m_staticline91, 0, wxEXPAND, 5 ); + + m_panel31 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel31->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer185; + bSizer185 = new wxBoxSizer( wxHORIZONTAL ); + + + bSizer185->Add( 60, 0, 0, 0, 5 ); + + m_staticline42 = new wxStaticLine( m_panel31, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer185->Add( m_staticline42, 0, wxEXPAND, 5 ); + + m_textCtrlFileList = new wxTextCtrl( m_panel31, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 500,200 ), wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + bSizer185->Add( m_textCtrlFileList, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); + + + m_panel31->SetSizer( bSizer185 ); + m_panel31->Layout(); + bSizer185->Fit( m_panel31 ); + bSizer24->Add( m_panel31, 1, wxEXPAND|wxALIGN_CENTER_HORIZONTAL, 5 ); + + wxBoxSizer* bSizer182; + bSizer182 = new wxBoxSizer( wxHORIZONTAL ); + + m_directoryTarget = new FolderHistoryBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); + bSizer182->Add( m_directoryTarget, 1, wxALIGN_CENTER_VERTICAL, 5 ); + + m_buttonSelectFolder = new wxButton( this, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 ); + m_buttonSelectFolder->SetToolTip( _("Select a folder") ); + + bSizer182->Add( m_buttonSelectFolder, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + m_bpButtonSelectSftp = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 26,-1 ), wxBU_AUTODRAW ); + m_bpButtonSelectSftp->SetToolTip( _("Select SFTP folder") ); + + bSizer182->Add( m_bpButtonSelectSftp, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); + + + bSizer24->Add( bSizer182, 0, wxEXPAND|wxALL, 5 ); + + m_staticline9 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer24->Add( m_staticline9, 0, wxEXPAND, 5 ); + + bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); + + wxBoxSizer* bSizer189; + bSizer189 = new wxBoxSizer( wxVERTICAL ); + + m_checkBoxKeepRelPath = new wxCheckBox( this, wxID_ANY, _("&Keep relative paths"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkBoxKeepRelPath->SetValue(true); + bSizer189->Add( m_checkBoxKeepRelPath, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 5 ); + + m_checkBoxOverwriteIfExists = new wxCheckBox( this, wxID_ANY, _("&Overwrite existing files"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkBoxOverwriteIfExists->SetValue(true); + bSizer189->Add( m_checkBoxOverwriteIfExists, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + + + bSizerStdButtons->Add( bSizer189, 1, wxALIGN_CENTER_VERTICAL, 5 ); + + m_buttonOK = new wxButton( this, wxID_OK, _("&Copy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_buttonOK->SetDefault(); + m_buttonOK->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + + bSizerStdButtons->Add( m_buttonOK, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer24->Add( bSizerStdButtons, 0, wxEXPAND, 5 ); + + + this->SetSizer( bSizer24 ); + this->Layout(); + bSizer24->Fit( this ); + + this->Centre( wxBOTH ); + + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CopyToDlgGenerated::OnClose ) ); + m_checkBoxKeepRelPath->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnUseRecycler ), NULL, this ); + m_checkBoxOverwriteIfExists->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnUseRecycler ), NULL, this ); + m_buttonOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnOK ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnCancel ), NULL, this ); +} + +CopyToDlgGenerated::~CopyToDlgGenerated() +{ +} + OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { this->SetSizeHints( wxSize( -1,-1 ), wxDefaultSize ); @@ -3436,18 +3563,6 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer171->Add( m_hyperlink11, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink9 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("MinGW"), wxT("http://www.mingw.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink9->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink9->SetToolTip( _("http://www.mingw.org") ); - - bSizer171->Add( m_hyperlink9, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - - m_hyperlink10 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Code::Blocks"), wxT("http://www.codeblocks.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink10->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink10->SetToolTip( _("http://www.codeblocks.org") ); - - bSizer171->Add( m_hyperlink10, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink7 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("wxWidgets"), wxT("http://www.wxwidgets.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink7->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); m_hyperlink7->SetToolTip( _("http://www.wxwidgets.org") ); @@ -3458,7 +3573,13 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_hyperlink14->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); m_hyperlink14->SetToolTip( _("http://wxformbuilder.org") ); - bSizer171->Add( m_hyperlink14, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer171->Add( m_hyperlink14, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + m_hyperlink16 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Artistic Style"), wxT("http://astyle.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink16->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink16->SetToolTip( _("http://astyle.sourceforge.net") ); + + bSizer171->Add( m_hyperlink16, 0, wxALIGN_CENTER_VERTICAL, 5 ); bSizer187->Add( bSizer171, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); @@ -3472,29 +3593,35 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer172->Add( m_hyperlink15, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_hyperlink12 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Google Test"), wxT("http://code.google.com/p/googletest"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink12->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink12->SetToolTip( _("http://code.google.com/p/googletest") ); + + bSizer172->Add( m_hyperlink12, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_hyperlink13 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Boost"), wxT("http://www.boost.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink13->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); m_hyperlink13->SetToolTip( _("http://www.boost.org") ); bSizer172->Add( m_hyperlink13, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink16 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Artistic Style"), wxT("http://astyle.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink16->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink16->SetToolTip( _("http://astyle.sourceforge.net") ); + m_hyperlink10 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("libssh2"), wxT("http://www.libssh2.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink10->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink10->SetToolTip( _("http://www.libssh2.org") ); - bSizer172->Add( m_hyperlink16, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer172->Add( m_hyperlink10, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink12 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Google Test"), wxT("http://code.google.com/p/googletest"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink12->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink12->SetToolTip( _("http://code.google.com/p/googletest") ); + m_hyperlink18 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("NSIS"), wxT("http://nsis.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink18->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink18->SetToolTip( _("http://nsis.sourceforge.net") ); - bSizer172->Add( m_hyperlink12, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer172->Add( m_hyperlink18, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink18 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Unicode NSIS"), wxT("http://www.scratchpaper.com"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink18->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink18->SetToolTip( _("http://www.scratchpaper.com") ); + m_hyperlink9 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Inno Setup"), wxT("http://www.jrsoftware.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink9->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink9->SetToolTip( _("http://www.jrsoftware.org") ); - bSizer172->Add( m_hyperlink18, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer172->Add( m_hyperlink9, 0, wxALIGN_CENTER_VERTICAL, 5 ); bSizer187->Add( bSizer172, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -3575,6 +3702,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_hyperlink1 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync.org"), wxT("http://www.freefilesync.org/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink1->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, true, wxEmptyString ) ); m_hyperlink1->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink1->SetToolTip( _("http://www.freefilesync.org") ); bSizer166->Add( m_hyperlink1, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -3589,6 +3717,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_hyperlink2 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("zenju@gmx.de"), wxT("mailto:zenju@gmx.de"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink2->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, true, wxEmptyString ) ); m_hyperlink2->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink2->SetToolTip( _("mailto:zenju@gmx.de") ); bSizer166->Add( m_hyperlink2, 0, wxALIGN_CENTER_VERTICAL, 5 ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index bc69b713..b1fd8228 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -435,6 +435,8 @@ protected: wxPanel* m_panel41; wxStaticText* m_staticText12311; wxTextCtrl* m_textCtrlServer; + wxStaticText* m_staticText1233; + wxTextCtrl* m_textCtrlPort; wxStaticText* m_staticText1381; wxStaticText* m_staticText1382; wxStaticText* m_staticText138; @@ -738,6 +740,44 @@ public: }; /////////////////////////////////////////////////////////////////////////////// +/// Class CopyToDlgGenerated +/////////////////////////////////////////////////////////////////////////////// +class CopyToDlgGenerated : public wxDialog +{ +private: + +protected: + wxStaticBitmap* m_bitmapCopyTo; + wxStaticText* m_staticTextHeader; + wxStaticLine* m_staticline91; + wxPanel* m_panel31; + wxStaticLine* m_staticline42; + wxTextCtrl* m_textCtrlFileList; + wxButton* m_buttonSelectFolder; + wxStaticLine* m_staticline9; + wxBoxSizer* bSizerStdButtons; + wxCheckBox* m_checkBoxKeepRelPath; + wxCheckBox* m_checkBoxOverwriteIfExists; + wxButton* m_buttonOK; + wxButton* m_buttonCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void OnUseRecycler( wxCommandEvent& event ) { event.Skip(); } + virtual void OnOK( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + + +public: + FolderHistoryBox* m_directoryTarget; + wxBitmapButton* m_bpButtonSelectSftp; + + CopyToDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Copy items"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER ); + ~CopyToDlgGenerated(); + +}; + +/////////////////////////////////////////////////////////////////////////////// /// Class OptionsDlgGenerated /////////////////////////////////////////////////////////////////////////////// class OptionsDlgGenerated : public wxDialog @@ -858,15 +898,15 @@ protected: wxStaticLine* m_staticline341; wxStaticText* m_staticText96; wxHyperlinkCtrl* m_hyperlink11; - wxHyperlinkCtrl* m_hyperlink9; - wxHyperlinkCtrl* m_hyperlink10; wxHyperlinkCtrl* m_hyperlink7; wxHyperlinkCtrl* m_hyperlink14; - wxHyperlinkCtrl* m_hyperlink15; - wxHyperlinkCtrl* m_hyperlink13; wxHyperlinkCtrl* m_hyperlink16; + wxHyperlinkCtrl* m_hyperlink15; wxHyperlinkCtrl* m_hyperlink12; + wxHyperlinkCtrl* m_hyperlink13; + wxHyperlinkCtrl* m_hyperlink10; wxHyperlinkCtrl* m_hyperlink18; + wxHyperlinkCtrl* m_hyperlink9; wxPanel* m_panelDonate; wxPanel* m_panel39; wxStaticBitmap* m_bitmapDonate; diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index fcaeff50..2502788b 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -20,7 +20,7 @@ using namespace zen; using namespace xmlAccess; -CompareStatusHandler::CompareStatusHandler(MainDialog& dlg) : +StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg) : mainDlg(dlg), ignoreErrors(false) { @@ -28,7 +28,6 @@ CompareStatusHandler::CompareStatusHandler(MainDialog& dlg) : #ifdef ZEN_WIN wxWindowUpdateLocker dummy(&mainDlg); //leads to GUI corruption problems on Linux/OS X! #endif - mainDlg.compareStatus->init(*this); //clear old values before showing panel //------------------------------------------------------------------ @@ -92,16 +91,16 @@ CompareStatusHandler::CompareStatusHandler(MainDialog& dlg) : mainDlg.Update(); //don't wait until idle event! //register keys - mainDlg.Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(CompareStatusHandler::OnKeyPressed), nullptr, this); - mainDlg.m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(CompareStatusHandler::OnAbortCompare), nullptr, this); + mainDlg.Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StatusHandlerTemporaryPanel::OnKeyPressed), nullptr, this); + mainDlg.m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusHandlerTemporaryPanel::OnAbortCompare), nullptr, this); } -CompareStatusHandler::~CompareStatusHandler() +StatusHandlerTemporaryPanel::~StatusHandlerTemporaryPanel() { //unregister keys - mainDlg.Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(CompareStatusHandler::OnKeyPressed), nullptr, this); - mainDlg.m_buttonCancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(CompareStatusHandler::OnAbortCompare), nullptr, this); + mainDlg.Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StatusHandlerTemporaryPanel::OnKeyPressed), nullptr, this); + mainDlg.m_buttonCancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusHandlerTemporaryPanel::OnAbortCompare), nullptr, this); mainDlg.auiMgr.GetPane(mainDlg.compareStatus->getAsWindow()).Hide(); mainDlg.auiMgr.Update(); @@ -109,7 +108,7 @@ CompareStatusHandler::~CompareStatusHandler() } -void CompareStatusHandler::OnKeyPressed(wxKeyEvent& event) +void StatusHandlerTemporaryPanel::OnKeyPressed(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); if (keyCode == WXK_ESCAPE) @@ -122,17 +121,17 @@ void CompareStatusHandler::OnKeyPressed(wxKeyEvent& event) } -void CompareStatusHandler::initNewPhase(int objectsTotal, std::int64_t dataTotal, Phase phaseID) +void StatusHandlerTemporaryPanel::initNewPhase(int objectsTotal, std::int64_t dataTotal, Phase phaseID) { StatusHandler::initNewPhase(objectsTotal, dataTotal, phaseID); switch (currentPhase()) { case PHASE_NONE: - case PHASE_SYNCHRONIZING: assert(false); case PHASE_SCANNING: break; + case PHASE_SYNCHRONIZING: case PHASE_COMPARING_CONTENT: { #ifdef ZEN_WIN @@ -149,7 +148,7 @@ void CompareStatusHandler::initNewPhase(int objectsTotal, std::int64_t dataTotal } -ProcessCallback::Response CompareStatusHandler::reportError(const std::wstring& errorMessage, size_t retryNumber) +ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::wstring& errorMessage, size_t retryNumber) { //no need to implement auto-retry here: 1. user is watching 2. comparison is fast //=> similar behavior like "ignoreErrors" which does not honor sync settings @@ -182,14 +181,14 @@ ProcessCallback::Response CompareStatusHandler::reportError(const std::wstring& } -void CompareStatusHandler::reportFatalError(const std::wstring& errorMessage) +void StatusHandlerTemporaryPanel::reportFatalError(const std::wstring& errorMessage) { forceUiRefresh(); showNotificationDialog(&mainDlg, DialogInfoType::ERROR2, PopupDialogCfg().setTitle(_("Serious Error")).setDetailInstructions(errorMessage)); } -void CompareStatusHandler::reportWarning(const std::wstring& warningMessage, bool& warningActive) +void StatusHandlerTemporaryPanel::reportWarning(const std::wstring& warningMessage, bool& warningActive) { if (!warningActive || ignoreErrors) //if errors are ignored, then warnings should also return; @@ -213,19 +212,19 @@ void CompareStatusHandler::reportWarning(const std::wstring& warningMessage, boo } -void CompareStatusHandler::forceUiRefresh() +void StatusHandlerTemporaryPanel::forceUiRefresh() { mainDlg.compareStatus->updateStatusPanelNow(); } -void CompareStatusHandler::OnAbortCompare(wxCommandEvent& event) +void StatusHandlerTemporaryPanel::OnAbortCompare(wxCommandEvent& event) { requestAbortion(); } -void CompareStatusHandler::abortProcessNow() +void StatusHandlerTemporaryPanel::abortProcessNow() { requestAbortion(); //just make sure... throw GuiAbortProcess(); @@ -233,14 +232,14 @@ void CompareStatusHandler::abortProcessNow() //######################################################################################################## -SyncStatusHandler::SyncStatusHandler(wxFrame* parentDlg, - size_t lastSyncsLogFileSizeMax, - OnGuiError handleError, - size_t automaticRetryCount, - size_t automaticRetryDelay, - const std::wstring& jobName, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory) : +StatusHandlerFloatingDialog::StatusHandlerFloatingDialog(wxFrame* parentDlg, + size_t lastSyncsLogFileSizeMax, + OnGuiError handleError, + size_t automaticRetryCount, + size_t automaticRetryDelay, + const std::wstring& jobName, + const Zstring& onCompletion, + std::vector<Zstring>& onCompletionHistory) : progressDlg(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, parentDlg, true, jobName, onCompletion, onCompletionHistory)), lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), handleError_(handleError), @@ -250,7 +249,7 @@ SyncStatusHandler::SyncStatusHandler(wxFrame* parentDlg, startTime_(wxGetUTCTimeMillis().GetValue()) {} -SyncStatusHandler::~SyncStatusHandler() +StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() { //------------ "on completion" command conceptually is part of the sync, not cleanup -------------------------------------- @@ -331,7 +330,7 @@ SyncStatusHandler::~SyncStatusHandler() if (showFinalResults) { if (abortIsRequested()) - progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events + progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events else if (totalErrors > 0) progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog); else if (totalWarnings > 0) @@ -348,13 +347,13 @@ SyncStatusHandler::~SyncStatusHandler() while (progressDlg) { wxTheApp->Yield(); //*first* refresh GUI (removing flicker) before sleeping! - boost::this_thread::sleep_for(boost::chrono::milliseconds(UI_UPDATE_INTERVAL)); //throw boost::thread_interrupted -> not expected => main thread! + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); } } } -void SyncStatusHandler::initNewPhase(int objectsTotal, std::int64_t dataTotal, Phase phaseID) +void StatusHandlerFloatingDialog::initNewPhase(int objectsTotal, std::int64_t dataTotal, Phase phaseID) { assert(phaseID == PHASE_SYNCHRONIZING); StatusHandler::initNewPhase(objectsTotal, dataTotal, phaseID); @@ -365,7 +364,7 @@ void SyncStatusHandler::initNewPhase(int objectsTotal, std::int64_t dataTotal, P } -void SyncStatusHandler::updateProcessedData(int objectsDelta, std::int64_t dataDelta) +void StatusHandlerFloatingDialog::updateProcessedData(int objectsDelta, std::int64_t dataDelta) { StatusHandler::updateProcessedData(objectsDelta, dataDelta); if (progressDlg) @@ -374,14 +373,14 @@ void SyncStatusHandler::updateProcessedData(int objectsDelta, std::int64_t dataD } -void SyncStatusHandler::reportInfo(const std::wstring& text) +void StatusHandlerFloatingDialog::reportInfo(const std::wstring& text) { StatusHandler::reportInfo(text); errorLog.logMsg(text, TYPE_INFO); } -ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& errorMessage, size_t retryNumber) +ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::wstring& errorMessage, size_t retryNumber) { //auto-retry if (retryNumber < automaticRetryCount_) @@ -394,7 +393,7 @@ ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& err { reportStatus(_("Error") + L": " + _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", (1000 * automaticRetryDelay_ - i * UI_UPDATE_INTERVAL + 999) / 1000)); //integer round up - boost::this_thread::sleep_for(boost::chrono::milliseconds(UI_UPDATE_INTERVAL)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); } return ProcessCallback::RETRY; } @@ -443,7 +442,7 @@ ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& err } -void SyncStatusHandler::reportFatalError(const std::wstring& errorMessage) +void StatusHandlerFloatingDialog::reportFatalError(const std::wstring& errorMessage) { errorLog.logMsg(errorMessage, TYPE_FATAL_ERROR); @@ -479,7 +478,7 @@ void SyncStatusHandler::reportFatalError(const std::wstring& errorMessage) } -void SyncStatusHandler::reportWarning(const std::wstring& warningMessage, bool& warningActive) +void StatusHandlerFloatingDialog::reportWarning(const std::wstring& warningMessage, bool& warningActive) { errorLog.logMsg(warningMessage, TYPE_WARNING); @@ -516,21 +515,21 @@ void SyncStatusHandler::reportWarning(const std::wstring& warningMessage, bool& } -void SyncStatusHandler::forceUiRefresh() +void StatusHandlerFloatingDialog::forceUiRefresh() { if (progressDlg) progressDlg->updateGui(); } -void SyncStatusHandler::abortProcessNow() +void StatusHandlerFloatingDialog::abortProcessNow() { requestAbortion(); //just make sure... - throw GuiAbortProcess(); //abort can be triggered by progressDlg + throw GuiAbortProcess(); //abort can be triggered by progressDlg } -void SyncStatusHandler::onProgressDialogTerminate() +void StatusHandlerFloatingDialog::onProgressDialogTerminate() { //it's responsibility of "progressDlg" to call requestAbortion() when closing dialog progressDlg = nullptr; diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index 803c8fdb..35789972 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -18,14 +18,14 @@ //Exception class used to abort the "compare" and "sync" process class GuiAbortProcess {}; -//classes handling sync and compare error as well as status information +//classes handling sync and compare errors as well as status feedback -//CompareStatusHandler(CompareProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! -class CompareStatusHandler : private wxEvtHandler, public zen::StatusHandler //throw GuiAbortProcess +//StatusHandlerTemporaryPanel(CompareProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! +class StatusHandlerTemporaryPanel : private wxEvtHandler, public zen::StatusHandler //throw GuiAbortProcess { public: - CompareStatusHandler(MainDialog& dlg); - ~CompareStatusHandler(); + StatusHandlerTemporaryPanel(MainDialog& dlg); + ~StatusHandlerTemporaryPanel(); void initNewPhase(int objectsTotal, std::int64_t dataTotal, Phase phaseID) override; void forceUiRefresh() override; @@ -45,19 +45,19 @@ private: }; -//SyncStatusHandler(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! -class SyncStatusHandler : public zen::StatusHandler +//StatusHandlerFloatingDialog(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! +class StatusHandlerFloatingDialog : public zen::StatusHandler { public: - SyncStatusHandler(wxFrame* parentDlg, - size_t lastSyncsLogFileSizeMax, - xmlAccess::OnGuiError handleError, - size_t automaticRetryCount, - size_t automaticRetryDelay, - const std::wstring& jobName, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory); - ~SyncStatusHandler(); + StatusHandlerFloatingDialog(wxFrame* parentDlg, + size_t lastSyncsLogFileSizeMax, + xmlAccess::OnGuiError handleError, + size_t automaticRetryCount, + size_t automaticRetryDelay, + const std::wstring& jobName, + const Zstring& onCompletion, + std::vector<Zstring>& onCompletionHistory); + ~StatusHandlerFloatingDialog(); void initNewPhase (int objectsTotal, std::int64_t dataTotal, Phase phaseID) override; void updateProcessedData(int objectsDelta, std::int64_t dataDelta ) override; diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 2a08c3f8..4850031c 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -173,7 +173,7 @@ private: wxWindow* getParentWindow() override { return &mainDlg; } std::unique_ptr<FilterConfig>& getFilterCfgOnClipboardRef() override { return mainDlg.filterCfgOnClipboard; } - void onAltCompCfgChange () override { mainDlg.applyCompareConfig(); } + void onAltCompCfgChange () override { mainDlg.applyCompareConfig(false /*setDefaultViewType*/); } void onAltSyncCfgChange () override { mainDlg.applySyncConfig(); } void onLocalFilterCfgChange() override { mainDlg.applyFilterConfig(); } //re-apply filter @@ -189,14 +189,17 @@ public: FolderPairPanel(wxWindow* parent, MainDialog& mainDialog) : FolderPairPanelGenerated(parent), FolderPairCallback<FolderPairPanelGenerated>(static_cast<FolderPairPanelGenerated&>(*this), mainDialog), //pass FolderPairPanelGenerated part... - dirpathLeft (mainDialog, *m_panelLeft, *m_buttonSelectDirLeft, *m_bpButtonSelectSftpLeft, *m_directoryLeft), - dirpathRight(mainDialog, *m_panelRight, *m_buttonSelectDirRight, *m_bpButtonSelectSftpRight, *m_directoryRight) + folderSelectorLeft (mainDialog, *m_panelLeft, *m_buttonSelectDirLeft, *m_bpButtonSelectSftpLeft, *m_directoryLeft), + folderSelectorRight(mainDialog, *m_panelRight, *m_buttonSelectDirRight, *m_bpButtonSelectSftpRight, *m_directoryRight) { - dirpathLeft .Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); - dirpathRight.Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); + folderSelectorLeft .setSiblingSelector(&folderSelectorRight); + folderSelectorRight.setSiblingSelector(&folderSelectorLeft); - dirpathLeft .Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); - dirpathRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + folderSelectorLeft .Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); + folderSelectorRight.Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); + + folderSelectorLeft .Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + folderSelectorRight.Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); m_bpButtonFolderPairOptions->SetBitmapLabel(getResourceImage(L"button_arrow_down")); } @@ -204,19 +207,16 @@ public: void setValues(const FolderPairEnh& fp) { setConfig(fp.altCmpConfig, fp.altSyncConfig, fp.localFilter); - dirpathLeft .setPath(fp.dirpathPhraseLeft); - dirpathRight.setPath(fp.dirpathPhraseRight); + folderSelectorLeft .setPath(fp.folderPathPhraseLeft_); + folderSelectorRight.setPath(fp.folderPathPhraseRight_); } - FolderPairEnh getValues() const { return FolderPairEnh(getLeftDir(), getRightDir(), getAltCompConfig(), getAltSyncConfig(), getAltFilterConfig()); } - - Zstring getLeftDir () const { return dirpathLeft .getPath(); } - Zstring getRightDir() const { return dirpathRight.getPath(); } + FolderPairEnh getValues() const { return FolderPairEnh(folderSelectorLeft.getPath(), folderSelectorRight.getPath(), getAltCompConfig(), getAltSyncConfig(), getAltFilterConfig()); } private: //support for drag and drop - FolderSelectorImpl dirpathLeft; - FolderSelectorImpl dirpathRight; + FolderSelectorImpl folderSelectorLeft; + FolderSelectorImpl folderSelectorRight; }; @@ -227,26 +227,29 @@ public: FolderPairCallback<MainDialogGenerated>(mainDialog, mainDialog), //prepare drag & drop - dirpathLeft(mainDialog, - *mainDialog.m_panelTopLeft, - *mainDialog.m_buttonSelectDirLeft, - *mainDialog.m_bpButtonSelectSftpLeft, - *mainDialog.m_directoryLeft, - mainDialog.m_staticTextResolvedPathL, - &mainDialog.m_gridMainL->getMainWin()), - dirpathRight(mainDialog, - *mainDialog.m_panelTopRight, - *mainDialog.m_buttonSelectDirRight, - *mainDialog.m_bpButtonSelectSftpRight, - *mainDialog.m_directoryRight, - mainDialog.m_staticTextResolvedPathR, - &mainDialog.m_gridMainR->getMainWin()) - { - dirpathLeft .Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); - dirpathRight.Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); - - dirpathLeft .Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); - dirpathRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + folderSelectorLeft(mainDialog, + *mainDialog.m_panelTopLeft, + *mainDialog.m_buttonSelectDirLeft, + *mainDialog.m_bpButtonSelectSftpLeft, + *mainDialog.m_directoryLeft, + mainDialog.m_staticTextResolvedPathL, + &mainDialog.m_gridMainL->getMainWin()), + folderSelectorRight(mainDialog, + *mainDialog.m_panelTopRight, + *mainDialog.m_buttonSelectDirRight, + *mainDialog.m_bpButtonSelectSftpRight, + *mainDialog.m_directoryRight, + mainDialog.m_staticTextResolvedPathR, + &mainDialog.m_gridMainR->getMainWin()) + { + folderSelectorLeft .setSiblingSelector(&folderSelectorRight); + folderSelectorRight.setSiblingSelector(&folderSelectorLeft); + + folderSelectorLeft .Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); + folderSelectorRight.Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); + + folderSelectorLeft .Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + folderSelectorRight.Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); mainDialog.m_panelTopLeft ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); mainDialog.m_panelTopMiddle->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); @@ -256,19 +259,16 @@ public: void setValues(const FolderPairEnh& fp) { setConfig(fp.altCmpConfig, fp.altSyncConfig, fp.localFilter); - dirpathLeft .setPath(fp.dirpathPhraseLeft); - dirpathRight.setPath(fp.dirpathPhraseRight); + folderSelectorLeft .setPath(fp.folderPathPhraseLeft_); + folderSelectorRight.setPath(fp.folderPathPhraseRight_); } - FolderPairEnh getValues() const { return FolderPairEnh(getLeftDir(), getRightDir(), getAltCompConfig(), getAltSyncConfig(), getAltFilterConfig()); } - - Zstring getLeftDir () const { return dirpathLeft .getPath(); } - Zstring getRightDir() const { return dirpathRight.getPath(); } + FolderPairEnh getValues() const { return FolderPairEnh(folderSelectorLeft.getPath(), folderSelectorRight.getPath(), getAltCompConfig(), getAltSyncConfig(), getAltFilterConfig()); } private: //support for drag and drop - FolderSelectorImpl dirpathLeft; - FolderSelectorImpl dirpathRight; + FolderSelectorImpl folderSelectorLeft; + FolderSelectorImpl folderSelectorRight; }; @@ -328,7 +328,7 @@ void setMenuItemImage(wxMenuItem*& menuItem, const wxBitmap& bmp) //just greyscale instead of brightened like bitmap buttons; much better: newItem->SetDisabledBitmap(bmp.ConvertToDisabled()); #endif - bool isDestroyed = menu->Destroy(menuItem); //actual workaround + bool isDestroyed = menu->Destroy(menuItem); //actual workaround assert(isDestroyed); (void)isDestroyed; menuItem = menu->Insert(pos, newItem); //don't forget to update input item pointer! @@ -404,7 +404,7 @@ void MainDialog::create(const Zstring& globalConfigFile) firstMissingDir.addJob([filepath] { return filepath.empty() /*ever empty??*/ || !fileExists(filepath) ? make_unique<FalseType>() : nullptr; }); //potentially slow network access: give all checks 500ms to finish - const bool allFilesExist = firstMissingDir.timedWait(boost::chrono::milliseconds(500)) && //false: time elapsed + const bool allFilesExist = firstMissingDir.timedWait(std::chrono::milliseconds(500)) && //false: time elapsed !firstMissingDir.get(); //no missing if (!allFilesExist) filepaths.clear(); //we do NOT want to show an error due to last config file missing on application start! @@ -524,11 +524,11 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, m_bpButtonAddPair ->SetBitmapLabel(getResourceImage(L"item_add")); m_bpButtonHideSearch ->SetBitmapLabel(getResourceImage(L"close_panel")); - warn_static("remove after test") -#ifdef ZEN_MAC - //follow OS conventions: - // m_menuItemOptions->SetItemLabel(_("&Preferences") + L"\tCtrl+,"); //"Ctrl" is automatically mapped to command button! - //this->SetMenuBar(m_menubar1); + //we have to use the OS X naming convention by default, because wxMac permanently populates the display menu when the wxMenuItem is created for the first time! + //=> other wx ports are not that badly programmed; therefore revert: + assert(m_menuItemOptions->GetItemLabel() == _("&Preferences") + L"\tCtrl+,"); //"Ctrl" is automatically mapped to command button! +#ifndef ZEN_MAC + m_menuItemOptions->SetItemLabel(_("&Options")); #endif //---------------- support for dockable gui style -------------------------------- @@ -805,8 +805,8 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, auto addDirCheck = [&](const FolderPairEnh& fp) { - std::unique_ptr<ABF> abfL = createAbstractBaseFolder(fp.dirpathPhraseLeft); - std::unique_ptr<ABF> abfR = createAbstractBaseFolder(fp.dirpathPhraseRight); + std::unique_ptr<ABF> abfL = createAbstractBaseFolder(fp.folderPathPhraseLeft_); + std::unique_ptr<ABF> abfR = createAbstractBaseFolder(fp.folderPathPhraseRight_); if (abfL->emptyBaseFolderPath() != abfR->emptyBaseFolderPath()) //only skip check if both sides are empty! havePartialPair = true; @@ -814,9 +814,9 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, haveFullPair = true; if (!abfL->emptyBaseFolderPath()) - asyncDirChecks.push_back(ABF::getAsyncCheckDirExists(abfL->getAbstractPath())); //noexcept + asyncDirChecks.push_back(ABF::getAsyncCheckFolderExists(abfL->getAbstractPath())); //noexcept if (!abfR->emptyBaseFolderPath()) - asyncDirChecks.push_back(ABF::getAsyncCheckDirExists(abfR->getAbstractPath())); //noexcept + asyncDirChecks.push_back(ABF::getAsyncCheckFolderExists(abfR->getAbstractPath())); //noexcept }; addDirCheck(currMainCfg.firstPair); @@ -839,7 +839,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, return make_unique<FalseType>(); }); - const bool startComparisonNow = !firstMissingDir.timedWait(boost::chrono::milliseconds(500)) || //= no result yet => start comparison anyway! + const bool startComparisonNow = !firstMissingDir.timedWait(std::chrono::milliseconds(500)) || //= no result yet => start comparison anyway! !firstMissingDir.get(); //= all directories exist if (startComparisonNow) @@ -1144,19 +1144,16 @@ void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& gridRe std::vector<FileSystemObject*> MainDialog::getGridSelection(bool fromLeft, bool fromRight) const { - std::set<size_t> selectedRows; - - auto addSelection = [&](const Grid& grid) - { - const std::vector<size_t>& sel = grid.getSelectedRows(); - selectedRows.insert(sel.begin(), sel.end()); - }; + std::vector<size_t> selectedRows; if (fromLeft) - addSelection(*m_gridMainL); + vector_append(selectedRows, m_gridMainL->getSelectedRows()); if (fromRight) - addSelection(*m_gridMainR); + vector_append(selectedRows, m_gridMainR->getSelectedRows()); + + removeDuplicates(selectedRows); + assert(std::is_sorted(selectedRows.begin(), selectedRows.end())); return gridDataView->getAllFileRef(selectedRows); } @@ -1195,174 +1192,96 @@ std::vector<FileSystemObject*> MainDialog::getTreeSelection() const } -//Exception class used to abort the "compare" and "sync" process -class AbortDeleteProcess {}; - -class ManualDeletionHandler : private wxEvtHandler, public DeleteFilesHandler //throw AbortDeleteProcess +void MainDialog::copyToAlternateFolder(const std::vector<zen::FileSystemObject*>& selectionLeft, + const std::vector<zen::FileSystemObject*>& selectionRight) { -public: - ManualDeletionHandler(MainDialog& main) : - mainDlg(main), - abortRequested(false), - ignoreErrors(false) - { - mainDlg.disableAllElements(true); //disable everything except abort button - - //register abort button - mainDlg.m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ManualDeletionHandler::OnAbortDeletion), nullptr, this ); - mainDlg.Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(ManualDeletionHandler::OnKeyPressed), nullptr, this); - } - - ~ManualDeletionHandler() - { - //de-register abort button - mainDlg.Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(ManualDeletionHandler::OnKeyPressed), nullptr, this); - mainDlg.m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ManualDeletionHandler::OnAbortDeletion ), nullptr, this ); + std::vector<FileSystemObject*> itemSelectionLeft = selectionLeft; + std::vector<FileSystemObject*> itemSelectionRight = selectionRight; + vector_remove_if(itemSelectionLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<LEFT_SIDE >(); }); + vector_remove_if(itemSelectionRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); }); + if (itemSelectionLeft.empty() && itemSelectionRight.empty()) + return; - mainDlg.enableAllElements(); - } + wxWindow* oldFocus = wxWindow::FindFocus(); + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus()); + + if (zen::showCopyToDialog(this, + itemSelectionLeft, itemSelectionRight, + globalCfg.gui.copyToCfg.lastUsedPath, + globalCfg.gui.copyToCfg.folderHistory, + globalCfg.gui.copyToCfg.historySizeMax, + globalCfg.gui.copyToCfg.keepRelPaths, + globalCfg.gui.copyToCfg.overwriteIfExists) != ReturnSmallDlg::BUTTON_OKAY) + return; - Response reportError(const std::wstring& msg) override + try { - if (ignoreErrors) - return DeleteFilesHandler::IGNORE_ERROR; + disableAllElements(true); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up + ZEN_ON_SCOPE_EXIT(app->Yield(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks - forceUiRefresh(); - bool ignoreNextErrors = false; - switch (showConfirmationDialog3(&mainDlg, DialogInfoType::ERROR2, PopupDialogCfg3(). - setDetailInstructions(msg). - setCheckBox(ignoreNextErrors, _("&Ignore subsequent errors"), ConfirmationButton3::DONT_DO_IT), - _("&Ignore"), _("&Retry"))) - { - case ConfirmationButton3::DO_IT: //ignore - ignoreErrors = ignoreNextErrors; - return DeleteFilesHandler::IGNORE_ERROR; + StatusHandlerTemporaryPanel statusHandler(*this); //handle status display and error messages - case ConfirmationButton3::DONT_DO_IT: //retry - return DeleteFilesHandler::RETRY; + zen::copyToAlternateFolder(itemSelectionLeft, itemSelectionRight, + globalCfg.gui.copyToCfg.lastUsedPath, + globalCfg.gui.copyToCfg.keepRelPaths, + globalCfg.gui.copyToCfg.overwriteIfExists, + statusHandler); - case ConfirmationButton3::CANCEL: - throw AbortDeleteProcess(); - } - - assert (false); - return DeleteFilesHandler::IGNORE_ERROR; //dummy return value + //"clearSelection" not needed/desired } + catch (GuiAbortProcess&) {} - void reportWarning(const std::wstring& msg, bool& warningActive) override - { - if (!warningActive || ignoreErrors) - return; - - forceUiRefresh(); - bool dontWarnAgain = false; - switch (showConfirmationDialog(&mainDlg, DialogInfoType::WARNING, PopupDialogCfg(). - setDetailInstructions(msg). - setCheckBox(dontWarnAgain, _("&Don't show this warning again")), _("&Ignore"))) - { - case ConfirmationButton::DO_IT: - warningActive = !dontWarnAgain; - break; - case ConfirmationButton::CANCEL: - throw AbortDeleteProcess(); - } - } + //updateGui(); -> not needed +} - void reportStatus (const std::wstring& msg) override - { - statusMsg = msg; - requestUiRefresh(); - } -private: - void requestUiRefresh() - { - if (updateUiIsAllowed()) //test if specific time span between ui updates is over - forceUiRefresh(); +void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selectionLeft, + const std::vector<FileSystemObject*>& selectionRight) +{ + std::vector<FileSystemObject*> itemSelectionLeft = selectionLeft; + std::vector<FileSystemObject*> itemSelectionRight = selectionRight; + vector_remove_if(itemSelectionLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<LEFT_SIDE >(); }); + vector_remove_if(itemSelectionRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); }); + if (itemSelectionLeft.empty() && itemSelectionRight.empty()) + return; - if (abortRequested) //test after (implicit) call to wxApp::Yield() - throw AbortDeleteProcess(); - } + wxWindow* oldFocus = wxWindow::FindFocus(); + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus()); - void forceUiRefresh() - { - //std::wstring msg = toGuiString(deletionCount) + - mainDlg.setStatusBarFullText(statusMsg); - wxTheApp->Yield(); - } + if (zen::showDeleteDialog(this, + itemSelectionLeft, itemSelectionRight, + globalCfg.gui.manualDeletionUseRecycler) != ReturnSmallDlg::BUTTON_OKAY) + return; - //context: C callstack message loop => throw()! - void OnAbortDeletion(wxCommandEvent& event) //handle abort button click - { - abortRequested = true; //don't throw exceptions across a C call stack! - } + disableAllElements(true); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up + ZEN_ON_SCOPE_EXIT(app->Yield(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks - void OnKeyPressed(wxKeyEvent& event) + //wxBusyCursor dummy; -> redundant: progress already shown in status bar! + try { - const int keyCode = event.GetKeyCode(); - if (keyCode == WXK_ESCAPE) - { - abortRequested = true; //don't throw exceptions across a C call stack! - return; - } - - event.Skip(); - } - - MainDialog& mainDlg; + StatusHandlerTemporaryPanel statusHandler(*this); //handle status display and error messages - bool abortRequested; - bool ignoreErrors; - //size_t deletionCount; // - std::wstring statusMsg; //status reporting -}; - - -void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selectionLeft, - const std::vector<FileSystemObject*>& selectionRight) -{ - //=> cleanup empty selection on either side: - std::vector<FileSystemObject*> selectionLeftTmp; - std::vector<FileSystemObject*> selectionRightTmp; - std::copy_if(selectionLeft .begin(), selectionLeft .end(), std::back_inserter(selectionLeftTmp ), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<LEFT_SIDE >(); }); - std::copy_if(selectionRight.begin(), selectionRight.end(), std::back_inserter(selectionRightTmp), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<RIGHT_SIDE>(); }); + zen::deleteFromGridAndHD(itemSelectionLeft, itemSelectionRight, + folderCmp, + extractDirectionCfg(getConfig().mainCfg), + globalCfg.gui.manualDeletionUseRecycler, + globalCfg.optDialogs.warningRecyclerMissing, + statusHandler); - if (!selectionLeftTmp.empty() || !selectionRightTmp.empty()) - { - wxWindow* oldFocus = wxWindow::FindFocus(); - ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();) + m_gridMainL->clearSelection(ALLOW_GRID_EVENT); + m_gridMainC->clearSelection(ALLOW_GRID_EVENT); + m_gridMainR->clearSelection(ALLOW_GRID_EVENT); - if (zen::showDeleteDialog(this, - selectionLeftTmp, - selectionRightTmp, - globalCfg.gui.useRecyclerForManualDeletion) == ReturnSmallDlg::BUTTON_OKAY) - { - //wxBusyCursor dummy; -> redundant: progress already shown in status bar! - try - { - //handle errors when deleting files/folders - ManualDeletionHandler statusHandler(*this); - - zen::deleteFromGridAndHD(selectionLeftTmp, - selectionRightTmp, - folderCmp, - extractDirectionCfg(getConfig().mainCfg), - globalCfg.gui.useRecyclerForManualDeletion, - statusHandler, - globalCfg.optDialogs.warningRecyclerMissing); - - m_gridMainL->clearSelection(ALLOW_GRID_EVENT); - m_gridMainC->clearSelection(ALLOW_GRID_EVENT); - m_gridMainR->clearSelection(ALLOW_GRID_EVENT); - } - catch (AbortDeleteProcess&) {} //do not clear grids, if aborted! + m_gridNavi->clearSelection(ALLOW_GRID_EVENT); + } + catch (GuiAbortProcess&) {} //do not clear grids, if aborted! - //remove rows that are empty: just a beautification, invalid rows shouldn't cause issues - gridDataView->removeInvalidRows(); + //remove rows that are empty: just a beautification, invalid rows shouldn't cause issues + gridDataView->removeInvalidRows(); - updateGui(); - } - } + updateGui(); } @@ -1416,8 +1335,8 @@ void MainDialog::openExternalApplication(const wxString& commandline, const std: (leftSide && selectionTmp[0]->isEmpty<LEFT_SIDE >()) || (!leftSide && selectionTmp[0]->isEmpty<RIGHT_SIDE>())) { - auto abfL = createAbstractBaseFolder(firstFolderPair->getLeftDir ()); //keep AbstractPathRef valid! - auto abfR = createAbstractBaseFolder(firstFolderPair->getRightDir()); // + auto abfL = createAbstractBaseFolder(firstFolderPair->getValues().folderPathPhraseLeft_); //keep AbstractPathRef valid! + auto abfR = createAbstractBaseFolder(firstFolderPair->getValues().folderPathPhraseRight_); // AbstractPathRef fallbackFolderPath = [&] { @@ -1567,20 +1486,20 @@ void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView, } -void MainDialog::setStatusBarFullText(const wxString& msg) -{ - const bool needLayoutUpdate = !m_staticTextFullStatus->IsShown(); - //select state - bSizerFileStatus->Show(false); - m_staticTextFullStatus->Show(); - - //update status information - setText(*m_staticTextFullStatus, msg); - m_panelStatusBar->Layout(); - - if (needLayoutUpdate) - auiMgr.Update(); //fix status bar height (needed on OS X) -} +//void MainDialog::setStatusBarFullText(const wxString& msg) +//{ +// const bool needLayoutUpdate = !m_staticTextFullStatus->IsShown(); +// //select state +// bSizerFileStatus->Show(false); +// m_staticTextFullStatus->Show(); +// +// //update status information +// setText(*m_staticTextFullStatus, msg); +// m_panelStatusBar->Layout(); +// +// if (needLayoutUpdate) +// auiMgr.Update(); //fix status bar height (needed on OS X) +//} void MainDialog::flashStatusInformation(const wxString& text) @@ -1594,7 +1513,7 @@ void MainDialog::flashStatusInformation(const wxString& text) m_panelStatusBar->Layout(); //if (needLayoutUpdate) auiMgr.Update(); -> not needed here, this is called anyway in updateGui() - processAsync2([] { boost::this_thread::sleep_for(boost::chrono::milliseconds(2500)); }, //throw boost::thread_interrupted + processAsync2([] { std::this_thread::sleep_for(std::chrono::milliseconds(2500)); }, [this] { this->restoreStatusInformation(); }); } @@ -1649,7 +1568,10 @@ void MainDialog::disableAllElements(bool enableAbort) m_bpButtonSyncConfig ->Disable(); m_buttonSync ->Disable(); m_panelDirectoryPairs->Disable(); - m_splitterMain ->Disable(); //includes m_panelCenter, but not m_panelStatusBar! + m_splitterMain ->Disable(); + m_gridMainL ->Disable(); //disabled state already covered by m_splitterMain, + m_gridMainC ->Disable(); //however grid.cpp used IsThisEnabled() for rendering! + m_gridMainR ->Disable(); // m_panelViewFilter ->Disable(); m_panelConfig ->Disable(); m_gridNavi ->Disable(); @@ -1688,6 +1610,9 @@ void MainDialog::enableAllElements() m_buttonSync ->Enable(); m_panelDirectoryPairs->Enable(); m_splitterMain ->Enable(); + m_gridMainL ->Enable(); + m_gridMainC ->Enable(); + m_gridMainR ->Enable(); m_panelViewFilter ->Enable(); m_panelConfig ->Enable(); m_gridNavi ->Enable(); @@ -1876,6 +1801,11 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) case WXK_INSERT: //CTRL + C || CTRL + INS copySelectionToClipboard({ m_gridMainL, m_gridMainR} ); return; // -> swallow event! don't allow default grid commands! + + case 'T': //CTRL + T + copyToAlternateFolder(getGridSelection(true, false), + getGridSelection(false, true)); + return; } else if (event.AltDown()) @@ -2125,7 +2055,7 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) } //---------------------------------------------------------------------------------------------------- - //FILE FILTER + auto addFilterMenu = [&](const std::wstring& label, const wxString& iconName, bool include) { if (selection.size() == 1) @@ -2170,10 +2100,17 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); //---------------------------------------------------------------------------------------------------- - //CONTEXT_DELETE_FILES + const bool haveNonEmptyItems = std::any_of(selection.begin(), selection.end(), [&](const FileSystemObject* fsObj) { return !fsObj->isEmpty<LEFT_SIDE>() || !fsObj->isEmpty<RIGHT_SIDE>(); }); + + //menu.addSeparator(); + + //menu.addItem(_("Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selection, selection); }, nullptr, haveNonEmptyItems); + + //---------------------------------------------------------------------------------------------------- + menu.addSeparator(); - menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, !selection.empty()); + menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, haveNonEmptyItems); menu.popup(*this); } @@ -2241,7 +2178,7 @@ void MainDialog::onMainGridContextRim(bool leftSide) } //---------------------------------------------------------------------------------------------------- - //FILE FILTER + auto addFilterMenu = [&](const wxString& label, const wxString& iconName, bool include) { if (selection.size() == 1) @@ -2284,6 +2221,7 @@ void MainDialog::onMainGridContextRim(bool leftSide) addFilterMenu(_("Exclude via filter:"), L"filter_exclude_small", false); //---------------------------------------------------------------------------------------------------- + if (!selection.empty()) { if (m_bpButtonShowExcluded->isActive() && !selection[0]->isActive()) @@ -2295,7 +2233,7 @@ void MainDialog::onMainGridContextRim(bool leftSide) menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); //---------------------------------------------------------------------------------------------------- - //CONTEXT_EXTERNAL_APP + if (!globalCfg.gui.externelApplications.empty()) { menu.addSeparator(); @@ -2319,16 +2257,25 @@ void MainDialog::onMainGridContextRim(bool leftSide) menu.addItem(description, openApp, nullptr, !selection.empty()); } } + //---------------------------------------------------------------------------------------------------- - //CONTEXT_DELETE_FILES + + std::vector<FileSystemObject*> itemSelectionLeft = getGridSelection(true, false); + std::vector<FileSystemObject*> itemSelectionRight = getGridSelection(false, true); + vector_remove_if(itemSelectionLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<LEFT_SIDE >(); }); + vector_remove_if(itemSelectionRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); }); + menu.addSeparator(); - menu.addItem(_("Delete") + L"\tDel", [this] - { - deleteSelectedFiles( - getGridSelection(true, false), - getGridSelection(false, true)); - }, nullptr, !selection.empty()); + menu.addItem(_("Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(itemSelectionLeft, itemSelectionRight); }, nullptr, + !itemSelectionLeft.empty() || !itemSelectionRight.empty()); + + //---------------------------------------------------------------------------------------------------- + + menu.addSeparator(); + + menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(itemSelectionLeft, itemSelectionRight); }, nullptr, + !itemSelectionLeft.empty() || !itemSelectionRight.empty()); menu.popup(*this); } @@ -2561,7 +2508,7 @@ void MainDialog::OnCompSettingsContext(wxMouseEvent& event) auto setVariant = [&](CompareVariant var) { currentCfg.mainCfg.cmpConfig.compareVar = var; - applyCompareConfig(true); //true: setDefaultViewType + applyCompareConfig(true /*setDefaultViewType*/); }; auto currentVar = getConfig().mainCfg.cmpConfig.compareVar; @@ -2596,7 +2543,7 @@ void MainDialog::OnSyncSettingsContext(wxMouseEvent& event) void MainDialog::onNaviPanelFilesDropped(FileDropEvent& event) { - loadConfiguration(event.getFiles()); + loadConfiguration(event.getPaths()); event.Skip(); } @@ -2706,22 +2653,22 @@ void MainDialog::removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filep auto getMissingFilesAsync = [filepaths]() -> std::vector<Zstring> { - //boost::this_thread::sleep_for(boost::chrono::seconds(5)); + //std::this_thread::sleep_for(std::chrono::seconds(5)); //check existence of all config files in parallel! - std::list<boost::unique_future<bool>> fileEx; + std::list<std::future<bool>> fileEx; for (const Zstring& filepath : filepaths) fileEx.push_back(zen::runAsync([=] { return fileExists(filepath); })); //potentially slow network access => limit maximum wait time! - wait_for_all_timed(fileEx.begin(), fileEx.end(), boost::chrono::milliseconds(1000)); + wait_for_all_timed(fileEx.begin(), fileEx.end(), std::chrono::milliseconds(1000)); std::vector<Zstring> missingFiles; auto itFut = fileEx.begin(); - for (auto it = filepaths.begin(); it != filepaths.end(); ++it, (void)++itFut) //void: prevent ADL from dragging in boost's ,-overload: "MSVC warning C4913: user defined binary operator ',' exists but no overload could convert all operands" - if (itFut->is_ready() && !itFut->get()) //remove only files that are confirmed to be non-existent + for (auto it = filepaths.begin(); it != filepaths.end(); ++it, ++itFut) + if (isReady(*itFut) && !itFut->get()) //remove only files that are confirmed to be non-existent missingFiles.push_back(*it); return missingFiles; @@ -3224,11 +3171,11 @@ void MainDialog::OnClose(wxCloseEvent& event) void MainDialog::onCheckRows(CheckRowsEvent& event) { - std::set<size_t> selectedRows; + std::vector<size_t> selectedRows; const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows for (size_t i = event.rowFirst_; i < rowLast; ++i) - selectedRows.insert(i); + selectedRows.push_back(i); if (!selectedRows.empty()) { @@ -3240,11 +3187,11 @@ void MainDialog::onCheckRows(CheckRowsEvent& event) void MainDialog::onSetSyncDirection(SyncDirectionEvent& event) { - std::set<size_t> selectedRows; + std::vector<size_t> selectedRows; const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows for (size_t i = event.rowFirst_; i < rowLast; ++i) - selectedRows.insert(i); + selectedRows.push_back(i); if (!selectedRows.empty()) { @@ -3350,19 +3297,19 @@ void MainDialog::updateGuiDelayedIf(bool condition) void MainDialog::showConfigDialog(SyncConfigPanel panelToShow) { - const CompConfig cmpCfgOld = currentCfg.mainCfg.cmpConfig; - const FilterConfig filterCfgOld = currentCfg.mainCfg.globalFilter; - const SyncConfig syncCfgOld = currentCfg.mainCfg.syncCfg; - - MiscGlobalCfg miscCfg = + MiscGlobalCfg miscCfgTmp = { currentCfg.handleError, - currentCfg.mainCfg.onCompletion, globalCfg.gui.onCompletionHistory, globalCfg.gui.onCompletionHistoryMax }; + const CompConfig cmpCfgOld = currentCfg.mainCfg.cmpConfig; + const FilterConfig filterCfgOld = currentCfg.mainCfg.globalFilter; + const SyncConfig syncCfgOld = currentCfg.mainCfg.syncCfg; + const MiscGlobalCfg miscCfgOld = miscCfgTmp; + if (showSyncConfigDlg(this, panelToShow, nullptr, @@ -3371,20 +3318,30 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow) nullptr, currentCfg.mainCfg.syncCfg, currentCfg.mainCfg.cmpConfig.compareVar, - &miscCfg, + &miscCfgTmp, _("Synchronization Settings")) == ReturnSyncConfig::BUTTON_OKAY) { if (currentCfg.mainCfg.cmpConfig != cmpCfgOld) - applyCompareConfig(true); //true: setDefaultViewType + applyCompareConfig(true /*setDefaultViewType*/); if (currentCfg.mainCfg.globalFilter != filterCfgOld) { updateGlobalFilterButton(); //refresh global filter icon - applyFilterConfig(); //re-apply filter + applyFilterConfig(); //re-apply filter } if (currentCfg.mainCfg.syncCfg != syncCfgOld) applySyncConfig(); + + if (miscCfgTmp != miscCfgOld) + { + currentCfg.handleError = miscCfgTmp.handleError; + currentCfg.mainCfg.onCompletion = miscCfgTmp.onCompletionCommand; + globalCfg.gui.onCompletionHistory = miscCfgTmp.onCompletionHistory; + //miscCfgTmp.onCompletionHistoryMax + + updateUnsavedCfgStatus(); //usually included by: updateGui(); + } } } @@ -3395,7 +3352,7 @@ void MainDialog::OnGlobalFilterContext(wxMouseEvent& event) { currentCfg.mainCfg.globalFilter = FilterConfig(); updateGlobalFilterButton(); //refresh global filter icon - applyFilterConfig(); //re-apply filter + applyFilterConfig(); //re-apply filter }; auto copyFilter = [&] { filterCfgOnClipboard = make_unique<FilterConfig>(currentCfg.mainCfg.globalFilter); }; auto pasteFilter = [&] @@ -3404,7 +3361,7 @@ void MainDialog::OnGlobalFilterContext(wxMouseEvent& event) { currentCfg.mainCfg.globalFilter = *filterCfgOnClipboard; updateGlobalFilterButton(); //refresh global filter icon - applyFilterConfig(); //re-apply filter + applyFilterConfig(); //re-apply filter } }; @@ -3569,7 +3526,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) //wxBusyCursor dummy; -> redundant: progress already shown in progress dialog! wxWindow* oldFocus = wxWindow::FindFocus(); - ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();); //e.g. keep focus on main grid after pressing F5 + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus()); //e.g. keep focus on main grid after pressing F5 int scrollPosX = 0; int scrollPosY = 0; @@ -3581,14 +3538,14 @@ void MainDialog::OnCompare(wxCommandEvent& event) clearGrid(); //avoid memory peak by clearing old data first - disableAllElements(true); //CompareStatusHandler will internally process Window messages, so avoid unexpected callbacks! + disableAllElements(true); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up ZEN_ON_SCOPE_EXIT(app->Yield(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks try { - //class handling status display and error messages - CompareStatusHandler statusHandler(*this); + //handle status display and error messages + StatusHandlerTemporaryPanel statusHandler(*this); const std::vector<zen::FolderPairCfg> cmpConfig = extractCompareCfg(getConfig().mainCfg, globalCfg.fileTimeTolerance); @@ -3768,18 +3725,18 @@ void MainDialog::OnStartSync(wxCommandEvent& event) const auto& guiCfg = getConfig(); - disableAllElements(false); //SyncStatusHandler will internally process Window messages, so avoid unexpected callbacks! + disableAllElements(false); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks! ZEN_ON_SCOPE_EXIT(enableAllElements()); //class handling status updates and error messages - SyncStatusHandler statusHandler(this, //throw GuiAbortProcess - globalCfg.lastSyncsLogFileSizeMax, - currentCfg.handleError, - globalCfg.automaticRetryCount, - globalCfg.automaticRetryDelay, - xmlAccess::extractJobName(activeCfgFilename), - guiCfg.mainCfg.onCompletion, - globalCfg.gui.onCompletionHistory); + StatusHandlerFloatingDialog statusHandler(this, //throw GuiAbortProcess + globalCfg.lastSyncsLogFileSizeMax, + currentCfg.handleError, + globalCfg.automaticRetryCount, + globalCfg.automaticRetryDelay, + xmlAccess::extractJobName(activeCfgFilename), + guiCfg.mainCfg.onCompletion, + globalCfg.gui.onCompletionHistory); //wxBusyCursor dummy; -> redundant: progress already shown in progress dialog! @@ -3895,13 +3852,13 @@ void MainDialog::OnSwapSides(wxCommandEvent& event) { //swap directory names: FolderPairEnh fp1st = firstFolderPair->getValues(); - std::swap(fp1st.dirpathPhraseLeft, fp1st.dirpathPhraseRight); + std::swap(fp1st.folderPathPhraseLeft_, fp1st.folderPathPhraseRight_); firstFolderPair->setValues(fp1st); for (FolderPairPanel* panel : additionalFolderPairs) { FolderPairEnh fp = panel->getValues(); - std::swap(fp.dirpathPhraseLeft, fp.dirpathPhraseRight); + std::swap(fp.folderPathPhraseLeft_, fp.folderPathPhraseRight_); panel->setValues(fp); } diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 7d62a61b..6be6bee6 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -54,8 +54,8 @@ private: bool startComparison); ~MainDialog(); - friend class CompareStatusHandler; - friend class SyncStatusHandler; + friend class StatusHandlerTemporaryPanel; + friend class StatusHandlerFloatingDialog; friend class ManualDeletionHandler; friend class FolderPairFirst; friend class FolderPairPanel; @@ -114,6 +114,10 @@ private: void setSyncDirManually(const std::vector<zen::FileSystemObject*>& selection, zen::SyncDirection direction); void setFilterManually(const std::vector<zen::FileSystemObject*>& selection, bool setIncluded); void copySelectionToClipboard(const std::vector<const zen::Grid*>& gridRefs); + + void copyToAlternateFolder(const std::vector<zen::FileSystemObject*>& selectionLeft, + const std::vector<zen::FileSystemObject*>& selectionRight); + void deleteSelectedFiles(const std::vector<zen::FileSystemObject*>& selectionLeft, const std::vector<zen::FileSystemObject*>& selectionRight); @@ -128,10 +132,10 @@ private: void processAsync2(Fun doAsync, Fun2 evalOnGui) { asyncTasks.add2(doAsync, evalOnGui); timerForAsyncTasks.Start(50); /*timer interval in [ms] */ } //status bar supports one of the following two states at a time: - void setStatusBarFullText(const wxString& msg); void setStatusBarFileStatistics(size_t filesOnLeftView, size_t foldersOnLeftView, size_t filesOnRightView, size_t foldersOnRightView, std::uint64_t filesizeLeftView, std::uint64_t filesizeRightView); + //void setStatusBarFullText(const wxString& msg); - void flashStatusInformation(const wxString& msg); //temporarily show different status (only valid for setStatusBarFullText) + void flashStatusInformation(const wxString& msg); //temporarily show different status (only valid for setStatusBarFileStatistics) void restoreStatusInformation(); //called automatically after a few seconds //events @@ -149,7 +153,7 @@ private: void OnGlobalFilterContext (wxMouseEvent& event) override; void OnViewButtonRightClick(wxMouseEvent& event) override; - void applyCompareConfig(bool setDefaultViewType = false); + void applyCompareConfig(bool setDefaultViewType); //context menu handler methods void onMainGridContextL(zen::GridClickEvent& event); diff --git a/FreeFileSync/Source/ui/on_completion_box.cpp b/FreeFileSync/Source/ui/on_completion_box.cpp index 27be3904..e2ba96ab 100644 --- a/FreeFileSync/Source/ui/on_completion_box.cpp +++ b/FreeFileSync/Source/ui/on_completion_box.cpp @@ -80,8 +80,6 @@ OnCompletionBox::OnCompletionBox(wxWindow* parent, const wxValidator& validator, const wxString& name) : wxComboBox(parent, id, value, pos, size, n, choices, style, validator, name), - history_(nullptr), - historyMax_(0), defaultCommands(getDefaultCommands()) { //##################################### diff --git a/FreeFileSync/Source/ui/on_completion_box.h b/FreeFileSync/Source/ui/on_completion_box.h index 5085735b..c787ffe2 100644 --- a/FreeFileSync/Source/ui/on_completion_box.h +++ b/FreeFileSync/Source/ui/on_completion_box.h @@ -51,8 +51,8 @@ private: void setValueAndUpdateList(const std::wstring& value); - std::vector<Zstring>* history_; - size_t historyMax_; + std::vector<Zstring>* history_ = nullptr; + size_t historyMax_ = 0; const std::vector<std::pair<std::wstring, Zstring>> defaultCommands; }; diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 86abc40e..7e9ce1ed 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -253,15 +253,16 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow() } break; + case ProcessCallback::PHASE_SYNCHRONIZING: case ProcessCallback::PHASE_COMPARING_CONTENT: { - auto objectsCurrent = syncStat_->getObjectsCurrent(ProcessCallback::PHASE_COMPARING_CONTENT); - auto objectsTotal = syncStat_->getObjectsTotal (ProcessCallback::PHASE_COMPARING_CONTENT); - auto dataCurrent = syncStat_->getDataCurrent (ProcessCallback::PHASE_COMPARING_CONTENT); - auto dataTotal = syncStat_->getDataTotal (ProcessCallback::PHASE_COMPARING_CONTENT); + 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()); //add both data + obj-count, to handle "deletion-only" cases - const double fraction = dataTotal + objectsTotal == 0 ? 0 : std::max(0.0, 1.0 * (dataCurrent + objectsCurrent) / (dataTotal + objectsTotal)); + const double fraction = dataTotal + itemsTotal == 0 ? 0 : std::max(0.0, 1.0 * (dataCurrent + itemsCurrent) / (dataTotal + itemsTotal)); //dialog caption, taskbar setTitle(fractionToString(fraction) + wxT(" - ") + _("Comparing content...")); @@ -275,7 +276,7 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow() m_gauge2->SetValue(numeric::round(fraction * GAUGE_FULL_RANGE)); //remaining objects and bytes for file comparison - setText(*m_staticTextItemsRemaining, toGuiString(objectsTotal - objectsCurrent), &layoutChanged); + setText(*m_staticTextItemsRemaining, toGuiString(itemsTotal - itemsCurrent), &layoutChanged); setText(*m_staticTextDataRemaining, L"(" + filesizeToShortString(dataTotal - dataCurrent) + L")", &layoutChanged); //remaining time and speed: only visible during binary comparison @@ -286,7 +287,7 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow() timeLastSpeedEstimateMs = timeNowMs; if (numeric::dist(binCompStartMs, timeNowMs) >= 1000) //discard stats for first second: probably messy - perf->addSample(objectsCurrent, dataCurrent, timeNowMs); + perf->addSample(itemsCurrent, dataCurrent, timeNowMs); //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only //-> call more often than once per second to correctly show last few seconds countdown, but don't call too often to avoid occasional jitter @@ -299,10 +300,6 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow() } } break; - - case ProcessCallback::PHASE_SYNCHRONIZING: - assert(false); - break; } //time elapsed @@ -728,7 +725,7 @@ private: includedTypes |= TYPE_INFO; msgView->updateView(includedTypes); //update MVC "model" - m_gridMessages->Refresh(); //update MVC "view" + m_gridMessages->Refresh(); //update MVC "view" } void onGridButtonEvent(wxKeyEvent& event) @@ -1209,7 +1206,9 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF { static_assert(IsSameType<TopLevelDialog, wxFrame >::value || IsSameType<TopLevelDialog, wxDialog>::value, ""); +#ifndef ZEN_MAC assert((IsSameType<TopLevelDialog, wxFrame>::value == !parentFrame)); +#endif //finish construction of this dialog: this->SetMinSize(wxSize(470, 280)); //== minimum size! no idea why SetMinSize() is not used... @@ -1268,7 +1267,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF curveDataBytes = std::make_shared<CurveDataStatistics>(); curveDataItems = std::make_shared<CurveDataStatistics>(); - const int xLabelHeight = this->GetCharHeight() + 2 * 1/*border*/; //use same height for both graphs to make sure they stretch evenly + const int xLabelHeight = this->GetCharHeight() + 2 * 1 /*border*/; //use same height for both graphs to make sure they stretch evenly const int yLabelWidth = 70; pnl.m_panelGraphBytes->setAttributes(Graph2D::MainAttributes(). setLabelX(Graph2D::X_LABEL_BOTTOM, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)). @@ -1694,7 +1693,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) { wxTheApp->Yield(); //receive UI message that end pause OR forceful termination! //*first* refresh GUI (removing flicker) before sleeping! - boost::this_thread::sleep_for(boost::chrono::milliseconds(UI_UPDATE_INTERVAL)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); } //after SyncProgressDialogImpl::OnClose() called wxWindow::Destroy() on OS X this instance is instantly toast! if (wereDead) @@ -1868,10 +1867,10 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul 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()); + const std::int64_t dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); + const std::int64_t dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); assert(dataCurrent <= dataTotal); //set overall speed (instead of current speed) @@ -2140,9 +2139,19 @@ SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb, std::vector<Zstring>& onCompletionHistory) { if (parentWindow) //sync from GUI + { + //due to usual "wxBugs", wxDialog on OS X does not float on its parent; wxFrame OTOH does => hack! + //https://groups.google.com/forum/#!topic/wx-users/J5SjjLaBOQE +#ifdef ZEN_MAC + return new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT, + [&](wxFrame& progDlg) { return parentWindow; }, + abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, onCompletion, onCompletionHistory); +#else return new SyncProgressDialogImpl<wxDialog>(wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxRESIZE_BORDER, [&](wxDialog& progDlg) { return parentWindow; }, - abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, onCompletion, onCompletionHistory); + abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, onCompletion, onCompletionHistory); +#endif + } else //FFS batch job { auto dlg = new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE, diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h index d86ef14d..92741ccd 100644 --- a/FreeFileSync/Source/ui/progress_indicator.h +++ b/FreeFileSync/Source/ui/progress_indicator.h @@ -33,7 +33,7 @@ private: }; -//SyncStatusHandler will internally process Window messages => disable GUI controls to avoid unexpected callbacks! +//StatusHandlerFloatingDialog will internally process Window messages => disable GUI controls to avoid unexpected callbacks! struct SyncProgressDialog { diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 5319454c..4642f3ad 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -21,6 +21,7 @@ #include <wx+/image_resources.h> #include "gui_generated.h" #include "custom_grid.h" +#include "folder_selector.h" #include "../algorithm.h" #include "../synchronization.h" #include "../lib/help_provider.h" @@ -179,6 +180,8 @@ SftpSetupDlg::SftpSetupDlg(wxWindow* parent, Zstring& folderPathPhrase) : SftpSe const Zstring serverRelPath = res.second; m_textCtrlServer ->ChangeValue(utfCvrtTo<wxString>(login.server)); + if (login.port > 0) + m_textCtrlPort->ChangeValue(numberTo<wxString>(login.port)); m_textCtrlUserName ->ChangeValue(utfCvrtTo<wxString>(login.username)); m_textCtrlPasswordHidden->ChangeValue(utfCvrtTo<wxString>(login.password)); m_textCtrlServerPath ->ChangeValue(utfCvrtTo<wxString>(serverRelPath)); @@ -213,12 +216,14 @@ void SftpSetupDlg::OnOkay(wxCommandEvent& event) { SftpLoginInfo login = {}; login.server = utfCvrtTo<Zstring>(m_textCtrlServer ->GetValue()); + login.port = stringTo<int> (m_textCtrlPort ->GetValue()); //0 if empty login.username = utfCvrtTo<Zstring>(m_textCtrlUserName->GetValue()); login.password = utfCvrtTo<Zstring>((m_checkBoxShowPassword->GetValue() ? m_textCtrlPasswordVisible : m_textCtrlPasswordHidden)->GetValue()); Zstring serverRelPath = utfCvrtTo<Zstring>(m_textCtrlServerPath->GetValue()); trim(login.server); + trim(login.username); trim(serverRelPath); folderPathPhraseOut = assembleSftpFolderPathPhrase(login, serverRelPath); //noexcept @@ -237,6 +242,135 @@ ReturnSmallDlg::ButtonPressed zen::showSftpSetupDialog(wxWindow* parent, Zstring //######################################################################################## +class CopyToDialog : public CopyToDlgGenerated +{ +public: + CopyToDialog(wxWindow* parent, + const std::vector<zen::FileSystemObject*>& rowsOnLeft, + const std::vector<zen::FileSystemObject*>& rowsOnRight, + Zstring& lastUsedPath, + const std::shared_ptr<FolderHistory>& folderHistory, + bool& keepRelPaths, + bool& overwriteIfExists); + +private: + void OnOK (wxCommandEvent& event) override; + void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + + Zstring& outLastUsedPath; + bool& outKeepRelPaths; + bool& outOverwriteIfExists; + + std::unique_ptr<FolderSelector> targetFolder; //always bound + std::shared_ptr<FolderHistory> folderHistory_; +}; + + +CopyToDialog::CopyToDialog(wxWindow* parent, + const std::vector<FileSystemObject*>& rowsOnLeft, + const std::vector<FileSystemObject*>& rowsOnRight, + Zstring& lastUsedPath, + const std::shared_ptr<FolderHistory>& folderHistory, + bool& keepRelPaths, + bool& overwriteIfExists) : + CopyToDlgGenerated(parent), + outLastUsedPath(lastUsedPath), + outKeepRelPaths(keepRelPaths), + outOverwriteIfExists(overwriteIfExists), + folderHistory_(folderHistory) +{ +#ifdef ZEN_WIN + new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" + wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! +#endif + + setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOK).setCancel(m_buttonCancel)); + + setMainInstructionFont(*m_staticTextHeader); + + m_bitmapCopyTo->SetBitmap(getResourceImage(L"copy_to")); + + targetFolder = make_unique<FolderSelector>(*this, *m_buttonSelectFolder, *m_bpButtonSelectSftp, *m_directoryTarget, nullptr /*staticText*/, nullptr /*wxWindow*/); + + m_directoryTarget->init(folderHistory_); + +#ifndef __WXGTK__ //wxWidgets holds portability promise by supporting multi-line controls...not + m_textCtrlFileList->SetMaxLength(0); //allow large entries! +#endif + /* + There is a nasty bug on wxGTK under Ubuntu: If a multi-line wxTextCtrl contains so many lines that scrollbars are shown, + it re-enables all windows that are supposed to be disabled during the current modal loop! + This only affects Ubuntu/wxGTK! No such issue on Debian/wxGTK or Suse/wxGTK + => another Unity problem like the following? + http://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" + */ + + const std::pair<std::wstring, int> selectionInfo = zen::getSelectedItemsAsString(rowsOnLeft, rowsOnRight); + + const wxString header = _P("Copy the following item to another folder?", + "Copy the following %x items to another folder?", selectionInfo.second); + m_staticTextHeader->SetLabel(header); + m_staticTextHeader->Wrap(460); //needs to be reapplied after SetLabel() + + m_textCtrlFileList->ChangeValue(selectionInfo.first); + + //----------------- set config --------------------------------- + targetFolder ->setPath(lastUsedPath); + m_checkBoxKeepRelPath ->SetValue(keepRelPaths); + m_checkBoxOverwriteIfExists->SetValue(overwriteIfExists); + //----------------- /set config -------------------------------- + + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() + //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! + + m_buttonOK->SetFocus(); +} + + +void CopyToDialog::OnOK(wxCommandEvent& event) +{ + //------- parameter validation (BEFORE writing output!) ------- + if (trimCpy(targetFolder->getPath()).empty()) + { + showNotificationDialog(this, DialogInfoType::INFO, PopupDialogCfg().setMainInstructions(_("Please enter a target folder."))); + //don't show error icon to follow "Windows' encouraging tone" + m_directoryTarget->SetFocus(); + return; + } + //------------------------------------------------------------- + + outLastUsedPath = targetFolder->getPath(); + outKeepRelPaths = m_checkBoxKeepRelPath->GetValue(); + outOverwriteIfExists = m_checkBoxOverwriteIfExists->GetValue(); + + folderHistory_->addItem(outLastUsedPath); + + EndModal(ReturnSmallDlg::BUTTON_OKAY); +} + + +ReturnSmallDlg::ButtonPressed zen::showCopyToDialog(wxWindow* parent, + const std::vector<FileSystemObject*>& rowsOnLeft, + const std::vector<FileSystemObject*>& rowsOnRight, + Zstring& lastUsedPath, + std::vector<Zstring>& folderPathHistory, + size_t historySizeMax, + bool& keepRelPaths, + bool& overwriteIfExists) +{ + + auto folderHistory = std::make_shared<FolderHistory>(folderPathHistory, historySizeMax); + + CopyToDialog dlg(parent, rowsOnLeft, rowsOnRight, lastUsedPath, folderHistory, keepRelPaths, overwriteIfExists); + const auto rc = static_cast<ReturnSmallDlg::ButtonPressed>(dlg.ShowModal()); + + folderPathHistory = folderHistory->getList(); //unconditionally write path history: support manual item deletion + cancel + return rc; +} + +//######################################################################################## + class DeleteDialog : public DeleteDlgGenerated { public: @@ -279,7 +413,7 @@ DeleteDialog::DeleteDialog(wxWindow* parent, m_checkBoxUseRecycler->SetValue(useRecycleBin); -#ifndef __WXGTK__ //wxWidgets holds portability promise by not supporting for multi-line controls...not +#ifndef __WXGTK__ //wxWidgets holds portability promise by supporting multi-line controls...not m_textCtrlFileList->SetMaxLength(0); //allow large entries! #endif @@ -300,8 +434,8 @@ void DeleteDialog::updateGui() wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! #endif - const std::pair<std::wstring, int> delInfo = zen::deleteFromGridAndHDPreview(rowsToDeleteOnLeft, - rowsToDeleteOnRight); + const std::pair<std::wstring, int> delInfo = zen::getSelectedItemsAsString(rowsToDeleteOnLeft, + rowsToDeleteOnRight); wxString header; if (m_checkBoxUseRecycler->GetValue()) { @@ -318,8 +452,7 @@ void DeleteDialog::updateGui() m_buttonOK->SetLabel(_("Delete")); } m_staticTextHeader->SetLabel(header); - //it seems like Wrap() needs to be reapplied after SetLabel() - m_staticTextHeader->Wrap(460); + m_staticTextHeader->Wrap(460); //needs to be reapplied after SetLabel() m_textCtrlFileList->ChangeValue(delInfo.first); /* @@ -361,10 +494,7 @@ ReturnSmallDlg::ButtonPressed zen::showDeleteDialog(wxWindow* parent, const std::vector<zen::FileSystemObject*>& rowsOnRight, bool& useRecycleBin) { - DeleteDialog confirmDeletion(parent, - rowsOnLeft, - rowsOnRight, - useRecycleBin); + DeleteDialog confirmDeletion(parent, rowsOnLeft, rowsOnRight, useRecycleBin); return static_cast<ReturnSmallDlg::ButtonPressed>(confirmDeletion.ShowModal()); } @@ -498,18 +628,17 @@ OptionsDlg::OptionsDlg(wxWindow* parent, xmlAccess::XmlGlobalSettings& globalSet #endif setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); - warn_static("remove after test") - //#ifdef ZEN_MAC - // SetTitle(_("Preferences")); //follow OS conventions - //#endif +#ifdef ZEN_MAC + SetTitle(replaceCpy(_("&Preferences"), L"&", L"")); //follow OS conventions +#endif //setMainInstructionFont(*m_staticTextHeader); m_gridCustomCommand->SetTabBehaviour(wxGrid::Tab_Leave); - m_bitmapSettings ->SetBitmap (getResourceImage(L"settings")); - m_bpButtonAddRow ->SetBitmapLabel(getResourceImage(L"item_add")); - m_bpButtonRemoveRow ->SetBitmapLabel(getResourceImage(L"item_remove")); + m_bitmapSettings ->SetBitmap (getResourceImage(L"settings")); + m_bpButtonAddRow ->SetBitmapLabel(getResourceImage(L"item_add")); + m_bpButtonRemoveRow->SetBitmapLabel(getResourceImage(L"item_remove")); setBitmapTextLabel(*m_buttonResetDialogs, getResourceImage(L"reset_dialogs").ConvertToImage(), m_buttonResetDialogs->GetLabel()); m_checkBoxFailSafe ->SetValue(globalSettings.failsafeFileCopy); diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h index 8b890f83..d45e1688 100644 --- a/FreeFileSync/Source/ui/small_dlgs.h +++ b/FreeFileSync/Source/ui/small_dlgs.h @@ -28,6 +28,15 @@ void showAboutDialog(wxWindow* parent); ReturnSmallDlg::ButtonPressed showFilterDialog(wxWindow* parent, FilterConfig& filter, const wxString& caption); +ReturnSmallDlg::ButtonPressed showCopyToDialog(wxWindow* parent, + const std::vector<FileSystemObject*>& rowsOnLeft, + const std::vector<FileSystemObject*>& rowsOnRight, + Zstring& lastUsedPath, + std::vector<Zstring>& folderPathHistory, + size_t historySizeMax, + bool& keepRelPaths, + bool& overwriteIfExists); + ReturnSmallDlg::ButtonPressed showDeleteDialog(wxWindow* parent, const std::vector<FileSystemObject*>& rowsOnLeft, const std::vector<FileSystemObject*>& rowsOnRight, diff --git a/FreeFileSync/Source/ui/sorting.h b/FreeFileSync/Source/ui/sorting.h index 0f8dfb22..83d06c1a 100644 --- a/FreeFileSync/Source/ui/sorting.h +++ b/FreeFileSync/Source/ui/sorting.h @@ -131,9 +131,9 @@ template <bool ascending, SelectedSide side> inline bool lessFiletime(const FileSystemObject& a, const FileSystemObject& b) { if (a.isEmpty<side>()) - return false; //empty rows always last + return false; //empty rows always last else if (b.isEmpty<side>()) - return true; //empty rows always last + return true; //empty rows always last const FilePair* fileObjA = dynamic_cast<const FilePair*>(&a); const FilePair* fileObjB = dynamic_cast<const FilePair*>(&b); @@ -144,7 +144,7 @@ bool lessFiletime(const FileSystemObject& a, const FileSystemObject& b) if (!fileObjA && !linkObjA) return false; //directories last else if (!fileObjB && !linkObjB) - return true; //directories last + return true; //directories last const std::int64_t dateA = fileObjA ? fileObjA->getLastWriteTime<side>() : linkObjA->getLastWriteTime<side>(); const std::int64_t dateB = fileObjB ? fileObjB->getLastWriteTime<side>() : linkObjB->getLastWriteTime<side>(); @@ -158,14 +158,14 @@ template <bool ascending, SelectedSide side> inline bool lessExtension(const FileSystemObject& a, const FileSystemObject& b) { if (a.isEmpty<side>()) - return false; //empty rows always last + return false; //empty rows always last else if (b.isEmpty<side>()) - return true; //empty rows always last + return true; //empty rows always last if (dynamic_cast<const DirPair*>(&a)) return false; //directories last else if (dynamic_cast<const DirPair*>(&b)) - return true; //directories last + return true; //directories last auto getExtension = [&](const FileSystemObject& fsObj) -> Zstring { diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 950e8f4e..a4aa7f30 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -212,7 +212,7 @@ ConfigDialog::ConfigDialog(wxWindow* parent, miscCfgOut(miscCfg), handleDeletion(DELETE_TO_RECYCLER), // onGuiError(ON_GUIERROR_POPUP), //dummy init - versioningFolder(*m_panelVersioning, *m_buttonSelectDirVersioning, *m_bpButtonSelectSftp, *m_versioningFolder/*, m_staticTextResolvedPath*/) + versioningFolder(*m_panelVersioning, *m_buttonSelectDirVersioning, *m_bpButtonSelectSftp, *m_versioningFolder, nullptr /*staticText*/, nullptr /*wxWindow*/) { #ifdef ZEN_WIN new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" @@ -387,12 +387,15 @@ void ConfigDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events witho { case WXK_F6: m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::COMPARISON)); + m_buttonOkay->SetFocus(); return; //handled! case WXK_F7: m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::FILTER)); + m_buttonOkay->SetFocus(); return; case WXK_F8: m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::SYNC)); + m_buttonOkay->SetFocus(); return; } @@ -740,7 +743,7 @@ void toggleDeletionPolicy(DeletionPolicy& deletionPolicy) void ConfigDialog::setSyncOptions(const SyncOptions& so) { - directionCfg = so.syncCfg.directionCfg; //make working copy; ownership *not* on GUI + directionCfg = so.syncCfg.directionCfg; //make working copy; ownership *not* on GUI handleDeletion = so.syncCfg.handleDeletion; versioningFolder.setPath(so.syncCfg.versioningFolderPhrase); diff --git a/FreeFileSync/Source/ui/sync_cfg.h b/FreeFileSync/Source/ui/sync_cfg.h index b560c7ac..98b22181 100644 --- a/FreeFileSync/Source/ui/sync_cfg.h +++ b/FreeFileSync/Source/ui/sync_cfg.h @@ -29,16 +29,24 @@ enum class SyncConfigPanel SYNC = 2, // }; -struct MiscGlobalCfg +struct MiscGlobalCfg //don't use references to allow distinct copies { - xmlAccess::OnGuiError& handleError; //in/out param - - Zstring& onCompletionCommand; // - std::vector<Zstring>& onCompletionHistory; + xmlAccess::OnGuiError handleError; //in/out param + Zstring onCompletionCommand; // + std::vector<Zstring> onCompletionHistory; size_t onCompletionHistoryMax; }; +inline +bool operator==(const MiscGlobalCfg& lhs, const MiscGlobalCfg& rhs) +{ + return lhs.handleError == rhs.handleError && + lhs.onCompletionCommand == rhs.onCompletionCommand && + lhs.onCompletionHistory == rhs.onCompletionHistory && + lhs.onCompletionHistoryMax == rhs.onCompletionHistoryMax; +} + ReturnSyncConfig::ButtonPressed showSyncConfigDlg(wxWindow* parent, SyncConfigPanel panelToShow, bool* useAlternateCmpCfg, //optional parameter diff --git a/FreeFileSync/Source/ui/taskbar.cpp b/FreeFileSync/Source/ui/taskbar.cpp index a41eb1fe..b12d6a06 100644 --- a/FreeFileSync/Source/ui/taskbar.cpp +++ b/FreeFileSync/Source/ui/taskbar.cpp @@ -36,14 +36,24 @@ public: if (!win7OrLater()) //check *before* trying to load DLL throw TaskbarNotAvailable(); + freeString_ = DllFun<FunType_freeString >(getDllName(), funName_freeString); setStatus_ = DllFun<FunType_setStatus >(getDllName(), funName_setStatus); setProgress_ = DllFun<FunType_setProgress>(getDllName(), funName_setProgress); - if (!assocWindow || !setStatus_ || !setProgress_) + if (!assocWindow || !freeString_ || !setStatus_ || !setProgress_) throw TaskbarNotAvailable(); } - ~Pimpl() { setStatus_(assocWindow, tbseven::STATUS_NOPROGRESS); } + ~Pimpl() + { + const wchar_t* errorMsg = nullptr; + setStatus_(assocWindow, tbseven::STATUS_NOPROGRESS, errorMsg); + if (errorMsg) + { + ZEN_ON_SCOPE_EXIT(freeString_(errorMsg)); + assert(false); + } + } void setStatus(Status status) { @@ -64,16 +74,30 @@ public: break; } - setStatus_(assocWindow, tbSevenStatus); + const wchar_t* errorMsg = nullptr; + setStatus_(assocWindow, tbSevenStatus, errorMsg); + if (errorMsg) + { + ZEN_ON_SCOPE_EXIT(freeString_(errorMsg)); + assert(false); + } } void setProgress(double fraction) { - setProgress_(assocWindow, fraction * 100000, 100000); + const wchar_t* errorMsg = nullptr; + setProgress_(assocWindow, fraction * 100000, 100000, errorMsg); + if (errorMsg) + { + ZEN_ON_SCOPE_EXIT(freeString_(errorMsg)); + assert(false); + } } private: void* const assocWindow; //HWND + + DllFun<FunType_freeString> freeString_; DllFun<FunType_setStatus> setStatus_; DllFun<FunType_setProgress> setProgress_; }; diff --git a/FreeFileSync/Source/ui/tree_view.h b/FreeFileSync/Source/ui/tree_view.h index aa7f07b0..1c511135 100644 --- a/FreeFileSync/Source/ui/tree_view.h +++ b/FreeFileSync/Source/ui/tree_view.h @@ -116,9 +116,9 @@ private: firstFileId(nullptr) {} std::uint64_t bytesGross; - std::uint64_t bytesNet; //bytes for files on view in this directory only + std::uint64_t bytesNet; //bytes for files on view in this directory only int itemCountGross; - int itemCountNet; //number of files on view for in this directory only + int itemCountNet; //number of files on view for in this directory only std::vector<DirNodeImpl> subDirs; FileSystemObject::ObjectId firstFileId; //weak pointer to first FilePair or SymlinkPair diff --git a/FreeFileSync/Source/ui/wx_form_build_hide_warnings.h b/FreeFileSync/Source/ui/wx_form_build_hide_warnings.h deleted file mode 100644 index 679fb62f..00000000 --- a/FreeFileSync/Source/ui/wx_form_build_hide_warnings.h +++ /dev/null @@ -1,22 +0,0 @@ -// ************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#ifndef WX_FORM_BUILD_230948324234234 -#define WX_FORM_BUILD_230948324234234 - -//pamper over wxFormBuilder "sub-optimal" code - -#ifdef __GNUC__ - #pragma GCC diagnostic ignored "-Wunused-variable" - #ifndef __clang__ //clang seems to define __GNUC__, but doesn't support this warning - #pragma GCC diagnostic ignored "-Wunused-but-set-variable" - #endif - -#elif defined _MSC_VER - #pragma warning(disable: 4189) -#endif - -#endif //WX_FORM_BUILD_230948324234234 diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 0ac9ee9f..9f098a22 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t ffsVersion[] = L"7.2"; //internal linkage! +const wchar_t ffsVersion[] = L"7.3"; //internal linkage! const wchar_t FFS_VERSION_SEPERATOR = L'.'; } diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 2feef6e2..c9f2469c 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -28,7 +28,7 @@ namespace zen /* 1. setup a window to emit EVENT_DROP_FILE: - - simple file system paths: setupFileDrop + - simple file system paths: setupFileDrop - any shell paths with validation: setupShellItemDrop 2. register events: @@ -64,14 +64,14 @@ const wxEventType EVENT_DROP_FILE = impl::createNewEventType(); class FileDropEvent : public wxCommandEvent { public: - FileDropEvent(const std::vector<Zstring>& filesDropped) : wxCommandEvent(EVENT_DROP_FILE), filesDropped_(filesDropped) {} + FileDropEvent(const std::vector<Zstring>& droppedPaths) : wxCommandEvent(EVENT_DROP_FILE), droppedPaths_(droppedPaths) {} - const std::vector<Zstring>& getFiles() const { return filesDropped_; } + const std::vector<Zstring>& getPaths() const { return droppedPaths_; } private: wxEvent* Clone() const override { return new FileDropEvent(*this); } - const std::vector<Zstring> filesDropped_; + const std::vector<Zstring> droppedPaths_; }; typedef void (wxEvtHandler::*FileDropEventFunction)(FileDropEvent&); diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 7573fcb9..b24fabdc 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -40,7 +40,15 @@ const int GridData::COLUMN_GAP_LEFT = 4; namespace { -//------------ Grid Constants -------------------------------- +//------------------------------ Grid Parameters -------------------------------- +wxColor getColorLabelText() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } + +wxColor getColorLabelGradientFrom () { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } +wxColor getColorLabelGradientTo () { return wxColour(200, 200, 200); } //light grey + +wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); } +wxColor getColorLabelGradientFocusTo () { return getColorSelectionGradientFrom(); } + const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value as Explorer! const int DEFAULT_COL_LABEL_BORDER = 6; //top + bottom border in addition to label height //const int COLUMN_LABEL_BORDER = GridData::COLUMN_GAP_LEFT; @@ -50,12 +58,6 @@ const int ROW_LABEL_BORDER = 3; const int COLUMN_RESIZE_TOLERANCE = 6; //unit [pixel] const int COLUMN_FILL_GAP_TOLERANCE = 10; //enlarge column to fill full width when resizing -const wxColor COLOR_LABEL_GRADIENT_FROM = *wxWHITE; -const wxColor COLOR_LABEL_GRADIENT_TO = wxColour(200, 200, 200); //light grey - -const wxColor COLOR_LABEL_GRADIENT_FROM_FOCUS = COLOR_LABEL_GRADIENT_FROM; -const wxColor COLOR_LABEL_GRADIENT_TO_FOCUS = getColorSelectionGradientFrom(); - const wxColor colorGridLine = wxColour(192, 192, 192); //light grey const bool fillGapAfterColumns = true; //draw rows/column label to fill full window width; may become an instance variable some time? @@ -202,7 +204,7 @@ wxRect GridData::drawColumnLabelBorder(wxDC& dc, const wxRect& rect) //returns r //draw border (with gradient) { wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); - dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight()), COLOR_LABEL_GRADIENT_FROM, dc.GetPen().GetColour(), wxSOUTH); + dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); } @@ -213,15 +215,15 @@ wxRect GridData::drawColumnLabelBorder(wxDC& dc, const wxRect& rect) //returns r void GridData::drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted) { if (highlighted) - dc.GradientFillLinear(rect, COLOR_LABEL_GRADIENT_FROM_FOCUS, COLOR_LABEL_GRADIENT_TO_FOCUS, wxSOUTH); + dc.GradientFillLinear(rect, getColorLabelGradientFocusFrom(), getColorLabelGradientFocusTo(), wxSOUTH); else //regular background gradient - dc.GradientFillLinear(rect, COLOR_LABEL_GRADIENT_FROM, COLOR_LABEL_GRADIENT_TO, wxSOUTH); //clear overlapping cells + dc.GradientFillLinear(rect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH); //clear overlapping cells } void GridData::drawColumnLabelText(wxDC& dc, const wxRect& rect, const wxString& text) { - wxDCTextColourChanger dummy(dc, *wxBLACK); //accessibility: always set both foreground AND background colors! + wxDCTextColourChanger dummy(dc, getColorLabelText()); //accessibility: always set both foreground AND background colors! drawTextLabelFitting(dc, text, rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); } @@ -384,17 +386,17 @@ private: { const wxRect& clientRect = GetClientRect(); - dc.GradientFillLinear(clientRect, COLOR_LABEL_GRADIENT_FROM, COLOR_LABEL_GRADIENT_TO, wxSOUTH); + dc.GradientFillLinear(clientRect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH); dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); { - wxDCPenChanger dummy(dc, COLOR_LABEL_GRADIENT_FROM); + wxDCPenChanger dummy(dc, getColorLabelGradientFrom()); dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight()); } - dc.GradientFillLinear(wxRect(clientRect.GetBottomLeft (), clientRect.GetTopLeft ()), COLOR_LABEL_GRADIENT_FROM, dc.GetPen().GetColour(), wxSOUTH); - dc.GradientFillLinear(wxRect(clientRect.GetBottomRight(), clientRect.GetTopRight()), COLOR_LABEL_GRADIENT_FROM, dc.GetPen().GetColour(), wxSOUTH); + dc.GradientFillLinear(wxRect(clientRect.GetBottomLeft (), clientRect.GetTopLeft ()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH); + dc.GradientFillLinear(wxRect(clientRect.GetBottomRight(), clientRect.GetTopRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH); dc.DrawLine(clientRect.GetBottomLeft(), clientRect.GetBottomRight()); @@ -470,12 +472,11 @@ private: void render(wxDC& dc, const wxRect& rect) override { - /* IsEnabled() vs IsThisEnabled() since wxWidgets 2.9.5: - void wxWindowBase::NotifyWindowOnEnableChange(), called from bool wxWindowBase::Enable(), has this buggy exception of NOT - refreshing child elements when disabling a IsTopLevel() dialog, e.g. when showing a modal dialog. + void wxWindowBase::NotifyWindowOnEnableChange(), called from bool wxWindowBase::Enable(), fails to refresh + child elements when disabling a IsTopLevel() dialog, e.g. when showing a modal dialog. The unfortunate effect on XP for using IsEnabled() when rendering the grid is that the user can move the modal dialog and *draw* with it on the background while the grid refreshes as disabled incrementally! @@ -513,8 +514,8 @@ private: void drawRowLabel(wxDC& dc, const wxRect& rect, size_t row) { //clearArea(dc, rect, getColorRowLabel()); - dc.GradientFillLinear(rect, COLOR_LABEL_GRADIENT_FROM, COLOR_LABEL_GRADIENT_TO, wxEAST); //clear overlapping cells - wxDCTextColourChanger dummy3(dc, *wxBLACK); //accessibility: always set both foreground AND background colors! + dc.GradientFillLinear(rect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxEAST); //clear overlapping cells + wxDCTextColourChanger dummy3(dc, getColorLabelText()); //accessibility: always set both foreground AND background colors! //label text wxRect textRect = rect; @@ -673,9 +674,9 @@ private: if (activeMove && activeMove->isRealMove()) { if (col + 1 == activeMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position - dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), COLOR_LABEL_GRADIENT_FROM, *wxBLUE, wxSOUTH); + dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); else if (col == activeMove->refColumnTo() && col == 0) //pos 0 - dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), COLOR_LABEL_GRADIENT_FROM, *wxBLUE, wxSOUTH); + dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); } } } @@ -2050,6 +2051,13 @@ void Grid::scrollTo(size_t row) } + bool Grid::Enable(bool enable) + { + Refresh(); + return wxScrolledWindow::Enable(enable); + } + + size_t Grid::getGridCursor() const { return mainWin_->getCursor(); @@ -192,7 +192,8 @@ public: void scrollTo(size_t row); void Refresh(bool eraseBackground = true, const wxRect* rect = nullptr) override; - bool Enable( bool enable = true) override { Refresh(); return wxScrolledWindow::Enable(enable); } + bool Enable(bool enable = true) override; + //############################################################################################################ private: @@ -234,11 +235,11 @@ private: std::vector<size_t> get() const { - std::vector<size_t> selection; + std::vector<size_t> result; for (size_t row = 0; row < rowSelectionValue.size(); ++row) if (rowSelectionValue[row] != 0) - selection.push_back(row); - return selection; + result.push_back(row); + return result; } void selectAll() { selectRange(0, rowSelectionValue.size(), true); } diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 9d4f2396..7383f620 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -42,10 +42,9 @@ class GlobalResources public: static GlobalResources& instance() { -#if defined _MSC_VER && _MSC_VER > 1800 -#error remove warning +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! #endif - //caveat: function scope static initialization is not thread-safe in VS 2010! => but we wouldn't use wxWidgets in combination with multithreading anyway! static GlobalResources inst; return inst; } diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp index 91298db4..ea9852d3 100644 --- a/wx+/tooltip.cpp +++ b/wx+/tooltip.cpp @@ -73,7 +73,7 @@ void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp) } tipWindow->GetSizer()->SetSizeHints(tipWindow); //~=Fit() + SetMinSize() - //Linux: Fit() seems to be somewhat broken => this needs to be called EVERY time inside show, not only if text or bmp change + //Linux: Fit() seems to be broken => this needs to be called EVERY time inside show, not only if text or bmp change const wxPoint newPos = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? mousePos - wxPoint(30 + tipWindow->GetSize().GetWidth(), 0) : diff --git a/zen/async_task.h b/zen/async_task.h index 5c6f7f6e..d8f489a3 100644 --- a/zen/async_task.h +++ b/zen/async_task.h @@ -11,7 +11,6 @@ #include <functional> #include "thread.h" #include "scope_guard.h" -//#include "type_tools.h" namespace zen { @@ -19,25 +18,25 @@ namespace zen class AsyncTasks { public: - AsyncTasks() : inRecursion(false) {} + AsyncTasks() {} template <class Fun, class Fun2> - void add(Fun doAsync, Fun2 evalOnGui) - //equivalent to "evalOnGui(doAsync())" - // -> doAsync: the usual thread-safety requirements apply! + void add(Fun runAsync, Fun2 evalOnGui) + //equivalent to "evalOnGui(runAsync())" + // -> runAsync: the usual thread-safety requirements apply! // -> evalOnGui: no thread-safety concerns, but must only reference variables with greater-equal lifetime than the AsyncTask instance! { tasks.push_back(zen::runAsync([=]() -> std::function<void()> { - auto result = doAsync(); + auto result = runAsync(); return [=]{ evalOnGui(result); }; })); } template <class Fun, class Fun2> - void add2(Fun doAsync, Fun2 evalOnGui) //for evalOnGui taking no parameters + void add2(Fun runAsync, Fun2 evalOnGui) //for evalOnGui taking no parameters { - tasks.push_back(zen::runAsync([doAsync, evalOnGui]() -> std::function<void()> { doAsync(); return [evalOnGui]{ evalOnGui(); }; })); + tasks.push_back(zen::runAsync([runAsync, evalOnGui]() -> std::function<void()> { runAsync(); return [evalOnGui]{ evalOnGui(); }; })); } void evalResults() //call from gui thread repreatedly @@ -47,9 +46,9 @@ public: inRecursion = true; ZEN_ON_SCOPE_EXIT(inRecursion = false); - tasks.remove_if([](boost::unique_future<std::function<void()>>& ft) -> bool + tasks.remove_if([](std::future<std::function<void()>>& ft) -> bool { - if (ft.is_ready()) + if (isReady(ft)) { (ft.get())(); return true; @@ -62,8 +61,11 @@ public: bool empty() const { return tasks.empty(); } private: - bool inRecursion; - std::list<boost::unique_future<std::function<void()>>> tasks; + AsyncTasks (const AsyncTasks&) = delete; + AsyncTasks& operator=(const AsyncTasks&) = delete; + + bool inRecursion = false; + std::list<std::future<std::function<void()>>> tasks; }; } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index e93e2b06..4abf3c0a 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -7,7 +7,7 @@ #include "dir_watcher.h" #include <algorithm> #include <set> -#include "thread.h" //includes <boost/thread.hpp> +#include "thread.h" #include "scope_guard.h" #ifdef ZEN_WIN @@ -16,8 +16,11 @@ #include "long_path_prefix.h" #elif defined ZEN_LINUX + #include <map> #include <sys/inotify.h> - #include <fcntl.h> + #include <fcntl.h> //fcntl + #include <unistd.h> //close + #include <limits.h> //NAME_MAX #include "file_traverser.h" #elif defined ZEN_MAC @@ -37,7 +40,7 @@ public: //context of worker thread void addChanges(const char* buffer, DWORD bytesWritten, const Zstring& dirpath) //throw () { - boost::lock_guard<boost::mutex> dummy(lockAccess); + std::lock_guard<std::mutex> dummy(lockAccess); if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change changedFiles.emplace_back(DirWatcher::ACTION_CREATE, L"Overflow."); @@ -89,7 +92,7 @@ public: ////context of main thread //void addChange(const Zstring& dirpath) //throw () //{ - // boost::lock_guard<boost::mutex> dummy(lockAccess); + // std::lock_guard<std::mutex> dummy(lockAccess); // changedFiles.insert(dirpath); //} @@ -97,7 +100,7 @@ public: //context of main thread void fetchChanges(std::vector<DirWatcher::Entry>& output) //throw FileError { - boost::lock_guard<boost::mutex> dummy(lockAccess); + std::lock_guard<std::mutex> dummy(lockAccess); //first check whether errors occurred in thread if (errorInfo) @@ -115,7 +118,7 @@ public: //context of worker thread void reportError(const std::wstring& msg, const std::wstring& description, DWORD errorCode) //throw() { - boost::lock_guard<boost::mutex> dummy(lockAccess); + std::lock_guard<std::mutex> dummy(lockAccess); ErrorInfo newInfo = { copyStringTo<BasicWString>(msg), copyStringTo<BasicWString>(description), errorCode }; errorInfo = make_unique<ErrorInfo>(newInfo); @@ -124,7 +127,7 @@ public: private: typedef Zbase<wchar_t> BasicWString; //thread safe string class for UI texts - boost::mutex lockAccess; + std::mutex lockAccess; std::vector<DirWatcher::Entry> changedFiles; struct ErrorInfo @@ -174,13 +177,13 @@ public: ::CloseHandle(hDir); } - void operator()() //thread entry + void operator()() const //thread entry { std::vector<char> buffer(64 * 1024); //needs to be aligned on a DWORD boundary; maximum buffer size restricted by some networks protocols (according to docu) for (;;) { - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption //actual work OVERLAPPED overlapped = {}; @@ -244,7 +247,7 @@ public: ::SleepEx(50, // __in DWORD dwMilliseconds, true); // __in BOOL bAlertable - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption } guardAio.dismiss(); @@ -271,7 +274,7 @@ class HandleVolumeRemoval public: HandleVolumeRemoval(HANDLE hDir, const Zstring& displayPath, - boost::thread& worker) : + InterruptibleThread& worker) : notificationHandle(registerFolderRemovalNotification(hDir, //throw FileError displayPath, [this] { this->onRequestRemoval (); }, //noexcept! @@ -307,7 +310,7 @@ private: void onRemovalFinished() { operationComplete = true; } //noexcept! DeviceNotificationHandle* notificationHandle; - boost::thread& worker_; + InterruptibleThread& worker_; bool removalRequested; bool operationComplete; }; @@ -316,7 +319,7 @@ private: struct DirWatcher::Pimpl { - boost::thread worker; + InterruptibleThread worker; std::shared_ptr<SharedData> shared; std::unique_ptr<HandleVolumeRemoval> volRemoval; }; @@ -330,7 +333,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError ReadChangesAsync reader(dirPath, pimpl_->shared); //throw FileError pimpl_->volRemoval = zen::make_unique<HandleVolumeRemoval>(reader.getDirHandle(), dirPath, pimpl_->worker); //throw FileError - pimpl_->worker = boost::thread(std::move(reader)); + pimpl_->worker = InterruptibleThread(std::move(reader)); } @@ -339,10 +342,8 @@ DirWatcher::~DirWatcher() if (pimpl_->worker.joinable()) //= thread::detach() precondition! -> may already be joined by HandleVolumeRemoval::onRequestRemoval() { pimpl_->worker.interrupt(); - //if (pimpl_->worker.joinable()) pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway - pimpl_->worker.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! + pimpl_->worker.detach(); //we don't have time to wait... will take ~50ms anyway: } - //caveat: exitting the app may simply kill this thread! } @@ -355,13 +356,13 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() //wait until device removal is confirmed, to prevent locking hDir again by some new watch! if (pimpl_->volRemoval->requestReceived()) { - const boost::chrono::steady_clock::time_point endTime = boost::chrono::steady_clock::now() + boost::chrono::seconds(15); + const std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now() + std::chrono::seconds(15); //HandleVolumeRemoval::finished() not guaranteed! note: Windows gives unresponsive applications ca. 10 seconds until unmounting the usb stick in worst case - while (!pimpl_->volRemoval->finished() && boost::chrono::steady_clock::now() < endTime) + while (!pimpl_->volRemoval->finished() && std::chrono::steady_clock::now() < endTime) { processGuiMessages(); //DBT_DEVICEREMOVECOMPLETE message is sent here! - boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } output.emplace_back(ACTION_DELETE, baseDirPath); //report removal as change to main directory diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 09a1eb07..84d3b264 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -434,26 +434,29 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro if (lastError == ERROR_NOT_SAME_DEVICE) throw ErrorDifferentVolume(errorMsg, errorDescr); - else if (lastError == ERROR_ALREADY_EXISTS || //-> used on Win7 x64 + if (lastError == ERROR_ALREADY_EXISTS || //-> used on Win7 x64 lastError == ERROR_FILE_EXISTS) //-> used by XP??? throw ErrorTargetExisting(errorMsg, errorDescr); - else - throw FileError(errorMsg, errorDescr); + throw FileError(errorMsg, errorDescr); } #elif defined ZEN_LINUX || defined ZEN_MAC - if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0) + //rename() will never fail with EEXIST, but always overwrite! + //=> OS X: no solution + //=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy + const bool alreadyExists = somethingExists(pathTarget); //we have to let go of atomicity! + + if (alreadyExists || ::rename(pathSource.c_str(), pathTarget.c_str()) != 0) { - const int lastError = errno; //copy before directly or indirectly making other system calls! + const int lastError = alreadyExists ? EEXIST : errno; //copy before directly or indirectly making other system calls! const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget)); const std::wstring errorDescr = formatSystemError(L"rename", lastError); if (lastError == EXDEV) throw ErrorDifferentVolume(errorMsg, errorDescr); - else if (lastError == EEXIST) - throw ErrorTargetExisting(errorMsg, errorDescr); - else - throw FileError(errorMsg, errorDescr); + if (lastError == EEXIST) + throw ErrorTargetExisting(errorMsg, errorDescr); + throw FileError(errorMsg, errorDescr); } #endif } @@ -2039,12 +2042,6 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, return PROGRESS_CONTINUE; } -#if defined _MSC_VER && _MSC_VER > 1800 - #error get rid! -#endif -const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000 -//caveat: function scope static initialization is not thread-safe in VS 2010! -> still not sufficient if multiple threads access during static init!!! - InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream const Zstring& targetFile, @@ -2062,8 +2059,8 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; - if (supportNonEncryptedDestination) - copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location + //encrypted destination is not supported with Windows 2000! -> whatever + copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location //if (vistaOrLater()) //see http://blogs.technet.com/b/askperf/archive/2007/05/08/slow-large-file-copy-issues.aspx // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, improvement for large files (20% in test NTFS -> NTFS) diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 5f529f9c..9624458c 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -190,7 +190,10 @@ public: private: static const IntegerFormat& getInst() { - static IntegerFormat inst; //not threadsafe in MSVC until C++11, but not required right now +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif + static IntegerFormat inst; return inst; } @@ -276,14 +279,6 @@ std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) } -#ifdef ZEN_WIN -namespace -{ -const bool useNewLocalTimeCalculation = zen::vistaOrLater(); -} -#endif - - std::wstring zen::utcToLocalTimeString(std::int64_t utcTime) { auto errorMsg = [&] { return _("Error") + L" (time_t: " + numberTo<std::wstring>(utcTime) + L")"; }; @@ -293,6 +288,11 @@ std::wstring zen::utcToLocalTimeString(std::int64_t utcTime) SYSTEMTIME systemTimeLocal = {}; +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif + static const bool useNewLocalTimeCalculation = zen::vistaOrLater(); + //http://msdn.microsoft.com/en-us/library/ms724277(VS.85).aspx if (useNewLocalTimeCalculation) //DST conversion like in Windows 7: NTFS stays fixed, but FAT jumps by one hour { @@ -10,7 +10,7 @@ #include <string> #include <boost/uuid/uuid.hpp> -#ifdef __GNUC__ //boost should start cleaning this mess up +#ifdef __GNUC__ //boost should clean this mess up #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wuninitialized" diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index f7ceb5e7..3db2722b 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -122,7 +122,6 @@ Zstring zen::ntPathToWin32Path(const Zstring& path) //noexcept const DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName, &buf[0], //_Out_opt_ LPTSTR lpBuffer, bufSize); //_In_ DWORD nSize - if (0 < charsWritten && charsWritten < bufSize) return replaceCpy(path, L"\\SystemRoot\\", appendSeparator(Zstring(&buf[0], charsWritten)), false); } diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp index c5932900..577e33a6 100644 --- a/zen/process_priority.cpp +++ b/zen/process_priority.cpp @@ -5,7 +5,6 @@ // ************************************************************************** #include "process_priority.h" -//#include "sys_error.h" #include "i18n.h" #ifdef ZEN_WIN diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 6cd34a17..75083d57 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -187,7 +187,7 @@ bool zen::recycleBinExists(const Zstring& dirpath, const std::function<void ()>& &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo }); - while (ft.wait_for(boost::chrono::milliseconds(50)) != boost::future_status::ready) + while (ft.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready) if (onUpdateGui) onUpdateGui(); //may throw! diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 8477c7ee..5e917853 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -8,8 +8,7 @@ #define ZEN_SCOPEGUARD_8971632487321434 #include <cassert> -//#include <type_traits> //std::decay -//#include <utility> +#include <type_traits> //std::decay //best of Zen, Loki and C++11 diff --git a/zen/stl_tools.h b/zen/stl_tools.h index bd76e264..685f5118 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -9,7 +9,7 @@ #include <memory> #include <algorithm> -#include <zen/type_tools.h> +#include "type_tools.h" //enhancements for <algorithm> @@ -22,6 +22,9 @@ void vector_remove_if(V& vec, Predicate p); template <class V, class W> void vector_append(V& vec, const W& vec2); +template <class V> +void removeDuplicates(V& v); + template <class V, class W> void set_append(V& s, const W& s2); @@ -72,6 +75,14 @@ void vector_remove_if(V& vec, Predicate p) } +template <class V> inline +void removeDuplicates(V& v) +{ + std::sort(v.begin(), v.end()); + v.erase(std::unique(v.begin(), v.end()), v.end()); +} + + template <class V, class W> inline void vector_append(V& vec, const W& vec2) { diff --git a/zen/string_base.h b/zen/string_base.h index f4ca5f2e..1bf8ed68 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -195,11 +195,10 @@ private: struct Descriptor { Descriptor(size_t len, size_t cap) : - refCount(1), length (static_cast<std::uint32_t>(len)), capacity(static_cast<std::uint32_t>(cap)) { static_assert(ATOMIC_INT_LOCK_FREE == 2, ""); } //2: "the types are always lock-free" - std::atomic<unsigned int> refCount; + std::atomic<unsigned int> refCount { 1 }; //std:atomic is uninitialized by default! std::uint32_t length; std::uint32_t capacity; //allocated size without null-termination }; @@ -222,11 +221,13 @@ public: Zbase(const Char* source); //implicit conversion from a C-string Zbase(const Char* source, size_t length); Zbase(const Zbase& source); - Zbase(Zbase&& tmp); //make noexcept in C++11 + Zbase(Zbase&& tmp) noexcept; explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! - //allow explicit construction from different string type, prevent ambiguity via SFINAE - template <class S> explicit Zbase(const S& other, typename S::value_type = 0); - ~Zbase(); //make noexcept in C++11 + +//allow explicit construction from different string type, prevent ambiguity via SFINAE +//template <class S> explicit Zbase(const S& other, typename S::value_type = 0); + + ~Zbase(); //operator const Char* () const; //NO implicit conversion to a C-string!! Many problems... one of them: if we forget to provide operator overloads, it'll just work with a Char*... @@ -263,11 +264,11 @@ public: Zbase& assign(const Char* source, size_t len); Zbase& append(const Char* source, size_t len); void resize(size_t newSize, Char fillChar = 0); - void swap(Zbase& other); //make noexcept in C++11 + void swap(Zbase& other); void push_back(Char val) { operator+=(val); } //STL access Zbase& operator=(const Zbase& source); - Zbase& operator=(Zbase&& tmp); //make noexcept in C++11 + Zbase& operator=(Zbase&& tmp) noexcept; Zbase& operator=(const Char* source); Zbase& operator=(Char source); Zbase& operator+=(const Zbase& other); @@ -377,14 +378,14 @@ Zbase<Char, SP, AP>::Zbase(const Zbase<Char, SP, AP>& source) template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>::Zbase(Zbase<Char, SP, AP>&& tmp) +Zbase<Char, SP, AP>::Zbase(Zbase<Char, SP, AP>&& tmp) noexcept { rawStr = tmp.rawStr; tmp.rawStr = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor! //caveat: do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory! } - +/* template <class Char, template <class, class> class SP, class AP> template <class S> inline Zbase<Char, SP, AP>::Zbase(const S& other, typename S::value_type) @@ -394,11 +395,13 @@ Zbase<Char, SP, AP>::Zbase(const S& other, typename S::value_type) std::copy(other.c_str(), other.c_str() + sourceLen, rawStr); rawStr[sourceLen] = 0; } - +*/ template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP>::~Zbase() { + static_assert(noexcept(this->~Zbase()), ""); //has exception spec of compiler-generated destructor by default + this->destroy(rawStr); //rawStr may be nullptr; see move constructor! } @@ -650,7 +653,7 @@ Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(const Zbase<Char, SP, AP>& o template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Zbase<Char, SP, AP>&& tmp) +Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Zbase<Char, SP, AP>&& tmp) noexcept { swap(tmp); //don't use unifying assignment but save one move-construction in the r-value case instead! return *this; diff --git a/zen/string_tools.h b/zen/string_tools.h index c04adf96..9708464e 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -18,7 +18,7 @@ #include "stl_tools.h" #include "string_traits.h" - + //enhance arbitray string class with useful non-member functions: namespace zen { @@ -48,11 +48,11 @@ template <class S, class T, class U> void replace ( S& str, const T& oldT template <class S, class T, class U> S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); //high-performance conversion between numbers and strings -template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf() - template <class S, class Num> S numberTo(const Num& number); template <class Num, class S > Num stringTo(const S& str); +template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf() + //string to string conversion: converts string-like type into char-compatible target string class template <class T, class S> T copyStringTo(const S& str); diff --git a/zen/string_traits.h b/zen/string_traits.h index 5f91bdc4..add53d3a 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -7,6 +7,7 @@ #ifndef STRING_TRAITS_HEADER_813274321443234 #define STRING_TRAITS_HEADER_813274321443234 +#include <cstring> //strlen #include "type_tools.h" //uniform access to string-like types, both classes and character arrays @@ -143,19 +144,22 @@ struct GetCharType : ResultType<typename implementation::StringTraits<T>::CharTy namespace implementation { +//strlen/wcslen are vectorized since VS14 CTP3 +inline size_t cStringLength(const char* str) { return std::strlen(str); } +inline size_t cStringLength(const wchar_t* str) { return std::wcslen(str); } + +//no significant perf difference for "comparison" test case between cStringLength/wcslen: +#if 0 template <class C> inline -size_t cStringLength(const C* str) //naive implementation seems somewhat faster than "optimized" strlen/wcslen! +size_t cStringLength(const C* str) { -#if defined _MSC_VER && _MSC_VER > 1800 - static_assert(false, "strlen/wcslen are vectorized in VS14 CTP3 -> test again!"); -#endif - static_assert(IsSameType<C, char>::value || IsSameType<C, wchar_t>::value, ""); size_t len = 0; while (*str++ != 0) ++len; return len; } +#endif template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline const typename GetCharType<S>::Type* strBegin(const S& str) //SFINAE: T must be a "string" diff --git a/zen/symlink_target.h b/zen/symlink_target.h index c8c8c4be..aa320dfe 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -25,7 +25,7 @@ namespace zen { #ifdef ZEN_WIN - bool isSymlink(const WIN32_FIND_DATA& data); //*not* a simple FILE_ATTRIBUTE_REPARSE_POINT check! + bool isSymlink(const WIN32_FIND_DATA& data); //checking FILE_ATTRIBUTE_REPARSE_POINT is insufficient! bool isSymlink(DWORD fileAttributes, DWORD reparseTag); #endif @@ -162,8 +162,7 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); if (!getFinalPathNameByHandle) throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); - - + const HANDLE hFile = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName, 0, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, diff --git a/zen/thread.h b/zen/thread.h index 6d647de8..a3b8760b 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -4,39 +4,62 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef BOOST_THREAD_WRAP_H_78963234 -#define BOOST_THREAD_WRAP_H_78963234 - -//temporary solution until C++11 thread becomes fully available (considering std::thread's non-interruptibility and std::async craziness, this may be NEVER) -#include <memory> - -//workaround this pathetic boost thread warning mess -#ifdef __GNUC__ - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wswitch-enum" - #pragma GCC diagnostic ignored "-Wstrict-aliasing" - #pragma GCC diagnostic ignored "-Wredundant-decls" - #pragma GCC diagnostic ignored "-Wshadow" - #ifndef __clang__ //clang defines __GNUC__, but doesn't support this warning - #pragma GCC diagnostic ignored "-Wunused-local-typedefs" - #endif -#endif -#ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4702 4913) //unreachable code; user defined binary operator ',' exists but no overload could convert all operands, default built-in binary operator ',' used -#endif +#ifndef STD_THREAD_WRAP_H_7896323423432 +#define STD_THREAD_WRAP_H_7896323423432 -#include <boost/thread.hpp> - -#ifdef __GNUC__ - #pragma GCC diagnostic pop -#endif -#ifdef _MSC_VER - #pragma warning(pop) -#endif +#include <thread> +#include <future> +#include <zen/scope_guard.h> +#include <zen/type_traits.h> namespace zen { +class InterruptionStatus; + + +class InterruptibleThread +{ +public: + InterruptibleThread() {} + InterruptibleThread (InterruptibleThread&& tmp) = default; + InterruptibleThread& operator=(InterruptibleThread&& tmp) = default; + + template <class Function> + InterruptibleThread(Function f); + + bool joinable () const { return stdThread.joinable(); } + void interrupt(); + void join () { stdThread.join(); } + void detach () { stdThread.detach(); } + + template <class Rep, class Period> + bool tryJoinFor(const std::chrono::duration<Rep, Period>& relTime) + { + if (threadCompleted.wait_for(relTime) == std::future_status::ready) + { + stdThread.join(); //runs thread-local destructors => this better be fast!!! + return true; + } + return false; + } + +private: + std::thread stdThread; + std::shared_ptr<InterruptionStatus> intStatus_; + std::future<void> threadCompleted; +}; + +//context of worker thread: +void interruptionPoint(); //throw ThreadInterruption + +template<class Predicate> +void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadInterruption + +template <class Rep, class Period> +void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadInterruption + +//------------------------------------------------------------------------------------------ + /* std::async replacement without crappy semantics: 1. guaranteed to run asynchronously @@ -45,16 +68,20 @@ std::async replacement without crappy semantics: Example: Zstring dirpath = ... auto ft = zen::runAsync([=](){ return zen::dirExists(dirpath); }); - if (ft.wait_for(boost::chrono::milliseconds(200)) == boost::future_status::ready && ft.get()) + if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get()) //dir exising */ template <class Function> -auto runAsync(Function fun) -> boost::unique_future<decltype(fun())>; +auto runAsync(Function fun) -> std::future<decltype(fun())>; //wait for all with a time limit: return true if *all* results are available! template<class InputIterator, class Duration> bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); +template<typename T> inline +bool isReady(const std::future<T>& f) { return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } +//------------------------------------------------------------------------------------------ + //wait until first job is successful or all failed: substitute until std::when_any is available template <class T> class GetFirstResult @@ -77,6 +104,7 @@ private: size_t jobsTotal_; }; +//------------------------------------------------------------------------------------------ //value associated with mutex and guaranteed protected access: template <class T> @@ -89,7 +117,7 @@ public: template <class Function> void access(Function fun) { - boost::lock_guard<boost::mutex> dummy(lockValue); + std::lock_guard<std::mutex> dummy(lockValue); fun(value_); } @@ -97,7 +125,7 @@ private: Protected (const Protected&) = delete; Protected& operator=(const Protected&) = delete; - boost::mutex lockValue; + std::mutex lockValue; T value_; }; @@ -110,22 +138,15 @@ private: //###################### implementation ###################### -#ifndef BOOST_HAS_THREADS - #error just some paranoia check... -#endif template <class Function> inline -auto runAsync(Function fun) -> boost::unique_future<decltype(fun())> +auto runAsync(Function fun) -> std::future<decltype(fun())> { typedef decltype(fun()) ResultType; -#if defined BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK //mirror "boost/thread/future.hpp", hopefully they know what they're doing - boost::packaged_task<ResultType()> pt(std::move(fun)); -#else - boost::packaged_task<ResultType> pt(std::move(fun)); -#endif + std::packaged_task<ResultType()> pt(std::move(fun)); auto fut = pt.get_future(); - boost::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! + std::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! return fut; } @@ -133,9 +154,9 @@ auto runAsync(Function fun) -> boost::unique_future<decltype(fun())> template<class InputIterator, class Duration> inline bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& duration) { - const boost::chrono::steady_clock::time_point endTime = boost::chrono::steady_clock::now() + duration; + const std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now() + duration; for (; first != last; ++first) - if (first->wait_until(endTime) != boost::future_status::ready) + if (first->wait_until(endTime) != std::future_status::ready) return false; //time elapsed return true; } @@ -155,27 +176,26 @@ public: void reportFinished(std::unique_ptr<T>&& result) { { - boost::lock_guard<boost::mutex> dummy(lockResult); + std::lock_guard<std::mutex> dummy(lockResult); ++jobsFinished; if (!result_) result_ = std::move(result); } - conditionJobDone.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 - //condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref + conditionJobDone.notify_all(); //better notify all, considering bugs like: https://svn.boost.org/trac/boost/ticket/7796 } //context: main thread template <class Duration> bool waitForResult(size_t jobsTotal, const Duration& duration) { - boost::unique_lock<boost::mutex> dummy(lockResult); - return conditionJobDone.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); }); //throw boost::thread_interrupted + std::unique_lock<std::mutex> dummy(lockResult); + return conditionJobDone.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); }); } std::unique_ptr<T> getResult(size_t jobsTotal) { - boost::unique_lock<boost::mutex> dummy(lockResult); - conditionJobDone.wait(dummy, [&] { return this->jobDone(jobsTotal); }); //throw boost::thread_interrupted + std::unique_lock<std::mutex> dummy(lockResult); + conditionJobDone.wait(dummy, [&] { return this->jobDone(jobsTotal); }); #ifndef NDEBUG assert(!returnedResult); @@ -191,10 +211,10 @@ private: bool returnedResult; #endif - boost::mutex lockResult; + std::mutex lockResult; size_t jobsFinished; // std::unique_ptr<T> result_; //our condition is: "have result" or "jobsFinished == jobsTotal" - boost::condition_variable conditionJobDone; + std::condition_variable conditionJobDone; }; @@ -207,8 +227,7 @@ template <class T> template <class Fun> inline void GetFirstResult<T>::addJob(Fun f) //f must return a std::unique_ptr<T> containing a value on success { - auto asyncResult = this->asyncResult_; //capture member variable, not "this"! - boost::thread t([asyncResult, f] { asyncResult->reportFinished(f()); }); + std::thread t([asyncResult = this->asyncResult_, f = std::move(f)] { asyncResult->reportFinished(f()); }); ++jobsTotal_; t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! } @@ -221,6 +240,158 @@ bool GetFirstResult<T>::timedWait(const Duration& duration) const { return async template <class T> inline std::unique_ptr<T> GetFirstResult<T>::get() const { return asyncResult_->getResult(jobsTotal_); } + +//------------------------------------------------------------------------------------------ + +//thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: +#ifdef _MSC_VER + #define ZEN_THREAD_LOCAL_SPECIFIER __declspec(thread) +#elif defined __GNUC__ || defined __clang__ + #define ZEN_THREAD_LOCAL_SPECIFIER __thread +#else + #error "game over" +#endif + + +class ThreadInterruption {}; + + +class InterruptionStatus +{ +public: + //context of InterruptibleThread instance: + void interrupt() + { + interrupted = true; + + { + std::lock_guard<std::mutex> dummy(lockSleep); //needed! makes sure the following signal is not lost! + //usually we'd make "interrupted" non-atomic, but this is already given due to interruptibleWait() handling + } + conditionSleepInterruption.notify_all(); + + std::lock_guard<std::mutex> dummy(lockConditionPtr); + if (activeCondition) + activeCondition->notify_all(); //signal may get lost! + //alternative design locking the cv's mutex here could be dangerous: potential for dead lock! + } + + //context of worker thread: + void checkInterruption() //throw ThreadInterruption + { + if (interrupted) + throw ThreadInterruption(); + } + + //context of worker thread: + template<class Predicate> + void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption + { + setConditionVar(&cv); + ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr)); + + //"interrupted" is not protected by cv's mutex => signal may get lost!!! => add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out! + while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->interrupted || pred(); })) + ; + + checkInterruption(); //throw ThreadInterruption + } + + //context of worker thread: + template <class Rep, class Period> + void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption + { + std::unique_lock<std::mutex> lock(lockSleep); + if (conditionSleepInterruption.wait_for(lock, relTime, [&] { return static_cast<bool>(this->interrupted); })) + throw ThreadInterruption(); + } + +private: + void setConditionVar(std::condition_variable* cv) + { + std::lock_guard<std::mutex> dummy(lockConditionPtr); + activeCondition = cv; + } + + std::atomic<bool> interrupted{ false }; //std:atomic is uninitialized by default! + + std::condition_variable* activeCondition = nullptr; + std::mutex lockConditionPtr; //serialize pointer access (only!) + + std::condition_variable conditionSleepInterruption; + std::mutex lockSleep; +}; + + +namespace impl +{ +inline +InterruptionStatus*& refThreadLocalInterruptionStatus() +{ + static ZEN_THREAD_LOCAL_SPECIFIER InterruptionStatus* threadLocalInterruptionStatus = nullptr; + return threadLocalInterruptionStatus; +} +} + +//context of worker thread: +inline +void interruptionPoint() //throw ThreadInterruption +{ + assert(impl::refThreadLocalInterruptionStatus()); + if (impl::refThreadLocalInterruptionStatus()) + impl::refThreadLocalInterruptionStatus()->checkInterruption(); //throw ThreadInterruption +} + + +//context of worker thread: +template<class Predicate> inline +void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption +{ + assert(impl::refThreadLocalInterruptionStatus()); + if (impl::refThreadLocalInterruptionStatus()) + impl::refThreadLocalInterruptionStatus()->interruptibleWait(cv, lock, pred); + else + cv.wait(lock, pred); +} + +//context of worker thread: +template <class Rep, class Period> inline +void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption +{ + assert(impl::refThreadLocalInterruptionStatus()); + if (impl::refThreadLocalInterruptionStatus()) + impl::refThreadLocalInterruptionStatus()->interruptibleSleep(relTime); + else + std::this_thread::sleep_for(relTime); +} + + +template <class Function> inline +InterruptibleThread::InterruptibleThread(Function f) : intStatus_(std::make_shared<InterruptionStatus>()) +{ + std::promise<void> pFinished; + threadCompleted = pFinished.get_future(); + + stdThread = std::thread([f = std::move(f), + intStatus = this->intStatus_, + pFinished = std::move(pFinished)]() mutable + { + assert(!impl::refThreadLocalInterruptionStatus()); + impl::refThreadLocalInterruptionStatus() = intStatus.get(); + ZEN_ON_SCOPE_EXIT(impl::refThreadLocalInterruptionStatus() = nullptr); + ZEN_ON_SCOPE_EXIT(pFinished.set_value()); + + try + { + f(); //throw ThreadInterruption + } + catch (ThreadInterruption&) {} + }); +} + + +inline +void InterruptibleThread::interrupt() { intStatus_->interrupt(); } } -#endif //BOOST_THREAD_WRAP_H_78963234 +#endif //STD_THREAD_WRAP_H_7896323423432 diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 8dcd4736..e2a756e6 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -43,22 +43,12 @@ time per call | function #ifdef ZEN_WIN namespace { -//warning: LOCALE_INVARIANT is NOT available with Windows 2000, so we have to make yet another distinction... -const LCID ZSTRING_INVARIANT_LOCALE = zen::winXpOrLater() ? - LOCALE_INVARIANT : - MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); //see: http://msdn.microsoft.com/en-us/goglobal/bb688122.aspx - //try to call "CompareStringOrdinal" for low-level string comparison: unfortunately available not before Windows Vista! //by a factor ~3 faster than old string comparison using "LCMapString" typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1, int cchCount1, LPCWSTR lpString2, int cchCount2, BOOL bIgnoreCase); const SysDllFun<CompareStringOrdinalFunc> compareStringOrdinal = SysDllFun<CompareStringOrdinalFunc>(L"kernel32.dll", "CompareStringOrdinal"); //watch for dependencies in global namespace!!! -//caveat: function scope static initialization is not thread-safe in VS 2010! -#if defined _MSC_VER && _MSC_VER > 1800 - #error not true anymore -#endif - } @@ -92,7 +82,7 @@ int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen auto copyToUpperCase = [&](const wchar_t* strIn, wchar_t* strOut) { //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString() - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale, + if (::LCMapString(LOCALE_INVARIANT, //__in LCID Locale, LCMAP_UPPERCASE, //__in DWORD dwMapFlags, strIn, //__in LPCTSTR lpSrcStr, static_cast<int>(minSize), //__in int cchSrc, @@ -138,13 +128,15 @@ Zstring makeUpperCopy(const Zstring& str) Zstring output; output.resize(len); + //LOCALE_INVARIANT is NOT available with Windows 2000 -> ok + //use Windows' upper case conversion: faster than ::CharUpper() - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale, - LCMAP_UPPERCASE, //__in DWORD dwMapFlags, - str.c_str(), //__in LPCTSTR lpSrcStr, - len, //__in int cchSrc, - &*output.begin(), //__out LPTSTR lpDestStr, - len) == 0) //__in int cchDest + if (::LCMapString(LOCALE_INVARIANT, //__in LCID Locale, + LCMAP_UPPERCASE, //__in DWORD dwMapFlags, + str.c_str(), //__in LPCTSTR lpSrcStr, + len, //__in int cchSrc, + &*output.begin(), //__out LPTSTR lpDestStr, + len) == 0) //__in int cchDest throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); return output; |