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 javafx.application.Platform;
24 import javafx.collections.FXCollections;
25 import javafx.collections.ObservableList;
26
27 import de.spiritscorp.datasync.gui.DialogService;
28 import de.spiritscorp.datasync.gui.Gui;
29 import de.spiritscorp.datasync.gui.NotifyStatus;
30 import de.spiritscorp.datasync.io.Debug;
31 import de.spiritscorp.datasync.io.Logger;
32 import de.spiritscorp.datasync.io.Preference;
33 import de.spiritscorp.datasync.io.PreferenceManager;
34 import de.spiritscorp.datasync.theme.AppTheme;
35
36
37
38
39
40
41
42
43
44
45
46
47 public class MainViewController implements ViewController {
48
49
50 public static final int BG_TIMEOUT = 20_000;
51
52 public static final int EXIT_TIMEOUT = 10_000;
53
54
55 private final Gui gui;
56
57 private final DialogService dialogService;
58
59 private final SyncJobService helper;
60
61 private final PreferenceManager manager;
62
63 private BgController bgController;
64
65
66
67
68
69
70 public MainViewController( final Gui gui ) {
71 this(
72 gui,
73 new DialogService( gui.getWindowStage() ) );
74 }
75
76
77
78
79
80
81
82 MainViewController( final Gui gui, final DialogService dialogService ) {
83 this(
84 gui,
85 new SyncJobService( dialogService, new LogFormatter() ),
86 PreferenceManager.getInstance(),
87 dialogService );
88 loadInitialJobList();
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102 MainViewController( final Gui gui, final SyncJobService helper, final PreferenceManager manager, final DialogService dialogService ) {
103 this.dialogService = dialogService;
104 this.gui = gui;
105 this.helper = helper;
106 this.manager = manager;
107 }
108
109 @SuppressWarnings( { "java:S106" } )
110 @Override
111 public void registerNativeShutdownHook() {
112 Runtime.getRuntime().addShutdownHook( new Thread( () -> {
113
114 Debug.printDebug( "[Exit] Host operating system shutdown signal intercepted via native runtime hook." );
115
116 executeShutdown( false );
117 Debug.printDebug( "[Exit] BYE, BYE" );
118 System.out.flush();
119 System.err.flush();
120 }, "DataSync-OS-Shutdown-Hook-Thread" ) );
121 Debug.printDebug( "[Info] Native OS runtime shutdown hook successfully registered." );
122 }
123
124 @Override
125 public void handleApplicationShutdown() {
126 if( dialogService.promptOkChancel( "Programm beenden", "Möchten sie DataSync wirklich schließen?", "Aktive Hintergrunddienste werden wenn möglich sauber beendet." ) ) {
127 Debug.printDebug( "[Exit] Complete system teardown triggered manually via user confirmation." );
128
129
130 Platform.exit();
131
132 Debug.printDebug( "[Exit] Manual graceful teardown completed. Evicting core JVM runtime context loop." );
133 System.exit( 0 );
134 }
135 }
136
137 @Override
138 public void runInBackground( final boolean firstStart ) {
139 bgController = new BgController( gui, this, gui.getJobList(), new Logger() );
140 bgController.startBgJob( firstStart );
141 }
142
143 @Override
144 public void handleAutostart( final boolean autostart ) {
145 manager.setGlobalAutoStart( autostart );
146 if( helper.setOSAutostart( autostart ) && manager.saveAllPreferences() ) {
147 Debug.printDebug( "[Settings] OS desktop autostart hooks successfully synchronized to state: " + autostart );
148 }else {
149 Debug.printDebug( "[Settings] Warning: Failed to apply host operating system autostart modifications." );
150 }
151 }
152
153 @Override
154 public void handleNavigate( final Gui.ViewState state ) {
155 gui.setViewState( state );
156 if( !gui.getWindowStage().isShowing() ) {
157 gui.getWindowStage().show();
158 gui.getWindowStage().toFront();
159 }
160 }
161
162 @Override
163 public void handleCreateNewJob() {
164 final String name = "Sync Job " + ( gui.getJobList().size() + 1 );
165 gui.getJobList().add( new SyncJobContext( name, manager.createProfile( name, true ) ) );
166 gui.showStatusNotification( name + " wurde erstellt und gespeichert", NotifyStatus.SUCCESS, Gui.INFO_DELAY );
167 }
168
169 @Override
170 public void handleRenameJob( final SyncJobContext job ) {
171 if( job != null ) {
172 final String oldName = job.getJobName();
173 final String newName = dialogService.promptTextInput( "Task umbenennen: " + oldName, "Geben Sie einen neuen Namen für den Task ein", "Neuer Name:" );
174
175 if( !newName.isBlank() && !newName.equals( oldName ) && manager.getProfile( newName ) == null ) {
176 manager.renameProfile( oldName, newName, job.getPreference() );
177 job.setJobName( newName );
178 gui.showStatusNotification( oldName + " wurde ersetzt und gespeichert durch " + newName, NotifyStatus.SUCCESS, Gui.INFO_DELAY );
179 }else {
180 gui.showStatusNotification( oldName + " wurde nicht ersetzt", NotifyStatus.WARNING, Gui.INFO_DELAY );
181 }
182 }
183 }
184
185 @Override
186 public void handleDuplicateJob( final SyncJobContext job ) {
187 if( job != null ) {
188 final String newName = job.getJobName() + " (Kopie)";
189 final Preference pref = job.getPreference();
190 gui.getJobList().addLast( new SyncJobContext( newName, manager.setNewProfile( newName, pref ) ) );
191 gui.showStatusNotification( newName + " wurde erstellt und gespeichert", NotifyStatus.SUCCESS, Gui.INFO_DELAY );
192 }else {
193 gui.showStatusNotification( "Fehler: Job ist unbekannt", NotifyStatus.ERROR, Gui.INFO_DELAY );
194 }
195 }
196
197 @Override
198 public void deleteSelectedDuplicates( final SyncJobContext jobContext ) {
199 helper.deleteSelectedDuplicates( jobContext );
200 }
201
202 @Override
203 public void handleDeleteJob( final SyncJobContext job ) {
204 if( job != null ) {
205 final String jobName = job.getJobName();
206 if( dialogService.promptYesNo( "Task entfernen", null, "Task '" + job.getJobName() + "' wirklich unwiderruflich löschen?" ) ) {
207 gui.getJobList().remove( job );
208 manager.removeProfile( jobName );
209 gui.showStatusNotification( jobName + " wurde erfolgreich gelöscht", NotifyStatus.SUCCESS, Gui.INFO_DELAY );
210 }else {
211 gui.showStatusNotification( jobName + " wurde nicht gelöscht", NotifyStatus.WARNING, Gui.INFO_DELAY );
212 }
213 }
214 }
215
216 @Override
217 public void handleDragJob( final int newIdx, final int draggedIdx ) {
218 final SyncJobContext item = gui.getJobList().remove( draggedIdx );
219 if( item != null ) {
220 gui.getJobList().add( newIdx, item );
221 manager.moveProfile( newIdx, draggedIdx, item.getPreference() );
222 }
223 }
224
225 @Override
226 public void handleExecuteTask( final SyncJobContext job ) {
227 if( job != null ) {
228 switch( job.getSelectedMode() ) {
229 case SYNCHRONIZE -> helper.startSynchronize( job );
230 case DUBLICATE_SCAN -> helper.startDuplicateScan( job );
231 case DEEP_SCAN, FLAT_SCAN -> helper.startBackup( job );
232 default -> throw new IllegalArgumentException( "Unexpected value: " + job.getSelectedMode() );
233 }
234 }
235 }
236
237 @Override
238 public void handleStopTask( final SyncJobContext job ) {
239 job.cancelRunningTask( EXIT_TIMEOUT );
240 }
241
242 @Override
243 public void handleSaveSettings( final AppTheme targetTheme ) {
244 gui.changeTheme( targetTheme );
245 gui.setViewState( Gui.ViewState.SETTINGS );
246
247
248 if( manager.saveAllPreferences() ) {
249 gui.showStatusNotification( "Die Einstellungen wurden erfolgreich in der Konfiguration gespeichert.", NotifyStatus.SUCCESS, Gui.INFO_DELAY );
250 Debug.printDebug( "[Settings] Configuration profile assets successfully serialized to disk." );
251 }else {
252 gui.showStatusNotification( "Fehler: Die Konfigurationsdaten konnten nicht in 'conf.json' geschrieben werden.", NotifyStatus.ERROR, Gui.INFO_DELAY );
253 Debug.printDebug( "[Settings Error] Critical: Failed to persist configuration profile assets." );
254 }
255 }
256
257
258
259
260
261
262
263
264 private void executeShutdown( final boolean gracePeriod ) {
265 final long timeout = gracePeriod ? BG_TIMEOUT : EXIT_TIMEOUT;
266 Debug.printDebug( "[Exit] Internal system teardown invoked. Dynamic grace mode: %b -> %d ms", gracePeriod, timeout );
267
268 for( final SyncJobContext job : gui.getJobList() ) {
269 job.cancelRunningTask( timeout );
270 }
271 if( !gui.isShowing() && bgController != null ) bgController.interruptBgJob( timeout );
272 Debug.printDebug( "[Exit] Core teardown protocol finalized. Flushing runtime buffers." );
273 }
274
275
276
277
278
279
280
281
282
283
284
285 private void loadInitialJobList() {
286 final ObservableList<SyncJobContext> jobList = FXCollections.observableArrayList();
287
288 if( manager.loadAllPreferences() ) {
289 manager.getLoadedProfiles().stream()
290 .map( entry -> {
291 final SyncJobContext ctx = new SyncJobContext( entry.getJobName(), entry );
292 ctx.setSelectedMode( entry.getScanMode().getDescription() );
293 return ctx;
294 } )
295 .forEach( jobList::add );
296 }else {
297 jobList.add( new SyncJobContext( "NAS Dokumente", manager.createProfile( "NAS Dokumente", false ) ) );
298 jobList.add( new SyncJobContext( "Lokales Workspace Backup", manager.createProfile( "Lokales Workspace Backup", false ) ) );
299 }
300 gui.setInitialJobConfigurations( jobList );
301 }
302 }