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