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