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