View Javadoc
1   package de.spiritscorp.datasync.io;
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.io.IOException;
24  import java.io.OutputStream;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.nio.file.Paths;
28  import java.nio.file.StandardOpenOption;
29  import java.nio.file.attribute.FileTime;
30  import java.util.HashMap;
31  import java.util.Map;
32  
33  import jakarta.json.Json;
34  import jakarta.json.JsonException;
35  import jakarta.json.JsonObject;
36  import jakarta.json.JsonObjectBuilder;
37  import jakarta.json.JsonReader;
38  import jakarta.json.JsonValue;
39  import jakarta.json.JsonWriter;
40  import jakarta.json.JsonWriterFactory;
41  import jakarta.json.stream.JsonGenerator;
42  
43  import de.spiritscorp.datasync.model.FileAttributes;
44  
45  /**
46   * Isolated binary mapping engine handling structural cache entries matching runtime properties.
47   * * @author Tom Spirit
48   */
49  class IOSyncMap {
50  
51  	/** Individual path to the sync map */
52  	private final Path jobSyncMapPath;
53  
54  	/**
55  	 * Binds tracking matrices to unique physical layout footprints.
56  	 */
57  	IOSyncMap( final String jobName ) {
58  		this.jobSyncMapPath = PreferenceManager.getInstance().getConfigPath().getParent().resolve( "syncMap_" + jobName + ".json" );
59  	}
60  
61  	/**
62  	 * Deserializes the structural file-tracking registry from disk and hydrates the provided synchronization mapping.
63  	 * Enforces strict pre-conditions ensuring the destination container is empty and the source checkpoint file
64  	 * exists prior to execution. Intercepts and logs file-system access errors, JSON syntax malformations,
65  	 * and internal parser state conflicts gracefully.
66  	 *
67  	 * @param syncMap The target destination map to be populated with path keys and their associated tracking metadata.
68  	 * @return true if the persistence store was parsed successfully and the map state was fully hydrated;<br>
69  	 *         false if pre-conditions failed or an internal parsing/IO exception occurred.
70  	 */
71  	boolean loadSyncMap( final Map<Path, FileAttributes> syncMap ) {
72  		if( !syncMap.isEmpty() || !Files.exists( jobSyncMapPath ) ) { return false; }
73  		try( JsonReader jsonReader = Json.createReader( Files.newInputStream( jobSyncMapPath, StandardOpenOption.READ ) ) ) {
74  			final JsonObject jsonObject = jsonReader.readObject();
75  
76  			for( final Map.Entry<String, JsonValue> entry : jsonObject.entrySet() ) {
77  				final JsonObject objectEntry = entry.getValue().asJsonObject();
78  				final FileAttributes fileAttributes = new FileAttributes(
79  						Paths.get( objectEntry.getString( "relativeFilePath" ) ),
80  						objectEntry.getString( "createTimeString" ),
81  						FileTime.fromMillis( Long.parseLong( objectEntry.get( "createTime" ).toString() ) ),
82  						objectEntry.getString( "modTimeString" ),
83  						FileTime.fromMillis( Long.parseLong( objectEntry.get( "modTime" ).toString() ) ),
84  						Long.parseLong( objectEntry.get( "size" ).toString() ),
85  						objectEntry.getString( "fileHash" ) );
86  				syncMap.put( Paths.get( objectEntry.getString( "relativeFilePath" ) ), fileAttributes );
87  			}
88  			return true;
89  		}catch( final IllegalArgumentException | UnsupportedOperationException | SecurityException | IOException e ) {
90  			// Block 1: IO & File System Operations (Options, Permissions, Missing Files)
91  			Debug.printDebug( "[IO Sync Map Error] File system or configuration failure while opening stream: %s", e.getMessage() );
92  			Debug.printException( this.getClass(), e );
93  			return false;
94  
95  		}catch( final JsonException exception ) {
96  			// Block 2: JSON Processing (Deals with both syntax/parsing and structural creation errors)
97  			Debug.printDebug( "[IO Sync Map Error] JSON processing or syntax error: %s", exception.getMessage() );
98  			Debug.printException( this.getClass(), exception );
99  			return false;
100 
101 		}catch( final IllegalStateException exception ) {
102 			// Block 3: Reader State Management (Reader already closed or multi-call violation)
103 			Debug.printDebug( "[IO Sync Map Error] Parser state conflict: %s", exception.getMessage() );
104 			Debug.printException( this.getClass(), exception );
105 			return false;
106 		}
107 	}
108 
109 	/**
110 	 * Serializes the active in-memory file synchronization snapshot and flushes the state to disk.
111 	 * Compiles the tracking dataset into a formatted JSON structure utilizing pretty-printing layout rules,
112 	 * completely truncating any pre-existing target file to establish a clean persistence baseline.
113 	 *
114 	 * @param syncMap The source state map containing the path-to-attribute records to be persisted.
115 	 */
116 	void writeSyncMap( final Map<Path, FileAttributes> syncMap ) {
117 		try( OutputStream outputStream = Files.newOutputStream( jobSyncMapPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ) ) {
118 			final HashMap<String, Boolean> config = new HashMap<>();
119 			config.put( JsonGenerator.PRETTY_PRINTING, true );
120 			final JsonWriterFactory jWriterFactory = Json.createWriterFactory( config );
121 
122 			final JsonObject jsonObject = createJsonObject( syncMap );
123 			final JsonWriter jsonWriter = jWriterFactory.createWriter( outputStream );
124 			jsonWriter.write( jsonObject );
125 			jsonWriter.close();
126 		}catch( final IOException exception ) {
127 			Debug.printDebug( "[IO Sync Map Error] File system or configuration failure while writing file: %s", exception.getMessage() );
128 			Debug.printException( this.getClass(), exception );
129 		}
130 	}
131 
132 	/**
133 	 * Deletes the persisted file synchronization snapshot from disk.
134 	 * <p>
135 	 * Removes the tracking dataset file if it exists, effectively clearing the persistence
136 	 * baseline and forcing a full scan or re-initialization upon the next execution.
137 	 *
138 	 * @return {@code true} if the file was successfully deleted or did not exist;
139 	 *         {@code false} if a file system failure occurred.
140 	 */
141 	boolean deleteSyncMap() {
142 		try {
143 			if( Files.deleteIfExists( jobSyncMapPath ) ) {
144 				Debug.printDebug( "[IO Sync Map] Successfully deleted sync map file: %s", jobSyncMapPath.getFileName().toString() );
145 			}
146 			return true;
147 		}catch( final IOException exception ) {
148 			Debug.printDebug( "[IO Sync Map Error] File system failure while deleting file: %s", exception.getMessage() );
149 			Debug.printException( this.getClass(), exception );
150 			return false;
151 		}
152 	}
153 
154 	/**
155 	 * Translates the raw in-memory domain mapping of file metadata into a structured JSON object tree.
156 	 * Iterates through the tracking entries, maps file primitives and specialized timestamps into a
157 	 * unified object blueprint, and indexes each sub-block by its relative file identifier path string.
158 	 *
159 	 * @param syncMap The raw file attribute domain dataset to convert.
160 	 * @return An immutable structural JSON representation ready for serialization to the persistence layer.
161 	 */
162 	private JsonObject createJsonObject( final Map<Path, FileAttributes> syncMap ) {
163 		final JsonObjectBuilder joBuilder = Json.createObjectBuilder();
164 		for( final Map.Entry<Path, FileAttributes> entry : syncMap.entrySet() ) {
165 			final JsonObject jsonObject = Json.createObjectBuilder()
166 					.add( "relativeFilePath", entry.getValue().getRelativeFilePath().toString() )
167 					.add( "fileHash", entry.getValue().getFileHash() )
168 					.add( "modTimeString", entry.getValue().getModTimeString() )
169 					.add( "createTime", entry.getValue().getCreateTime().toMillis() )
170 					.add( "modTime", entry.getValue().getModTime().toMillis() )
171 					.add( "size", entry.getValue().getSize() )
172 					.add( "createTimeString", entry.getValue().getCreateTimeString() )
173 					.build();
174 			joBuilder.add( entry.getValue().getRelativeFilePath().toString(), jsonObject );
175 		}
176 		return joBuilder.build();
177 	}
178 }