View Javadoc
1   package de.spiritscorp.datasync.model;
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.Files;
24  import java.nio.file.Path;
25  import java.util.List;
26  import java.util.Map;
27  
28  import de.spiritscorp.datasync.ScanType;
29  import de.spiritscorp.datasync.io.Debug;
30  import de.spiritscorp.datasync.io.Logger;
31  import de.spiritscorp.datasync.io.Preference;
32  
33  public class BgModel {
34  
35  	private final Preference pref;
36  	private final FileAnalyzer analyzer;
37  	private final FileHandler handler;
38  	private final Map<Path, FileAttributes> sourceMap;
39  	private final Map<Path, FileAttributes> destMap;
40  
41  	public BgModel( final Preference pref, final Logger logger, final Map<Path, FileAttributes> sourceMap, final Map<Path, FileAttributes> destMap ) {
42  		this.pref = pref;
43  		this.sourceMap = sourceMap;
44  		this.destMap = destMap;
45  		analyzer = new FileAnalyzer();
46  		handler = new FileHandler( logger );
47  	}
48  
49  	/**
50  	 * List all files, equals them and make the prefer sync in the background
51  	 *
52  	 * @return <b>boolean</b> <br>
53  	 *         true if the process ran and both maps are empty
54  	 */
55  	public boolean runBgJob() {
56  		final boolean logOn = pref.isLogOn();
57  		final Map<Path, FileAttributes> syncMap = pref.getSyncMap();
58  		final Path startSourcePath = pref.getSourcePaths().get( 0 );
59  		final Path startDestPath = pref.getDestPaths().get( 0 );
60  		final Path trashbinPath = pref.getTrashbinPath();
61  		final boolean trashbin = pref.isTrashbin();
62  		final boolean autoDel = pref.isAutoDel();
63  		final ScanType scanType = pref.getScanMode();
64  		final long lastScanDuration = System.currentTimeMillis() - pref.getLastScanTime();
65  		if( lastScanDuration > pref.getBgTime().getTime() && Files.exists( startDestPath ) ) {
66  			Debug.printDebug( "[BgModel] Time since last scan: %s", formatDuration( lastScanDuration ) );
67  			if( scanType == ScanType.SYNCHRONIZE ) {
68  				Debug.printDebug( "[Bg Model] BgJob running" );
69  				Debug.printDebug( "[Bg Model] List start" );
70  				final Thread thread1 = new Thread( () -> handler.listFiles( pref.getSourcePaths(), sourceMap, scanType, false ) );
71  				final Thread thread2 = new Thread( () -> handler.listFiles( pref.getDestPaths(), destMap, scanType, false ) );
72  				thread1.start();
73  				thread2.start();
74  				try {
75  					thread1.join();
76  					thread2.join();
77  				}catch( InterruptedException _ ) {
78  					Thread.currentThread().interrupt();
79  				}
80  				Debug.printDebug( "[Bg Model] List ready" );
81  				Debug.printDebug( "[Bg Model] SourceMap size -> %s | DestMap size -> %s", sourceMap.size(), destMap.size() );
82  				Debug.printDebug( "[Bg Model] Synchronization start" );
83  				final List<Map<Path, FileAttributes>> result = analyzer.getSyncFiles( sourceMap, destMap, startSourcePath, startDestPath, syncMap );
84  				Debug.printDebug( "[Bg Model] Synchronization list ready" );
85  
86  				Debug.printDebug( "[Bg Model] Process files start" );
87  				if( !result.get( 0 ).isEmpty() ) handler.copyFiles( result.get( 0 ), logOn, startDestPath );
88  				if( !result.get( 1 ).isEmpty() ) handler.copyFiles( result.get( 1 ), logOn, startSourcePath );
89  				if( !result.get( 2 ).isEmpty() ) handler.deleteFiles( result.get( 2 ), logOn, false, null );
90  
91  				sourceMap.clear();
92  				destMap.clear();
93  				syncMap.clear();
94  
95  				final Map<Path, FileAttributes> tempMap = Model.createMap();
96  				handler.listFiles( pref.getSourcePaths(), tempMap, scanType, false );
97  				for( final Map.Entry<Path, FileAttributes> entry : tempMap.entrySet() ) {
98  					syncMap.put( entry.getValue().getRelativeFilePath(), entry.getValue() );
99  				}
100 				pref.writeSyncMap();
101 				pref.saveLastScanTime();
102 				Debug.printDebug( "[Bg Model] Files successfully processed" );
103 				Debug.printDebug( "[Bg Model] BgJob finish" );
104 				return result.get( 0 ).isEmpty() && result.get( 1 ).isEmpty() && result.get( 2 ).isEmpty();
105 			}else if( scanType == ScanType.DEEP_SCAN || scanType == ScanType.FLAT_SCAN ) {
106 				Debug.printDebug( "[Bg Model] BgJob running" );
107 				Debug.printDebug( "[Bg Model] List start" );
108 				final Thread thread1 = new Thread( () -> handler.listFiles( pref.getSourcePaths(), sourceMap, scanType, pref.isSubDir() ) );
109 				final Thread thread2 = new Thread( () -> handler.listFiles( pref.getDestPaths(), destMap, scanType, pref.isSubDir() ) );
110 				thread1.start();
111 				thread2.start();
112 				try {
113 					thread1.join();
114 					thread2.join();
115 				}catch( InterruptedException _ ) {
116 					Thread.currentThread().interrupt();
117 				}
118 				Debug.printDebug( "[Bg Model] List ready" );
119 				Debug.printDebug( "[Bg Model] SourceMap size -> %s | DestMap size -> %s", sourceMap.size(), destMap.size() );
120 
121 				Debug.printDebug( "[Bg Model] Equals Files start" );
122 				analyzer.equalsFiles( sourceMap, destMap );
123 				Debug.printDebug( "[Bg Model] Equals Files ready" );
124 
125 				Debug.printDebug( "[Bg Model] Process files start" );
126 				if( autoDel && !destMap.isEmpty() ) handler.deleteFiles( destMap, logOn, trashbin, trashbinPath );
127 				if( !sourceMap.isEmpty() ) handler.copyFiles( sourceMap, logOn, startDestPath );
128 				pref.saveLastScanTime();
129 				Debug.printDebug( "[Bg Model] Files successfully processed" );
130 				Debug.printDebug( "[Bg Model] BgJob finish" );
131 				return sourceMap.isEmpty() && destMap.isEmpty();
132 			}else {
133 				Debug.printDebug( "[Bg Model] No valid background job" );
134 			}
135 		}
136 		return false;
137 	}
138 
139 	/**
140 	 * Formats a millisecond duration into a human-readable string using the largest necessary time units.
141 	 *
142 	 * @param durationMs The active delta time measured in milliseconds.
143 	 * @return A concisely formatted string (e.g., "2d 4h 15m", "45m 12s", or "8s").
144 	 */
145 	private static String formatDuration( final long durationMs ) {
146 		if( durationMs < 1_000 ) { return durationMs + "ms"; }
147 
148 		final long totalSeconds = durationMs / 1_000;
149 		final long seconds = totalSeconds % 60;
150 		final long totalMinutes = totalSeconds / 60;
151 		final long minutes = totalMinutes % 60;
152 		final long totalHours = totalMinutes / 60;
153 		final long hours = totalHours % 24;
154 		final long days = totalHours / 24;
155 
156 		final StringBuilder builder = new StringBuilder();
157 
158 		if( days > 0 ) {
159 			builder.append( days ).append( "d " );
160 		}
161 		if( hours > 0 ) {
162 			builder.append( hours ).append( "h " );
163 		}
164 		if( minutes > 0 ) {
165 			builder.append( minutes ).append( "m " );
166 		}
167 		// Seconds are always shown if there are no days/hours, or if there is a remaining second balance
168 		if( seconds > 0 || builder.isEmpty() ) {
169 			builder.append( seconds ).append( 's' );
170 		}
171 
172 		return builder.toString().trim();
173 	}
174 }