1 package de.spiritscorp.datasync.io;
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.IOException;
24 import java.lang.reflect.InvocationTargetException;
25 import java.nio.file.Files;
26 import java.nio.file.LinkOption;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.nio.file.StandardOpenOption;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.locks.ReentrantLock;
37
38 import jakarta.json.Json;
39 import jakarta.json.JsonObject;
40 import jakarta.json.JsonObjectBuilder;
41 import jakarta.json.JsonReader;
42 import jakarta.json.JsonWriter;
43 import jakarta.json.JsonWriterFactory;
44 import jakarta.json.stream.JsonGenerator;
45 import jakarta.json.stream.JsonParsingException;
46
47 import de.spiritscorp.datasync.theme.AppTheme;
48 import de.spiritscorp.datasync.theme.DarkSlateTheme;
49
50
51
52
53
54
55
56
57
58 public final class PreferenceManager {
59
60
61 static final Path DATASYNC_HOME = Paths.get( System.getProperty( "user.home" ), "DataSync" );
62
63 private Path rootPath = DATASYNC_HOME;
64
65 private Path configPath = rootPath.resolve( "conf.json" );
66
67 private Path logPath = rootPath.resolve( "log.json" );
68
69 private Path debugPath = rootPath.resolve( "debug.log" );
70
71 private Path errorPath = rootPath.resolve( "debug.err" );
72
73
74 private static final long LOCK_TIME = 1;
75
76
77 private final List<Preference> loadedProfiles = new ArrayList<>();
78
79
80 private static final PreferenceManager INSTANCE = new PreferenceManager();
81
82
83 private final ReentrantLock profileLock = new ReentrantLock();
84
85
86 private boolean globalAutoStart;
87
88
89 private AppTheme theme = new DarkSlateTheme();
90
91
92 private long maxLogSize = 5_000_000L;
93
94
95 private int maxLogCount = 5;
96
97
98 private PreferenceManager() {
99 }
100
101
102
103
104
105
106 public static PreferenceManager getInstance() { return INSTANCE; }
107
108
109
110
111
112
113
114
115
116 public void initGlobalRootConfigPath( final Path customRoot ) {
117 try {
118 if( profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
119 try {
120 if( customRoot != null &&
121 Files.exists( customRoot, LinkOption.NOFOLLOW_LINKS ) &&
122 Files.isDirectory( customRoot, LinkOption.NOFOLLOW_LINKS ) &&
123 Files.isWritable( customRoot ) ) {
124 rootPath = customRoot.toAbsolutePath().normalize();
125 configPath = customRoot.resolve( "conf.json" );
126 logPath = customRoot.resolve( "log.json" );
127 debugPath = customRoot.resolve( "debug.log" );
128 errorPath = customRoot.resolve( "debug.err" );
129 }
130 }finally {
131 profileLock.unlock();
132 }
133 }else {
134 Debug.printError( "[Pref Manager Error] initGlobalRootConfigPath() -> Profiles are allready locked" );
135 }
136 }catch( InterruptedException _ ) {
137 Thread.currentThread().interrupt();
138 }
139 }
140
141
142
143
144
145
146
147
148 public Preference createProfile( final String jobName, final boolean withSave ) {
149 try {
150 if( profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
151 try {
152 if( getProfile( jobName ) == null ) {
153 final Preference pref = Preference.createSinglePreference( jobName );
154 loadedProfiles.add( pref );
155 if( !withSave || saveAllPreferences() )
156 return pref;
157 }
158 }finally {
159 profileLock.unlock();
160 }
161 }else {
162 Debug.printError( "[Pref Manager Error] createProfiles() -> Profiles are allready locked" );
163 }
164 }catch( InterruptedException _ ) {
165 Thread.currentThread().interrupt();
166 }
167 return null;
168 }
169
170
171
172
173
174
175
176 public Preference getProfile( final String jobName ) {
177 for( Preference pref : loadedProfiles ) {
178 if( jobName.equals( pref.getJobName() ) ) { return pref; }
179 }
180 return null;
181 }
182
183
184
185
186
187
188
189
190
191 public Preference setNewProfile( final String jobName, final Preference pref ) {
192 try {
193 if( profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
194 try {
195 if( getProfile( jobName ) == null ) {
196 Preference newPref = Preference.createSinglePreference( jobName );
197 newPref.deserialize( pref.serialize() );
198 newPref.setJobNameFromManager( jobName );
199 loadedProfiles.addLast( newPref );
200 if( saveAllPreferences() ) return newPref;
201 }
202 }catch( ConfigException _ ) {
203
204 }finally {
205
206 profileLock.unlock();
207 }
208 }else {
209 Debug.printError( "[Pref Manager Error] setNewProfile() -> Profiles are allready locked" );
210 }
211 }catch( InterruptedException _ ) {
212
213 Thread.currentThread().interrupt();
214 }
215 return null;
216 }
217
218
219
220
221
222
223
224
225
226
227
228 public boolean moveProfile( final int newIdx, final int draggedIdx, final Preference pref ) {
229 try {
230 if( profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
231 try {
232 if( pref != null && draggedIdx != newIdx ) {
233 loadedProfiles.remove( draggedIdx );
234 loadedProfiles.add( newIdx, pref );
235 return saveAllPreferences();
236 }
237 }finally {
238
239 profileLock.unlock();
240 }
241 }else {
242 Debug.printError( "[Pref Manager Error] setProfile() -> Profiles are allready locked" );
243 }
244 }catch( InterruptedException _ ) {
245
246 Thread.currentThread().interrupt();
247 }
248 return false;
249 }
250
251
252
253
254
255
256
257
258
259
260 public boolean renameProfile( final String oldName, final String newName, final Preference pref ) {
261 try {
262
263 if( profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
264 try {
265
266 if( oldName != null && newName != null && pref != null && !oldName.equals( newName ) ) {
267 pref.setJobNameFromManager( newName );
268 return saveAllPreferences();
269 }
270 }finally {
271
272 profileLock.unlock();
273 }
274 }else {
275 Debug.printError( "[Pref Manager Error] renameProfile() -> Profiles are allready locked" );
276 }
277 }catch( InterruptedException _ ) {
278
279 Thread.currentThread().interrupt();
280 }
281 return false;
282 }
283
284
285
286
287
288
289
290
291 public boolean removeProfile( final String jobName ) {
292 try {
293 if( profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
294 try {
295 Preference pref = getProfile( jobName );
296 if( pref != null ) {
297 pref.removeProfile();
298 loadedProfiles.remove( pref );
299 return saveAllPreferences();
300 }
301 }finally {
302 profileLock.unlock();
303 }
304 }else {
305 Debug.printError( "[Pref Manager Error] removeProfile() -> Profiles are allready locked" );
306 }
307 }catch( InterruptedException _ ) {
308 Thread.currentThread().interrupt();
309 }
310 return false;
311 }
312
313
314
315
316
317
318
319 public List<Preference> getLoadedProfiles() { return Collections.unmodifiableList( loadedProfiles ); }
320
321
322
323
324
325
326
327 public boolean saveAllPreferences() {
328 try {
329 if( profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
330 try {
331 if( !Files.exists( rootPath ) ) {
332 Files.createDirectories( rootPath );
333 }
334
335 final JsonObjectBuilder rootBuilder = Json.createObjectBuilder();
336
337
338 final JsonObject globalDoc = Json.createObjectBuilder()
339 .add( "autoStart", globalAutoStart )
340 .add( "theme", theme.getClass().getName() )
341 .add( "maxLogSize", maxLogSize )
342 .add( "maxLogCount", maxLogCount )
343 .build();
344 rootBuilder.add( "globalSettings", globalDoc );
345
346
347 for( final Preference entry : loadedProfiles ) {
348 rootBuilder.add( entry.getJobName(), entry.serialize() );
349 }
350
351 final Map<String, Object> writerConfig = new HashMap<>();
352 writerConfig.put( JsonGenerator.PRETTY_PRINTING, true );
353 final JsonWriterFactory factory = Json.createWriterFactory( writerConfig );
354
355 try( JsonWriter writer = factory.createWriter( Files.newOutputStream( configPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ) ) ) {
356 writer.write( rootBuilder.build() );
357 return true;
358 }
359 }catch( final IOException exception ) {
360 Debug.printDebug( "[Pref Manager Error] Critical: Failed to serialize active memory states to 'conf.json'. Reason: %s", exception.getMessage() );
361 Debug.printException( this.getClass(), exception );
362 return false;
363 }finally {
364 profileLock.unlock();
365 }
366 }else {
367 Debug.printError( "[Pref Manager Error] removeProfile() -> Profiles are allready locked" );
368 }
369 }catch( InterruptedException _ ) {
370 Thread.currentThread().interrupt();
371 }
372 return false;
373 }
374
375
376
377
378
379
380
381 public boolean loadAllPreferences() {
382 if( !Files.exists( configPath, LinkOption.NOFOLLOW_LINKS ) ) {
383 Debug.printDebug( "[Pref Manager Warn] Config file not available. Save to create it." );
384 return false;
385 }
386 try {
387 if( !profileLock.tryLock( LOCK_TIME, TimeUnit.SECONDS ) ) {
388 Debug.printError( "[Pref Manager Error] Profiles are allready locked" );
389 return false;
390 }
391 }catch( InterruptedException _ ) {
392 Thread.currentThread().interrupt();
393 return false;
394 }
395
396 try( JsonReader reader = Json.createReader( Files.newInputStream( configPath ) ) ) {
397 final JsonObject rootObj = reader.readObject();
398 if( rootObj.isEmpty() ) return false;
399
400
401 if( !extractGlobal( rootObj ) ) {
402 Debug.printDebug( "[Pref Manager Warn] load globals incompleted" );
403 }
404 loadedProfiles.clear();
405
406
407 if( !extractProfiles( rootObj ) ) {
408 Debug.printDebug( "[Pref Manager Warn] load profiles incompleted" );
409 }
410 return true;
411 }catch( final JsonParsingException | ClassCastException | IOException exception ) {
412 Debug.printDebug( "[Error] Critical: Failed to load profiles. Reason: %s", exception.getMessage() );
413 Debug.printException( this.getClass(), exception );
414 return false;
415 }finally {
416 profileLock.unlock();
417 }
418 }
419
420 private boolean extractGlobal( final JsonObject rootObj ) {
421 if( rootObj.containsKey( "globalSettings" ) ) {
422 final JsonObject globalDoc = rootObj.getJsonObject( "globalSettings" );
423 if( globalDoc == null ) return false;
424 this.globalAutoStart = globalDoc.getBoolean( "autoStart", false );
425 this.maxLogCount = globalDoc.getInt( "maxLogCount", 5 );
426 if( globalDoc.containsKey( "maxLogSize" ) ) {
427 try {
428 this.maxLogSize = globalDoc.getJsonNumber( "maxLogSize" ).longValueExact();
429 }catch( final ClassCastException | JsonParsingException | ArithmeticException exception ) {
430 Debug.printException( getClass(), exception );
431 }
432 }
433 if( globalDoc.containsKey( "theme" ) ) {
434 final String className = globalDoc.getString( "theme" );
435 try {
436 final Class<?> themeClass = Class.forName( className );
437 this.theme = (AppTheme) themeClass.getDeclaredConstructor().newInstance();
438 return true;
439 }catch( final ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
440 | InvocationTargetException | NoSuchMethodException | SecurityException exception ) {
441 Debug.printDebug( "[Error] Falling back to default. Failed to instantiate theme class: %s", exception.getMessage() );
442 Debug.printException( getClass(), exception );
443 }
444 }else {
445 Debug.printDebug( "[Pref Manager Warn] No value for instantiate theme class. Falling back to default." );
446 }
447 }
448 return false;
449 }
450
451 private boolean extractProfiles( final JsonObject rootObj ) {
452 for( final String jobName : rootObj.keySet() ) {
453 if( jobName.equals( "globalSettings" ) ) continue;
454 final JsonObject jobData = rootObj.getJsonObject( jobName );
455 final Preference pref = Preference.createSinglePreference( jobName );
456 try {
457 pref.deserialize( jobData );
458 loadedProfiles.add( pref );
459 }catch( final ConfigException exception ) {
460 Debug.printDebug( "[Pref Manager Error] Critical: Failed to load job profile '%s'. Skipping entry. Reason: %s", jobName, exception.getMessage() );
461 Debug.printException( this.getClass(), exception );
462 return false;
463 }
464 }
465 return !loadedProfiles.isEmpty();
466 }
467
468
469
470
471
472
473
474
475
476 public boolean isGlobalAutoStart() { return globalAutoStart; }
477
478
479
480
481
482
483
484 public void setGlobalAutoStart( final boolean globalAutoStart ) { this.globalAutoStart = globalAutoStart; }
485
486
487
488
489
490
491
492
493 public Path getConfigPath() { return configPath; }
494
495
496
497
498
499
500 public Path getLogPath() { return logPath; }
501
502
503
504
505
506
507 public Path getDebugPath() { return debugPath; }
508
509
510
511
512
513
514 public Path getErrorPath() { return errorPath; }
515
516
517
518
519
520
521 public void setTheme( final AppTheme theme ) { this.theme = theme; }
522
523
524
525
526
527
528 public AppTheme getTheme() { return theme; }
529
530
531
532
533
534
535
536 public boolean isCustomConfigDir() { return !DATASYNC_HOME.equals( rootPath ); }
537
538
539
540
541
542
543 public Path getRootPath() { return rootPath; }
544
545
546
547
548
549
550 public long getMaxLogSize() { return maxLogSize; }
551
552
553
554
555
556
557 public int getMaxLogCount() { return maxLogCount; }
558 }