hydrate_model/data_source/
file_system_id_based.rs1use crate::edit_context::EditContext;
2use crate::{AssetId, AssetSourceId, DataSource, PathNodeRoot, PendingFileOperations};
3use hydrate_base::hashing::HashMap;
4use hydrate_base::uuid_path::{path_to_uuid, uuid_to_path};
5use hydrate_data::{AssetLocation, HashObjectMode};
6use hydrate_pipeline::{HydrateProjectConfiguration, ImportJobToQueue};
7use hydrate_schema::SchemaNamedType;
8use std::path::PathBuf;
9
10struct FileMetadata {
11 }
14
15impl FileMetadata {
16 pub fn new(_metadata: &std::fs::Metadata) -> Self {
17 FileMetadata {
18 }
21 }
22
23 }
27
28struct AssetDiskState {
29 object_hash: u64,
30 _file_metadata: FileMetadata,
31}
32
33pub struct FileSystemIdBasedDataSource {
34 asset_source_id: AssetSourceId,
35 file_system_root_path: PathBuf,
36
37 assets_disk_state: HashMap<AssetId, AssetDiskState>,
40
41 path_node_root_schema: SchemaNamedType,
42}
43
44impl FileSystemIdBasedDataSource {
45 fn is_asset_owned_by_this_data_source(
46 &self,
47 edit_context: &EditContext,
48 asset_id: AssetId,
49 ) -> bool {
50 if edit_context.asset_schema(asset_id).unwrap().fingerprint()
51 == self.path_node_root_schema.fingerprint()
52 {
53 return false;
54 }
55
56 let root_location = edit_context
58 .asset_location_chain(asset_id)
59 .unwrap_or_default()
60 .last()
61 .cloned()
62 .unwrap_or_else(AssetLocation::null);
63 root_location.path_node_id().as_uuid() == *self.asset_source_id.uuid()
64 || root_location.is_null()
65 }
66
67 pub fn asset_source_id(&self) -> AssetSourceId {
68 self.asset_source_id
69 }
70
71 pub fn new<RootPathT: Into<PathBuf>>(
72 file_system_root_path: RootPathT,
73 edit_context: &mut EditContext,
74 asset_source_id: AssetSourceId,
75 ) -> Self {
76 let path_node_root_schema = edit_context
77 .schema_set()
78 .find_named_type(PathNodeRoot::schema_name())
79 .unwrap()
80 .clone();
81
82 let file_system_root_path = file_system_root_path.into();
83 log::info!(
84 "Creating file system asset data source {:?}",
85 file_system_root_path,
86 );
87
88 FileSystemIdBasedDataSource {
89 asset_source_id,
90 file_system_root_path: file_system_root_path.into(),
91 assets_disk_state: Default::default(),
92 path_node_root_schema,
93 }
94 }
95
96 fn path_for_asset(
97 &self,
98 asset_id: AssetId,
99 ) -> PathBuf {
100 uuid_to_path(&self.file_system_root_path, asset_id.as_uuid(), "af")
101 }
102}
103
104impl DataSource for FileSystemIdBasedDataSource {
105 fn is_generated_asset(
106 &self,
107 _asset_id: AssetId,
108 ) -> bool {
109 false
111 }
112
113 fn persist_generated_asset(
118 &mut self,
119 _edit_context: &mut EditContext,
120 _asset_id: AssetId,
121 ) {
122 }
124
125 #[profiling::function]
126 fn load_from_storage(
127 &mut self,
128 _project_config: &HydrateProjectConfiguration,
129 edit_context: &mut EditContext,
130 _import_job_to_queue: &mut ImportJobToQueue,
131 ) {
132 profiling::scope!(&format!(
133 "load_from_storage {:?}",
134 self.file_system_root_path
135 ));
136
137 let mut assets_to_delete = Vec::default();
141 for (asset_id, _) in edit_context.assets() {
142 if self.is_asset_owned_by_this_data_source(edit_context, *asset_id) {
143 assets_to_delete.push(*asset_id);
144 }
145 }
146
147 for asset_to_delete in assets_to_delete {
148 edit_context.delete_asset(asset_to_delete).unwrap();
149 }
150
151 self.assets_disk_state.clear();
152
153 let walker =
157 globwalk::GlobWalkerBuilder::from_patterns(&self.file_system_root_path, &["**.af"])
158 .file_type(globwalk::FileType::FILE)
159 .build()
160 .unwrap();
161
162 for file in walker {
163 if let Ok(file) = file {
164 let file = dunce::canonicalize(&file.path()).unwrap();
165
166 let asset_file_metadata = FileMetadata::new(&std::fs::metadata(&file).unwrap());
167
168 let file_uuid = path_to_uuid(&self.file_system_root_path, &file).unwrap();
170 let contents = std::fs::read_to_string(&file).unwrap();
171 let default_asset_location =
172 AssetLocation::new(AssetId(*self.asset_source_id.uuid()));
173
174 let schema_set = edit_context.schema_set().clone();
175 crate::json_storage::AssetJson::load_asset_from_string(
176 edit_context,
177 &schema_set,
178 Some(file_uuid),
179 default_asset_location,
180 None,
181 &contents,
182 )
183 .unwrap();
184 let asset_id = AssetId::from_uuid(file_uuid);
185
186 let object_hash = edit_context
187 .data_set()
188 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationId)
189 .unwrap();
190
191 let old = self.assets_disk_state.insert(
192 asset_id,
193 AssetDiskState {
194 object_hash: object_hash,
195 _file_metadata: asset_file_metadata,
196 },
197 );
198 assert!(old.is_none());
199 }
200 }
201 }
202
203 fn flush_to_storage(
204 &mut self,
205 edit_context: &mut EditContext,
206 ) {
207 profiling::scope!(&format!(
208 "flush_to_storage {:?}",
209 self.file_system_root_path
210 ));
211
212 let mut pending_deletes = Vec::<AssetId>::default();
213 let mut pending_writes = Vec::<AssetId>::default();
214
215 for &asset_id in edit_context.assets().keys() {
216 if self.is_asset_owned_by_this_data_source(edit_context, asset_id) {
217 match self.assets_disk_state.get(&asset_id) {
218 None => {
219 pending_writes.push(asset_id);
221 }
222 Some(asset_disk_state) => {
223 let object_hash = edit_context
224 .data_set()
225 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationId)
226 .unwrap();
227 if asset_disk_state.object_hash != object_hash {
228 pending_writes.push(asset_id);
230 }
231 }
232 }
233 }
234 }
235
236 for (&asset_id, _) in &self.assets_disk_state {
238 if !edit_context.has_asset(asset_id)
239 || !self.is_asset_owned_by_this_data_source(edit_context, asset_id)
240 {
241 pending_deletes.push(asset_id);
243 }
244 }
245
246 for asset_id in pending_writes {
250 if asset_id.as_uuid() == *self.asset_source_id.uuid() {
251 continue;
253 }
254
255 let asset_info = edit_context.data_set().assets().get(&asset_id).unwrap();
256
257 let asset_location = if asset_info.asset_location().is_null()
260 || asset_info.asset_location().path_node_id().as_uuid()
261 == *self.asset_source_id.uuid()
262 {
263 None
264 } else {
265 Some(asset_info.asset_location())
266 };
267
268 let data = crate::json_storage::AssetJson::save_asset_to_string(
269 edit_context.schema_set(),
270 edit_context.assets(),
271 asset_id,
272 false, asset_location,
274 );
275 let file_path = self.path_for_asset(asset_id);
276
277 if let Some(parent) = file_path.parent() {
278 std::fs::create_dir_all(parent).unwrap();
279 }
280
281 std::fs::write(&file_path, data).unwrap();
282
283 let object_hash = edit_context
284 .data_set()
285 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationId)
286 .unwrap();
287 let asset_file_metadata = FileMetadata::new(&std::fs::metadata(&file_path).unwrap());
288
289 self.assets_disk_state.insert(
290 asset_id,
291 AssetDiskState {
292 object_hash,
293 _file_metadata: asset_file_metadata,
294 },
295 );
296 }
297
298 for asset_id in pending_deletes {
302 let file_path = self.path_for_asset(asset_id);
303 std::fs::remove_file(&file_path).unwrap();
304 self.assets_disk_state.remove(&asset_id);
305
306 }
308 }
309
310 fn edit_context_has_unsaved_changes(
311 &self,
312 edit_context: &EditContext,
313 ) -> bool {
314 for &asset_id in edit_context.assets().keys() {
315 if asset_id.as_uuid() == *self.asset_source_id.uuid() {
316 continue;
318 }
319
320 if self.is_asset_owned_by_this_data_source(edit_context, asset_id) {
321 match self.assets_disk_state.get(&asset_id) {
322 None => {
323 return true;
325 }
326 Some(asset_disk_state) => {
327 let object_hash = edit_context
328 .data_set()
329 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationId)
330 .unwrap();
331 if asset_disk_state.object_hash != object_hash {
332 return true;
334 }
335 }
336 }
337 }
338 }
339
340 for (&asset_id, _) in &self.assets_disk_state {
342 if !edit_context.has_asset(asset_id)
343 || !self.is_asset_owned_by_this_data_source(edit_context, asset_id)
344 {
345 return true;
347 }
348 }
349
350 return false;
351 }
352
353 fn append_pending_file_operations(
354 &self,
355 edit_context: &EditContext,
356 pending_file_operations: &mut PendingFileOperations,
357 ) {
358 for &asset_id in edit_context.assets().keys() {
359 if asset_id.as_uuid() == *self.asset_source_id.uuid() {
360 continue;
362 }
363
364 if self.is_asset_owned_by_this_data_source(edit_context, asset_id) {
365 match self.assets_disk_state.get(&asset_id) {
366 None => {
367 let file_path = self.path_for_asset(asset_id);
369 pending_file_operations
370 .create_operations
371 .push((asset_id, file_path));
372 }
373 Some(asset_disk_state) => {
374 let object_hash = edit_context
375 .data_set()
376 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationId)
377 .unwrap();
378 if asset_disk_state.object_hash != object_hash {
379 let file_path = self.path_for_asset(asset_id);
381 pending_file_operations
382 .modify_operations
383 .push((asset_id, file_path));
384 }
385 }
386 }
387 }
388 }
389
390 for (&asset_id, _) in &self.assets_disk_state {
392 if !edit_context.has_asset(asset_id)
393 || !self.is_asset_owned_by_this_data_source(edit_context, asset_id)
394 {
395 let file_path = self.path_for_asset(asset_id);
397 pending_file_operations
398 .delete_operations
399 .push((asset_id, file_path));
400 }
401 }
402 }
403}