View Javadoc
1   package de.spiritscorp.datasync.controller;
2   
3   /*-
4    * 		Data Sync
5    *
6    * 		Copyright ©   2022    The Spirit
7    * 		@email                        thespirit@spiritscorp.network
8    *
9    * 		This program is free software; you can redistribute it and/or modify
10   * 		it under the terms of the GNU General Public License as published by
11   * 		the Free Software Foundation; either version 3 of the License, or
12   * 		(at your option) any later version.
13   *
14   * 		This program is distributed in the hope that it will be useful,
15   * 		but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * 		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17   * 		See the GNU General Public License for more details.
18   *
19   * 		You should have received a copy of the GNU General Public License
20   * 		along with this program. If not, see <http://www.gnu.org/licenses/>.
21   */
22  
23  import java.nio.file.Path;
24  
25  import de.spiritscorp.datasync.ScanType;
26  import de.spiritscorp.datasync.io.Debug;
27  import de.spiritscorp.datasync.io.Preference;
28  import de.spiritscorp.datasync.io.PreferenceManager;
29  import de.spiritscorp.datasync.model.FileAttributes;
30  import javafx.application.Platform;
31  import javafx.beans.property.BooleanProperty;
32  import javafx.beans.property.SimpleBooleanProperty;
33  import javafx.beans.property.SimpleStringProperty;
34  import javafx.beans.property.StringProperty;
35  import javafx.collections.FXCollections;
36  import javafx.collections.ObservableList;
37  
38  /**
39   * Manages the reactive runtime context for an individual synchronization or backup task.
40   * Holds task-specific properties, isolated file tables, and active worker thread references.
41   * * @author Tom Spirit
42   */
43  public class SyncJobContext {
44  
45  	private final StringProperty jobName = new SimpleStringProperty();
46  	private final BooleanProperty running = new SimpleBooleanProperty( false );
47  	private final StringProperty statusMessage = new SimpleStringProperty( "Bereit" );
48  	private final StringProperty logOutput = new SimpleStringProperty( "" );
49  	private final StringProperty selectedMode = new SimpleStringProperty( ScanType.SYNCHRONIZE.getDescription() );
50  
51  	private final Preference taskPreference;
52  	private Thread activeWorkerThread;
53  
54  	private final ObservableList<FileRow> duplicateFiles = FXCollections.observableArrayList();
55  
56  	/**
57  	 * Creates a new isolated synchronization job environment with its own preference clone.
58  	 * * @param name The identification name for the user interface sidebar
59  	 *
60  	 * @param taskPreference The template preference instance to derive task-specific settings from
61  	 */
62  	public SyncJobContext( String name, Preference taskPreference ) {
63  		this.jobName.set( name );
64  		this.taskPreference = taskPreference;
65  	}
66  
67  	/**
68  	 * Assigns the thread processing file modifications to allow secure termination handles.
69  	 * * @param thread The execution context running background tasks
70  	 */
71  	synchronized void setActiveWorkerThread( Thread thread ) { this.activeWorkerThread = thread; }
72  
73  	/**
74  	 * Signals the underlying worker thread to terminate via standard interruption flags.
75  	 * If a timeout greater than zero is specified, this method blocks the invoking context
76  	 * to await a graceful structural thread finalization.
77  	 *
78  	 * @param timeoutMs Maximum duration in milliseconds to await thread join; 0 executes asynchronously.
79  	 */
80  	public synchronized void cancelRunningTask( long timeoutMs ) {
81  		if( activeWorkerThread != null && activeWorkerThread.isAlive() ) {
82  			Debug.printDebug( "[Info] Sending interruption signal to worker thread for job: %s", getJobName() );
83  			activeWorkerThread.interrupt();
84  
85  			if( timeoutMs > 0 ) {
86  				try {
87  					// Gracefully await the thread to flush buffers and exit its iteration loops
88  					activeWorkerThread.join( timeoutMs );
89  					if( activeWorkerThread.isAlive() ) {
90  						Debug.printDebug( "[Warn] Warning: Worker thread for job '%s' breached timeout matrix.", getJobName() );
91  					}
92  				}catch( final InterruptedException e ) {
93  					Debug.printDebug( "[Info] Thread joining sequence was interrupted for job: %s", getJobName() );
94  					Thread.currentThread().interrupt();
95  				}
96  			}
97  
98  			// If it was cleared or joined successfully, adjust states safely
99  			if( !activeWorkerThread.isAlive() ) {
100 				setRunning( false );
101 				setStatusMessage( "Aktion erfolgreich beendet." );
102 				appendLog( "-> Vorgang sauber beendet." );
103 			}else {
104 				setRunning( false );
105 				setStatusMessage( "Aktion vom Benutzer abgebrochen (Forced)." );
106 				appendLog( "-> Vorgang erzwungen abgebrochen." );
107 			}
108 		}else {
109 			updateUIAndLog( "Keine aktive Aktion.", "-> Nichts zu beenden gefunden." );
110 		}
111 	}
112 
113 	/**
114 	 * Helper method to safely update UI properties and internal log feeds across thread boundaries.
115 	 */
116 	private void updateUIAndLog( String status, String logEntry ) {
117 		if( Platform.isFxApplicationThread() ) {
118 			setStatusMessage( status );
119 			appendLog( logEntry );
120 		}else {
121 			try {
122 				Platform.runLater( () -> {
123 					setStatusMessage( status );
124 					appendLog( logEntry );
125 				} );
126 			}catch( final IllegalStateException e ) {
127 				// Caught if the JavaFX toolkit is already dead during a hard native OS shutdown.
128 				// We log the text purely to the background core debug stream.
129 				Debug.printDebug( "[Info] GUI framework offline. Suppressed state update: %s (%s)", status, logEntry );
130 			}
131 		}
132 	}
133 
134 	public void appendLog( String line ) {
135 		this.logOutput.set( this.logOutput.get() + line + System.lineSeparator() );
136 	}
137 
138 	void clearLog() {
139 		this.logOutput.set( "" );
140 	}
141 
142 	public String getJobName() { return jobName.get(); }
143 
144 	public StringProperty jobNameProperty() {
145 		return jobName;
146 	}
147 
148 	public boolean isRunning() { return running.get(); }
149 
150 	public BooleanProperty runningProperty() {
151 		return running;
152 	}
153 
154 	void setRunning( boolean value ) {
155 		this.running.set( value );
156 	}
157 
158 	public String getStatusMessage() { return statusMessage.get(); }
159 
160 	public StringProperty statusMessageProperty() {
161 		return statusMessage;
162 	}
163 
164 	public void setStatusMessage( String message ) {
165 		this.statusMessage.set( message );
166 	}
167 
168 	public String getLogOutput() { return logOutput.get(); }
169 
170 	public StringProperty logOutputProperty() {
171 		return logOutput;
172 	}
173 
174 	public ScanType getSelectedMode() { return ScanType.get( selectedMode.get() ); }
175 
176 	public StringProperty selectedModeProperty() {
177 		return selectedMode;
178 	}
179 
180 	public void setSelectedMode( String mode ) {
181 		this.selectedMode.set( mode );
182 	}
183 
184 	public Preference getPreference() { return taskPreference; }
185 
186 	public ObservableList<FileRow> getDuplicateFiles() { return duplicateFiles; }
187 
188 	/**
189 	 * Wraps file characteristics inside property types suited for dynamic UI grids.
190 	 */
191 	public static class FileRow {
192 		private final BooleanProperty selected = new SimpleBooleanProperty( false );
193 		private final StringProperty fileName = new SimpleStringProperty();
194 		private final StringProperty size = new SimpleStringProperty();
195 		private final StringProperty hash = new SimpleStringProperty();
196 		private final StringProperty path = new SimpleStringProperty();
197 		private final Path fileSystemPath;
198 
199 		public FileRow( Path path, FileAttributes attr, String readableSize ) {
200 			this.fileSystemPath = path;
201 			this.fileName.set( attr.getFileName() );
202 			this.size.set( readableSize );
203 			this.hash.set( attr.getFileHash() );
204 			this.path.set( path.toString() );
205 		}
206 
207 		public BooleanProperty selectedProperty() {
208 			return selected;
209 		}
210 
211 		public boolean isSelected() { return selected.get(); }
212 
213 		public void setSelected( boolean val ) {
214 			this.selected.set( val );
215 		}
216 
217 		public StringProperty fileNameProperty() {
218 			return fileName;
219 		}
220 
221 		public StringProperty sizeProperty() {
222 			return size;
223 		}
224 
225 		public StringProperty hashProperty() {
226 			return hash;
227 		}
228 
229 		public StringProperty pathProperty() {
230 			return path;
231 		}
232 
233 		public Path getFileSystemPath() { return fileSystemPath; }
234 	}
235 
236 	void setJobName( String newTaskName ) {
237 		PreferenceManager.getInstance().renameProfile( jobName.get(), newTaskName, this.getPreference() );
238 		this.jobName.set( newTaskName );
239 	}
240 }