1 package de.spiritscorp.datasync.controller;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import java.io.BufferedWriter;
24 import java.io.IOException;
25 import java.io.OutputStreamWriter;
26 import java.nio.charset.StandardCharsets;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34
35 import javafx.application.Platform;
36
37 import de.spiritscorp.datasync.CLIFlags;
38 import de.spiritscorp.datasync.Main;
39 import de.spiritscorp.datasync.gui.DialogService;
40 import de.spiritscorp.datasync.io.Debug;
41 import de.spiritscorp.datasync.io.Logger;
42 import de.spiritscorp.datasync.io.Preference;
43 import de.spiritscorp.datasync.io.PreferenceManager;
44 import de.spiritscorp.datasync.model.FileAttributes;
45 import de.spiritscorp.datasync.model.Model;
46
47
48
49
50
51
52
53 public class SyncJobService {
54
55
56
57
58 private final DialogService dialogService;
59
60
61
62
63 private final LogFormatter logFormatter;
64
65
66
67
68
69
70
71 public SyncJobService( final DialogService dialogService, final LogFormatter logFormatter ) {
72 this.dialogService = dialogService;
73 this.logFormatter = logFormatter;
74 }
75
76
77
78
79
80
81 public void startSynchronize( final SyncJobContext context ) {
82 if( context.isRunning() ) return;
83
84 context.setRunning( true );
85 context.setStatusMessage( "Synchronisation gestartet. Scanne Verzeichnisse..." );
86 context.clearLog();
87
88 final Map<Path, FileAttributes> sourceMap = Model.createMap();
89 final Map<Path, FileAttributes> destMap = Model.createMap();
90 final Map<Path, FileAttributes> failMap = Model.createMap();
91 final Preference pref = context.getPreference();
92 final Model model = new Model( new Logger(), sourceMap, destMap );
93 final Long[] stats = new Long[4];
94
95 final Thread worker = new Thread( () -> {
96 long startTime = System.nanoTime();
97 try {
98 final Path startDestPath = pref.getDestPaths().get( 0 );
99 final Path startSourcePath = pref.getSourcePaths().get( 0 );
100
101 if( startDestPath == null || !Files.exists( startDestPath ) ) {
102 updateUIStatus( context, false, "Kein Ziellaufwerk vorhanden" );
103 return;
104 }
105
106 if( pref.getSourcePaths().size() > 1 ) {
107 updateUIStatus( context, false, "Die Synchronisierung funktioniert nur mit einem Quellordner!" );
108 return;
109 }
110
111 failMap.putAll( model.scanSyncFiles( pref.getSourcePaths(), pref.getDestPaths(), stats, pref.getScanMode(), false, false ) );
112 if( Thread.currentThread().isInterrupted() ) throw new InterruptedException();
113
114 final ArrayList<Map<Path, FileAttributes>> result = model.getSyncFiles( pref.getSyncMap(), startSourcePath, startDestPath );
115 final String scanTimeFormatted = logFormatter.getTimeFormatted( System.nanoTime() - startTime ) + " Laufzeit für das Scannen";
116
117 if( Thread.currentThread().isInterrupted() ) throw new InterruptedException();
118
119 appendLogData( context, logFormatter.formatMaps( pref.getScanMode(), result.get( 0 ), result.get( 1 ), result.get( 2 ) ) );
120 appendLogData( context, String.format( "Quelldateien: %d Stück und Zieldateien: %d Stück", stats[0], stats[1] ) );
121 appendLogData( context, String.format( "Zu löschende Dateien: %d", result.get( 2 ).size() ) );
122 appendLogData( context,
123 String.format( "Größe aller Quelldateien: %s | Größe aller Zieldateien: %s", logFormatter.getReadableBytes( stats[2] ), logFormatter.getReadableBytes( stats[3] ) ) );
124 startTime = System.nanoTime();
125 final boolean success = model.syncFiles( context, result, pref.getSyncMap(), startSourcePath, startDestPath, false );
126 final String syncTimeFormatted = logFormatter.getTimeFormatted( System.nanoTime() - startTime ) + " Laufzeit für das Synchronisieren";
127
128 if( success ) {
129 pref.saveLastScanTime();
130 appendLogData( context, scanTimeFormatted );
131 appendLogData( context, syncTimeFormatted );
132 updateUIStatus( context, false, "Synchronisation erfolgreich!" );
133 Debug.printDebug( "[Controller Helper] Synchronization completed successfully for profile: %s", context.getJobName() );
134 }else {
135 updateUIStatus( context, false, "Synchronisation fehlgeschlagen!" );
136 Debug.printDebug( "[Controller Helper Error] Synchronization routine failed for: %s", context.getJobName() );
137 }
138 Debug.printDebug( "[Controller Helper] Scan Time -> " + scanTimeFormatted );
139 Debug.printDebug( "[Controller Helper] Sync Time -> " + syncTimeFormatted );
140
141 }catch( final InterruptedException e ) {
142 updateUIStatus( context, false, "Synchronisation abgebrochen." );
143 Debug.printDebug( "[Controller Helper Error] Synchronization routine aborted due to interruption: %s", e.getMessage() );
144 Debug.printException( this.getClass(), e );
145 }catch( final Exception e ) {
146 updateUIStatus( context, false, "Fehler: " + e.getMessage() );
147 Debug.printDebug( "[Controller Helper Error] Synchronization routine failed: %s", e.getMessage() );
148 Debug.printException( this.getClass(), e );
149 }
150 context.setRunning( false );
151 } );
152
153 worker.setDaemon( true );
154 context.setActiveWorkerThread( worker );
155 worker.start();
156 }
157
158
159
160
161
162
163 public void startBackup( final SyncJobContext context ) {
164 if( context.isRunning() ) return;
165
166 context.setRunning( true );
167 context.setStatusMessage( "Backup gestartet. Analysiere geänderte Daten..." );
168 context.clearLog();
169
170 final Map<Path, FileAttributes> sourceMap = Model.createMap();
171 final Map<Path, FileAttributes> destMap = Model.createMap();
172 final Map<Path, FileAttributes> failMap = Model.createMap();
173 final Preference pref = context.getPreference();
174 final Model model = new Model( new Logger(), sourceMap, destMap );
175 final Long[] stats = new Long[4];
176
177 final Thread worker = new Thread( () -> {
178 long startTime = System.nanoTime();
179 try {
180 final Path startDestPath = pref.getDestPaths().get( 0 );
181 if( startDestPath == null || !Files.exists( startDestPath ) ) {
182 updateUIStatus( context, false, "Kein Ziellaufwerk vorhanden" );
183 return;
184 }
185
186 failMap.putAll( model.scanSyncFiles( pref.getSourcePaths(), pref.getDestPaths(), stats, pref.getScanMode(), pref.isSubDir(), pref.isTrashbin() ) );
187 model.compareEqualsFiles();
188 final String scanTimeFormatted = logFormatter.getTimeFormatted( System.nanoTime() - startTime ) + " Laufzeit für das Scannen";
189 Debug.printDebug( "[Controller Helper] sourceMap size = %d, destMap size = %d, failtures = %d", stats[0], stats[1], failMap.size() );
190
191 if( Thread.currentThread().isInterrupted() ) throw new InterruptedException();
192
193 appendLogData( context, logFormatter.formatMaps( pref.getScanMode(), sourceMap, destMap, failMap ) );
194 appendLogData( context, String.format( "Quelldateien: %d Stück und Zieldateien: %d Stück", stats[0], stats[1] ) );
195 appendLogData( context,
196 String.format( "Größe aller Quelldateien: %s | Größe aller Zieldateien: %s", logFormatter.getReadableBytes( stats[2] ), logFormatter.getReadableBytes( stats[3] ) ) );
197 appendLogData( context, String.format( "Fehlerhafter Zugriff: %d", failMap.size() ) );
198
199 boolean success = false;
200 String backupTimeFormatted = "";
201
202 boolean delete = true;
203 if( !pref.isAutoDel() ) {
204 delete = dialogService.promptYesNo( "Dateien löschen", "Löschen bestätigen?", "Alle gelöschten Dateien auch im Zielverzeichnis löschen?" );
205 }
206
207 if( pref.isAutoSync() || dialogService.promptYesNo( "Dateien sichern", "Kopieren bestätigen?", "Alle neuen Dateien in das Zielverzeichnis kopieren?" ) ) {
208 startTime = System.nanoTime();
209 success = model.backupFiles( delete, pref.isLogOn(), startDestPath, pref.isTrashbin(), pref.getTrashbinPath() );
210 backupTimeFormatted = logFormatter.getTimeFormatted( System.nanoTime() - startTime ) + " Laufzeit für das Synchronisieren";
211 }
212
213 if( success ) {
214 pref.saveLastScanTime();
215 appendLogData( context, scanTimeFormatted );
216 appendLogData( context, backupTimeFormatted );
217 updateUIStatus( context, false, "Backup erfolgreich abgeschlossen!" );
218 Debug.printDebug( "[Controller Helper] Backup completed successfully for profile: %s", context.getJobName() );
219 }else {
220 updateUIStatus( context, false, "Backup fehlgeschlagen!" );
221 Debug.printDebug( "[Controller Helper Error] Backup routine failed in: %s", context.getJobName() );
222 }
223 Debug.printDebug( "[Controller Helper] Scan Time -> " + scanTimeFormatted );
224 Debug.printDebug( "[Controller Helper] Backup Time -> " + backupTimeFormatted );
225 }catch( final InterruptedException e ) {
226 updateUIStatus( context, false, "Backup-Vorgang abgebrochen." );
227 Debug.printDebug( "[Controller Helper Error] Backup routine aborted due to interruption: %s", e.getMessage() );
228 Debug.printException( this.getClass(), e );
229 }catch( final Exception e ) {
230 updateUIStatus( context, false, "Fehler während des Backups: " + e.getMessage() );
231 Debug.printDebug( "[Controller Helper Error] Backup routine failed: %s", e.getMessage() );
232 Debug.printException( this.getClass(), e );
233 }
234 context.setRunning( false );
235 } );
236
237 worker.setDaemon( true );
238 context.setActiveWorkerThread( worker );
239 worker.start();
240 }
241
242
243
244
245
246
247 public void startDuplicateScan( final SyncJobContext job ) {
248 if( job.isRunning() ) return;
249
250 job.setRunning( true );
251 job.setStatusMessage( "Scanne nach Duplikaten..." );
252 job.clearLog();
253
254 final Map<Path, FileAttributes> sourceMap = Model.createMap();
255 final Map<Path, FileAttributes> destMap = Model.createMap();
256 final Long[] stats = new Long[4];
257
258 final Preference pref = job.getPreference();
259 final Model model = new Model( new Logger(), sourceMap, destMap );
260
261 final Thread worker = new Thread( () -> {
262 final long startTime = System.nanoTime();
263 try {
264 final Map<Path, FileAttributes> duplicateMap = model.scanDublicates( pref.getSourcePaths(), stats );
265 final String scanTimeFormatted = logFormatter.getTimeFormatted( System.nanoTime() - startTime ) + "Laufzeit ";
266
267 if( Thread.currentThread().isInterrupted() ) throw new InterruptedException( "Manual abort ..." );
268
269 final List<SyncJobContext.FileRow> preparedRows = new ArrayList<>();
270 if( duplicateMap != null && !duplicateMap.isEmpty() ) {
271 for( final Map.Entry<Path, FileAttributes> entry : duplicateMap.entrySet() ) {
272 preparedRows.add( new SyncJobContext.FileRow(
273 entry.getKey(),
274 entry.getValue(),
275 logFormatter.getReadableBytes( entry.getValue().getSize() ) ) );
276 }
277 }
278
279 Platform.runLater( () -> {
280 job.getDuplicateFiles().clear();
281 job.getDuplicateFiles().addAll( preparedRows );
282 job.setRunning( false );
283 job.setStatusMessage( "Scan abgeschlossen. Duplikate insgesamt gefunden: " + job.getDuplicateFiles().size() + " (" + scanTimeFormatted + "s)" );
284 Debug.printDebug( "[Controller Helper] duplicateMap size = %d, sourceMap size = %d, failtures = %d", duplicateMap.size(), stats[0], stats[1] );
285 Debug.printDebug( "[Controller Helper] Scan Time -> " + scanTimeFormatted );
286 } );
287 }catch( final InterruptedException e ) {
288 updateUIStatus( job, false, "Scan abgebrochen." );
289 Debug.printDebug( "[Controller Helper Error] Duplicate scan aborted due to interruption: %s", e.getMessage() );
290 }catch( final Exception e ) {
291 updateUIStatus( job, false, "Fehler beim Duplikat Scan: " + e.getMessage() );
292 Debug.printDebug( "[Controller Helper Error] Duplicat scan abord: %s", e.getMessage() );
293 Debug.printException( this.getClass(), e );
294 }
295 job.setRunning( false );
296 } );
297
298 worker.setDaemon( true );
299 job.setActiveWorkerThread( worker );
300 worker.start();
301 }
302
303
304
305
306
307
308 public void deleteSelectedDuplicates( final SyncJobContext context ) {
309 final ArrayList<SyncJobContext.FileRow> toDelete = new ArrayList<>();
310 for( final SyncJobContext.FileRow row : context.getDuplicateFiles() ) {
311 if( row.isSelected() ) {
312 toDelete.add( row );
313 }
314 }
315
316 if( toDelete.isEmpty() ) {
317 context.setStatusMessage( "Keine Dateien zum Löschen ausgewählt." );
318 return;
319 }
320 if( !dialogService.promptYesNo( "Duplikate entfernen", "Löschen bestätigen?", "Alle ausgewählten Dateien wirklich löschen?" ) ) return;
321
322 context.setRunning( true );
323 context.setStatusMessage( "Lösche ausgewählte Duplikate..." );
324
325 final Thread worker = new Thread( () -> {
326 int successCount = 0;
327 try {
328 for( final SyncJobContext.FileRow row : toDelete ) {
329 if( Thread.currentThread().isInterrupted() ) throw new InterruptedException();
330 if( Files.deleteIfExists( row.getFileSystemPath() ) ) {
331 successCount++;
332 }
333 }
334 final int finalSuccess = successCount;
335 Platform.runLater( () -> {
336 context.getDuplicateFiles().removeIf( SyncJobContext.FileRow::isSelected );
337 context.setRunning( false );
338 context.setStatusMessage( finalSuccess + " Duplikate erfolgreich gelöscht." );
339 Debug.printDebug( "[Controller Helper] Duplicates successfully deleted: %d items.", finalSuccess );
340 } );
341 }catch( final InterruptedException e ) {
342 updateUIStatus( context, false, "Löschvorgang unterbrochen." );
343 Debug.printDebug( "[Controller Helper Error] Duplicate deletion aborted: %s", e.getMessage() );
344 Debug.printException( this.getClass(), e );
345 Thread.currentThread().interrupt();
346 }catch( final Exception e ) {
347 updateUIStatus( context, false, "Fehler beim Löschen: " + e.getMessage() );
348 Debug.printDebug( "[Controller Helper Error] Duplicate deletion failed: %s", e.getMessage() );
349 Debug.printException( this.getClass(), e );
350 }
351 } );
352
353 worker.setDaemon( true );
354 context.setActiveWorkerThread( worker );
355 worker.start();
356 }
357
358
359
360
361
362
363
364
365
366 public boolean setOSAutostart( final boolean set ) {
367 final String javaPath = System.getProperty( "sun.boot.library.path" );
368 final String exePath = System.getProperty( "jpackage.app-path" );
369 final String datei = System.getProperty( "sun.java.command" );
370 final String fullPath = Paths.get( "" ).toAbsolutePath().toString() + System.getProperty( "file.separator" ) + datei;
371 final String possibleOS = System.getProperty( "os.name" );
372 String operatingSystem = "";
373 if( possibleOS != null ) operatingSystem = possibleOS.toLowerCase( Locale.ROOT );
374 final String flags = computeBootFlags();
375
376 if( operatingSystem.contains( "win" ) ) {
377 final String regCmd = "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
378 try {
379 if( set ) {
380
381 final String dataPayload = ( exePath == null )
382 ? String.format( "%s\\javaw.exe -Xmx200m -jar %s %s %s", javaPath, datei, CLIFlags.BOOT_DELAY.getLongFlag(), flags )
383 : String.format( "%s %s %s", exePath, CLIFlags.BOOT_DELAY.getLongFlag(), flags );
384
385 final ProcessBuilder pb = new ProcessBuilder( "reg", "add", regCmd, "/v", "DataSync", "/t", "REG_SZ", "/d", dataPayload, "/f" );
386 pb.start();
387 }else {
388 final ProcessBuilder pb = new ProcessBuilder( "reg", "delete", regCmd, "/v", "DataSync", "/f" );
389 pb.start();
390 }
391 }catch( final IOException e ) {
392 Debug.printError( "[Controller Helper Error] Set Windows registry autostart tracking hive failed: %s", e.getMessage() );
393 Debug.printException( getClass(), e );
394 return false;
395 }
396 }else if( operatingSystem.contains( "nix" ) || operatingSystem.contains( "aix" ) || operatingSystem.contains( "nux" ) ) {
397 final String crontab = "crontab";
398 try {
399 if( set ) {
400 final String cronPayload = ( exePath == null )
401 ? String.format( "@reboot %s/java -jar %s %s %s", javaPath, fullPath, CLIFlags.BOOT_DELAY.getLongFlag(), flags )
402 : String.format( "@reboot %s %s %s", exePath, CLIFlags.BOOT_DELAY.getLongFlag(), flags );
403
404
405 final ProcessBuilder pb = new ProcessBuilder( crontab, "-" );
406 final Process process = pb.start();
407
408 try( BufferedWriter writer = new BufferedWriter(
409 new OutputStreamWriter( process.getOutputStream(), StandardCharsets.UTF_8 ) ) ) {
410 writer.write( cronPayload );
411 writer.newLine();
412 }
413 process.waitFor();
414 }else {
415 final ProcessBuilder pb = new ProcessBuilder( crontab, "-r" );
416 pb.start().waitFor();
417 }
418 }catch( final IOException | InterruptedException e ) {
419 Debug.printError( "[Controller Helper Error] Set Unix crontab daemon automated launch failed: %s", e.getMessage() );
420 Debug.printException( getClass(), e );
421 if( e instanceof InterruptedException ) {
422 Thread.currentThread().interrupt();
423 }
424 return false;
425 }
426 }
427 return true;
428 }
429
430 private String computeBootFlags() {
431 final StringBuilder stringBuilder = new StringBuilder();
432 final PreferenceManager manager = PreferenceManager.getInstance();
433 if( Main.isDebugToFile() ) {
434 stringBuilder.append( String.format( " %s", CLIFlags.DEBUG_TO_FILE.getLongFlag() ) );
435 }
436 if( manager.isCustomConfigDir() ) {
437 stringBuilder.append( String.format( " %s %s", CLIFlags.CONFIG_DIR.getLongFlag(), manager.getConfigPath().getParent().toString() ) );
438 }
439 return stringBuilder.toString();
440 }
441
442 private void updateUIStatus( final SyncJobContext context, final boolean running, final String message ) {
443 Platform.runLater( () -> {
444 context.setRunning( running );
445 context.setStatusMessage( message );
446 } );
447 }
448
449 private void appendLogData( final SyncJobContext context, final String line ) {
450 Platform.runLater( () -> context.appendLog( line ) );
451 }
452 }