1use crate::edit_context::EditContext;
2use crate::{AssetSourceId, DataSource, PendingFileOperations};
3use crate::{PathNode, PathNodeRoot};
4use hydrate_base::hashing::HashSet;
5use hydrate_data::json_storage::{MetaFile, MetaFileJson};
6use hydrate_data::{
7 AssetId, AssetLocation, AssetName, CanonicalPathReference, DataSetAssetInfo, HashObjectMode,
8 ImportableName, ImporterId, PathReference,
9};
10use hydrate_pipeline::{
11 HydrateProjectConfiguration, ImportJobSourceFile, ImportJobToQueue, ImportLogEvent, ImportType,
12 Importer, ImporterRegistry, LogEventLevel, PipelineResult, RequestedImportable, ScanContext,
13 ScannedImportable,
14};
15use hydrate_schema::{HashMap, SchemaNamedType};
16use std::ffi::OsStr;
17use std::path::{Path, PathBuf};
18use std::sync::Arc;
19use uuid::Uuid;
20
21struct ScannedSourceFile<'a> {
46 meta_file: MetaFile,
47 importer: &'a Arc<dyn Importer>,
48 scanned_importables: Vec<ScannedImportable>,
49}
50
51struct FileMetadata {
52 }
55
56impl FileMetadata {
57 pub fn new(_metadata: &std::fs::Metadata) -> Self {
58 FileMetadata {
59 }
62 }
63
64 }
68
69struct SourceFileDiskState {
71 generated_assets: HashSet<AssetId>,
73 persisted_assets: HashSet<AssetId>,
74 _importer_id: ImporterId,
76 _importables: HashMap<ImportableName, AssetId>,
77}
78
79struct GeneratedAssetDiskState {
81 source_file_path: PathBuf, }
83
84struct PersistedAssetDiskState {
86 asset_file_path: PathBuf,
87 _asset_file_metadata: FileMetadata,
88 object_hash: u64,
89 }
92
93enum AssetDiskState {
94 Generated(GeneratedAssetDiskState),
95 Persisted(PersistedAssetDiskState),
96}
97
98impl AssetDiskState {
99 fn is_persisted(&self) -> bool {
100 match self {
101 AssetDiskState::Generated(_) => false,
102 AssetDiskState::Persisted(_) => true,
103 }
104 }
105
106 fn is_generated(&self) -> bool {
107 !self.is_persisted()
108 }
109
110 fn as_generated_asset_disk_state(&self) -> Option<&GeneratedAssetDiskState> {
111 match self {
112 AssetDiskState::Generated(x) => Some(x),
113 AssetDiskState::Persisted(_) => None,
114 }
115 }
116}
117
118pub struct FileSystemPathBasedDataSource {
119 asset_source_id: AssetSourceId,
120 file_system_root_path: PathBuf,
121
122 importer_registry: ImporterRegistry,
123
124 source_files_disk_state: HashMap<PathBuf, SourceFileDiskState>,
126 assets_disk_state: HashMap<AssetId, AssetDiskState>,
127
128 path_node_schema: SchemaNamedType,
129 path_node_root_schema: SchemaNamedType,
130}
131
132impl FileSystemPathBasedDataSource {
133 pub fn asset_source_id(&self) -> AssetSourceId {
134 self.asset_source_id
135 }
136
137 pub fn new<RootPathT: Into<PathBuf>>(
138 file_system_root_path: RootPathT,
139 edit_context: &mut EditContext,
140 asset_source_id: AssetSourceId,
141 importer_registry: &ImporterRegistry,
142 ) -> Self {
143 let path_node_schema = edit_context
144 .schema_set()
145 .find_named_type(PathNode::schema_name())
146 .unwrap()
147 .clone();
148 let path_node_root_schema = edit_context
149 .schema_set()
150 .find_named_type(PathNodeRoot::schema_name())
151 .unwrap()
152 .clone();
153
154 let file_system_root_path = file_system_root_path.into();
155 log::info!(
156 "Creating file system asset data source {:?}",
157 file_system_root_path,
158 );
159
160 FileSystemPathBasedDataSource {
161 asset_source_id,
162 file_system_root_path: file_system_root_path.into(),
163 importer_registry: importer_registry.clone(),
164
165 source_files_disk_state: Default::default(),
166 assets_disk_state: Default::default(),
167
168 path_node_schema,
169 path_node_root_schema,
170 }
171 }
172
173 fn is_asset_owned_by_this_data_source(
174 &self,
175 edit_context: &EditContext,
176 asset_id: AssetId,
177 ) -> bool {
178 if edit_context.asset_schema(asset_id).unwrap().fingerprint()
179 == self.path_node_root_schema.fingerprint()
180 {
181 return false;
182 }
183
184 let root_location = edit_context
185 .asset_location_chain(asset_id)
186 .unwrap_or_default()
187 .last()
188 .cloned()
189 .unwrap_or_else(AssetLocation::null);
190 self.is_root_location_owned_by_this_data_source(&root_location)
191 }
192
193 fn is_root_location_owned_by_this_data_source(
194 &self,
195 root_location: &AssetLocation,
196 ) -> bool {
197 root_location.path_node_id().as_uuid() == *self.asset_source_id.uuid()
198 }
199
200 fn path_for_asset(
201 &self,
202 containing_file_path: &Path,
203 asset_id: AssetId,
204 asset_info: &DataSetAssetInfo,
205 ) -> PathBuf {
206 let is_directory = asset_info.schema().fingerprint() == self.path_node_schema.fingerprint();
207 let asset_name = Self::sanitize_asset_name(asset_id, asset_info.asset_name());
208 let file_name = Self::file_name_for_asset(&asset_name, is_directory);
209 let asset_file_path = containing_file_path.join(file_name);
210 asset_file_path
211 }
212
213 fn containing_file_path_for_asset(
214 &self,
215 edit_context: &EditContext,
216 asset_id: AssetId,
217 ) -> PathBuf {
218 let mut location_chain = edit_context
219 .asset_location_chain(asset_id)
220 .unwrap_or_default();
221
222 let mut parent_dir = self.file_system_root_path.clone();
223
224 let path_node_root_id = location_chain.pop();
226
227 if path_node_root_id
230 != Some(AssetLocation::new(AssetId::from_uuid(
231 *self.asset_source_id.uuid(),
232 )))
233 {
234 return parent_dir;
235 }
236
237 for location in location_chain.iter().rev() {
238 let name = edit_context.asset_name(location.path_node_id()).unwrap();
239 parent_dir.push(name.as_string().unwrap());
240 }
241
242 parent_dir
243 }
244
245 fn file_name_for_asset(
259 asset_name: &str,
260 is_directory: bool,
261 ) -> PathBuf {
262 if is_directory {
266 PathBuf::from(asset_name)
267 } else {
268 PathBuf::from(format!("{}.af", asset_name))
269 }
270 }
271
272 fn sanitize_asset_name(
273 asset_id: AssetId,
274 asset_name: &AssetName,
275 ) -> String {
276 asset_name
277 .as_string()
278 .cloned()
279 .unwrap_or_else(|| asset_id.as_uuid().to_string())
280 }
281
282 fn canonicalize_all_path_nodes(
283 &self,
284 edit_context: &mut EditContext,
285 ) -> HashMap<PathBuf, AssetId> {
286 let mut all_paths: HashMap<PathBuf, AssetId> = Default::default();
287
288 for (k, v) in edit_context.assets() {
293 let mut location_chain = edit_context.asset_location_chain(*k).unwrap_or_default();
294 let root_location = location_chain
295 .last()
296 .cloned()
297 .unwrap_or_else(AssetLocation::null);
298 if !self.is_root_location_owned_by_this_data_source(&root_location) {
299 continue;
301 }
302
303 location_chain.pop();
305
306 let is_path_node = v.schema().fingerprint() == self.path_node_schema.fingerprint();
307 if !is_path_node {
308 continue;
310 }
311
312 let mut root_dir = self.file_system_root_path.clone();
313 for element in location_chain {
314 let node_name = edit_context.asset_name(element.path_node_id()).unwrap();
315 let sanitized_name = Self::sanitize_asset_name(element.path_node_id(), node_name);
316 root_dir.push(sanitized_name);
317
318 if all_paths.contains_key(&root_dir) {
319 } else {
322 all_paths.insert(root_dir.clone(), element.path_node_id());
323 }
324 }
325 }
326
327 all_paths.insert(
328 self.file_system_root_path.clone(),
329 AssetId::from_uuid(*self.asset_source_id.uuid()),
330 );
331
332 all_paths
333 }
334
335 fn ensure_asset_location_exists(
336 &self,
337 ancestor_path: &Path,
338 path_to_path_node_id: &mut HashMap<PathBuf, AssetId>,
339 edit_context: &mut EditContext,
340 ) -> AssetLocation {
341 let mut ancestor_paths = Vec::default();
347 let mut ancestor_path_iter = Some(ancestor_path);
348 let mut found_root = false;
349 while let Some(path) = ancestor_path_iter {
350 if path == self.file_system_root_path {
351 found_root = true;
352 break;
353 }
354
355 ancestor_paths.push(path.to_path_buf());
356 ancestor_path_iter = path.parent();
358 }
359
360 assert!(found_root);
362
363 let mut previous_asset_id = AssetId::from_uuid(*self.asset_source_id.uuid());
366
367 for ancestor_path in ancestor_paths.iter().rev() {
369 if let Some(existing_path_node_id) = path_to_path_node_id.get(ancestor_path) {
370 previous_asset_id = *existing_path_node_id;
372 } else {
373 let file_name = ancestor_path.file_name().unwrap().to_string_lossy();
375 let new_path_node_id = edit_context.new_asset(
376 &AssetName::new(file_name),
377 &AssetLocation::new(previous_asset_id),
378 self.path_node_schema.as_record().unwrap(),
379 );
380
381 path_to_path_node_id.insert(ancestor_path.to_path_buf(), new_path_node_id);
383 previous_asset_id = new_path_node_id;
384 }
385 }
386
387 AssetLocation::new(previous_asset_id)
388 }
389
390 fn find_canonical_path_references(
391 project_config: &HydrateProjectConfiguration,
392 source_file_path: &PathBuf,
393 scanned_importable: &ScannedImportable,
394 scanned_source_files: &HashMap<PathBuf, ScannedSourceFile>,
395 ) -> PipelineResult<HashMap<CanonicalPathReference, AssetId>> {
396 let mut canonical_path_references = HashMap::default();
399
400 for (path_reference, &importer_id) in &scanned_importable.referenced_source_file_info {
401 let path_reference_absolute =
402 path_reference.canonicalized_absolute_path(project_config, source_file_path)?;
403
404 let referenced_scanned_source_file = scanned_source_files
408 .get(&PathBuf::from(path_reference_absolute.path()))
409 .ok_or_else(|| format!(
410 "{:?} is referencing source file {:?} via absolute path {:?} but it does not exist or failed to import",
411 source_file_path,
412 path_reference.path(),
413 path_reference_absolute
414 ))?;
415 assert_eq!(
416 importer_id,
417 referenced_scanned_source_file.importer.importer_id()
418 );
419 canonical_path_references.insert(
420 path_reference.clone(),
421 *referenced_scanned_source_file
422 .meta_file
423 .past_id_assignments
424 .get(path_reference.importable_name())
425 .ok_or_else(|| format!(
426 "{:?} is referencing importable {:?} in {:?} but it was not found when the file was scanned",
427 source_file_path,
428 path_reference.path(),
429 path_reference.importable_name())
430 )
431 .unwrap()
432 );
433 }
434 Ok(canonical_path_references)
435 }
436}
437
438impl DataSource for FileSystemPathBasedDataSource {
439 fn is_generated_asset(
440 &self,
441 asset_id: AssetId,
442 ) -> bool {
443 if let Some(asset_disk_state) = self.assets_disk_state.get(&asset_id) {
444 asset_disk_state.is_generated()
445 } else {
446 false
447 }
448 }
449
450 fn persist_generated_asset(
456 &mut self,
457 edit_context: &mut EditContext,
458 asset_id: AssetId,
459 ) {
460 if !self.is_asset_owned_by_this_data_source(edit_context, asset_id) {
461 return;
462 }
463
464 let old_asset_disk_state = self.assets_disk_state.get(&asset_id).unwrap();
465 if !old_asset_disk_state.is_generated() {
466 return;
467 }
468
469 let old_asset_disk_state = self.assets_disk_state.remove(&asset_id);
470 let source_file_path = old_asset_disk_state
471 .unwrap()
472 .as_generated_asset_disk_state()
473 .unwrap()
474 .source_file_path
475 .clone();
476
477 let mut meta_file_path = source_file_path.clone().into_os_string();
478 meta_file_path.push(".meta");
479
480 let containing_file_path = self.containing_file_path_for_asset(edit_context, asset_id);
484 let asset_info = edit_context.assets().get(&asset_id).unwrap();
485 let asset_file_path = self.path_for_asset(&containing_file_path, asset_id, asset_info);
486 let data = crate::json_storage::AssetJson::save_asset_to_string(
488 edit_context.schema_set(),
489 edit_context.assets(),
490 asset_id,
491 true,
492 None,
493 );
494
495 std::fs::create_dir_all(&containing_file_path).unwrap();
496 std::fs::write(&asset_file_path, data).unwrap();
497
498 let contents = std::fs::read_to_string(&meta_file_path).unwrap();
502 let mut meta_file_contents = MetaFileJson::load_from_string(&contents);
503 meta_file_contents.persisted_assets.insert(asset_id);
504 std::fs::write(
505 &meta_file_path,
506 MetaFileJson::store_to_string(&meta_file_contents),
507 )
508 .unwrap();
509
510 let object_hash = edit_context
514 .data_set()
515 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationChainNames)
516 .unwrap();
517
518 let asset_file_metadata = FileMetadata::new(&std::fs::metadata(&asset_file_path).unwrap());
519 self.assets_disk_state.insert(
520 asset_id,
521 AssetDiskState::Persisted(PersistedAssetDiskState {
522 _asset_file_metadata: asset_file_metadata,
523 asset_file_path: asset_file_path.clone(),
524 object_hash,
525 }),
526 );
527
528 let source_file_disk_state = self
529 .source_files_disk_state
530 .get_mut(&source_file_path)
531 .unwrap();
532 source_file_disk_state.generated_assets.remove(&asset_id);
533 source_file_disk_state.persisted_assets.insert(asset_id);
534 }
535
536 fn load_from_storage(
537 &mut self,
538 project_config: &HydrateProjectConfiguration,
539 edit_context: &mut EditContext,
540 import_job_to_queue: &mut ImportJobToQueue,
541 ) {
542 profiling::scope!(&format!(
543 "load_from_storage {:?}",
544 self.file_system_root_path
545 ));
546 let mut assets_to_delete = Vec::default();
550 for (asset_id, _) in edit_context.assets() {
551 if self.is_asset_owned_by_this_data_source(edit_context, *asset_id) {
552 assets_to_delete.push(*asset_id);
553 }
554 }
555
556 for asset_to_delete in assets_to_delete {
557 edit_context.delete_asset(asset_to_delete).unwrap();
558 }
559
560 let mut path_to_path_node_id = self.canonicalize_all_path_nodes(edit_context);
565
566 let mut source_files = Vec::default();
567 let mut asset_files = Vec::default();
568 let mut meta_files = Vec::default();
569
570 let mut source_files_disk_state = HashMap::<PathBuf, SourceFileDiskState>::default();
571 let mut assets_disk_state = HashMap::<AssetId, AssetDiskState>::default();
572
573 {
574 profiling::scope!("Categorize files on disk");
575 let walker =
579 globwalk::GlobWalkerBuilder::from_patterns(&self.file_system_root_path, &["**"])
580 .file_type(globwalk::FileType::DIR)
581 .build()
582 .unwrap();
583
584 for file in walker {
585 if let Ok(file) = file {
586 let asset_file = dunce::canonicalize(&file.path()).unwrap();
587 let asset_location = self.ensure_asset_location_exists(
588 &asset_file,
589 &mut path_to_path_node_id,
590 edit_context,
591 );
592 let asset_id = asset_location.path_node_id();
593
594 let asset_file_metadata =
595 FileMetadata::new(&std::fs::metadata(&asset_file).unwrap());
596 let object_hash = edit_context
597 .data_set()
598 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationChainNames)
599 .unwrap();
600
601 assets_disk_state.insert(
602 asset_id,
603 AssetDiskState::Persisted(PersistedAssetDiskState {
604 asset_file_path: asset_file,
605 _asset_file_metadata: asset_file_metadata,
606 object_hash,
607 }),
608 );
609 }
610 }
611
612 let walker =
619 globwalk::GlobWalkerBuilder::from_patterns(&self.file_system_root_path, &["**"])
620 .file_type(globwalk::FileType::FILE)
621 .build()
622 .unwrap();
623
624 for file in walker {
625 if let Ok(file) = file {
626 let file = dunce::canonicalize(&file.path()).unwrap();
627 if file.extension() == Some(OsStr::new("meta")) {
628 meta_files.push(file.to_path_buf());
629 } else if file.extension() == Some(OsStr::new("af")) {
630 asset_files.push(file.to_path_buf());
631 } else {
632 source_files.push(file.to_path_buf());
633 }
634 }
635 }
636 }
637
638 let mut source_file_meta_files = HashMap::<PathBuf, MetaFile>::default();
644 {
645 profiling::scope!("Read meta files");
646 for meta_file in meta_files {
647 let source_file = meta_file.with_extension("");
648 if !source_file.exists() {
649 println!("Could not find source file, can't re-import data. Restore the source file or delete the meta file.");
650 continue;
651 }
652 let contents = std::fs::read_to_string(meta_file.as_path()).unwrap();
655 let meta_file_contents = MetaFileJson::load_from_string(&contents);
656
657 source_file_meta_files.insert(source_file, meta_file_contents);
658 }
659 }
660
661 {
665 profiling::scope!("Load Asset Files");
666 for asset_file in asset_files {
667 let contents = std::fs::read_to_string(asset_file.as_path()).unwrap();
669
670 let asset_location = self.ensure_asset_location_exists(
671 asset_file.as_path().parent().unwrap(),
672 &mut path_to_path_node_id,
673 edit_context,
674 );
675 let default_asset_location =
676 AssetLocation::new(AssetId(*self.asset_source_id.uuid()));
677 let schema_set = edit_context.schema_set().clone();
678 let asset_id = crate::json_storage::AssetJson::load_asset_from_string(
679 edit_context,
680 &schema_set,
681 None,
682 default_asset_location,
683 Some(asset_location.clone()),
684 &contents,
685 )
686 .unwrap();
687
688 let asset_file_metadata =
689 FileMetadata::new(&std::fs::metadata(&asset_file).unwrap());
690
691 let object_hash = edit_context
692 .data_set()
693 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationChainNames)
694 .unwrap();
695
696 assets_disk_state.insert(
697 asset_id,
698 AssetDiskState::Persisted(PersistedAssetDiskState {
699 asset_file_path: asset_file,
700 _asset_file_metadata: asset_file_metadata,
701 object_hash,
702 }),
703 );
704 }
705 }
706
707 let mut scanned_source_files = HashMap::<PathBuf, ScannedSourceFile>::default();
722
723 {
724 profiling::scope!("Scan Source Files");
725
726 for source_file in source_files {
727 let extension = &source_file.extension();
733 if extension.is_none() {
734 continue;
736 }
737
738 let importers = self
739 .importer_registry
740 .importers_for_file_extension(&extension.unwrap().to_string_lossy());
741
742 if importers.is_empty() {
743 } else if importers.len() > 1 {
745 } else {
747 let importer = self.importer_registry.importer(importers[0]).unwrap();
748
749 let mut scanned_importables = HashMap::default();
750 {
751 profiling::scope!(&format!(
752 "Importer::scan_file {}",
753 source_file.to_string_lossy()
754 ));
755 let scan_result = importer.scan_file(ScanContext::new(
756 &source_file,
757 edit_context.schema_set(),
758 &self.importer_registry,
759 project_config,
760 &mut scanned_importables,
761 &mut import_job_to_queue.log_data.log_events,
762 ));
763
764 match scan_result {
765 Ok(result) => result,
766 Err(e) => {
767 import_job_to_queue
768 .log_data
769 .log_events
770 .push(ImportLogEvent {
771 path: source_file.clone(),
772 asset_id: None,
773 level: LogEventLevel::FatalError,
774 message: format!(
775 "scan_file returned error: {}",
776 e.to_string()
777 ),
778 });
779
780 continue;
781 }
782 }
783 };
784
785 let mut meta_file = source_file_meta_files
787 .get(&source_file)
788 .cloned()
789 .unwrap_or_default();
790 for (_, scanned_importable) in &scanned_importables {
791 meta_file
793 .past_id_assignments
794 .entry(scanned_importable.name.clone())
795 .or_insert_with(|| AssetId::from_uuid(Uuid::new_v4()));
796 }
797
798 let mut meta_file_path = source_file.clone().into_os_string();
799 meta_file_path.push(".meta");
800
801 let mut importables = HashMap::<ImportableName, AssetId>::default();
804 for (_, scanned_importable) in &scanned_importables {
805 let imporable_asset_id =
806 meta_file.past_id_assignments.get(&scanned_importable.name);
807 importables.insert(
808 scanned_importable.name.clone(),
809 *imporable_asset_id.unwrap(),
810 );
811 }
812
813 source_files_disk_state.insert(
814 source_file.clone(),
815 SourceFileDiskState {
816 generated_assets: Default::default(),
817 persisted_assets: Default::default(),
818 _importer_id: importer.importer_id(),
820 _importables: importables,
821 },
822 );
823
824 std::fs::write(meta_file_path, MetaFileJson::store_to_string(&meta_file))
825 .unwrap();
826 scanned_source_files.insert(
827 source_file,
828 ScannedSourceFile {
829 meta_file,
830 importer,
831 scanned_importables: scanned_importables.into_values().collect(),
832 },
833 );
834 }
835 }
836 }
837
838 {
842 profiling::scope!("Enqueue import operations");
843 for (source_file_path, scanned_source_file) in &scanned_source_files {
844 let parent_dir = source_file_path.parent().unwrap();
845 let import_location =
847 AssetLocation::new(*path_to_path_node_id.get(parent_dir).unwrap());
848
849 let source_file_disk_state =
850 source_files_disk_state.get_mut(source_file_path).unwrap();
851
852 let mut requested_importables = HashMap::default();
853 for scanned_importable in &scanned_source_file.scanned_importables {
854 let importable_asset_id = *scanned_source_files
857 .get(source_file_path)
858 .unwrap()
859 .meta_file
860 .past_id_assignments
861 .get(&scanned_importable.name)
862 .unwrap();
863
864 let asset_name =
866 hydrate_pipeline::create_asset_name(source_file_path, scanned_importable);
867
868 let asset_file_exists = assets_disk_state.get(&importable_asset_id).is_some();
869 let asset_is_persisted = scanned_source_file
870 .meta_file
871 .persisted_assets
872 .contains(&importable_asset_id);
873
874 if asset_is_persisted && !asset_file_exists {
875 continue;
877 }
878
879 if !asset_is_persisted {
880 assets_disk_state.insert(
881 importable_asset_id,
882 AssetDiskState::Generated(GeneratedAssetDiskState {
883 source_file_path: source_file_path.clone(),
884 }),
885 );
886 source_file_disk_state
887 .generated_assets
888 .insert(importable_asset_id);
889 } else {
890 assert!(asset_file_exists);
891 assert_eq!(
892 edit_context
893 .asset_schema(importable_asset_id)
894 .unwrap()
895 .fingerprint(),
896 scanned_importable.asset_type.fingerprint()
897 );
898 assert!(assets_disk_state
904 .get(&importable_asset_id)
905 .unwrap()
906 .is_persisted());
907 source_file_disk_state
908 .persisted_assets
909 .insert(importable_asset_id);
910 }
911
912 let canonical_path_references = Self::find_canonical_path_references(
913 project_config,
914 source_file_path,
915 &scanned_importable,
916 &scanned_source_files,
917 );
918 match canonical_path_references {
919 Ok(canonical_path_references) => {
920 let source_file = PathReference::new(
921 "".to_string(),
922 source_file_path.to_string_lossy().to_string(),
923 scanned_importable.name.clone(),
924 )
925 .simplify(project_config);
926
927 let requested_importable = RequestedImportable {
928 asset_id: importable_asset_id,
929 schema: scanned_importable.asset_type.clone(),
930 asset_name,
931 asset_location: import_location,
932 source_file,
934 canonical_path_references,
935 path_references: scanned_importable.referenced_source_files.clone(),
936 replace_with_default_asset: !asset_is_persisted,
937 };
938
939 requested_importables
940 .insert(scanned_importable.name.clone(), requested_importable);
941 }
942 Err(e) => {
943 import_job_to_queue
944 .log_data
945 .log_events
946 .push(ImportLogEvent {
947 path: source_file_path.clone(),
948 asset_id: Some(importable_asset_id),
949 level: LogEventLevel::FatalError,
950 message: format!(
951 "While resolving references to other assets: {}",
952 e.to_string()
953 ),
954 });
955 }
956 }
957 }
958
959 if !requested_importables.is_empty() {
960 import_job_to_queue
961 .import_job_source_files
962 .push(ImportJobSourceFile {
963 source_file_path: source_file_path.to_path_buf(),
964 importer_id: scanned_source_file.importer.importer_id(),
965 requested_importables,
966 import_type: ImportType::ImportIfImportDataStale,
967 });
968 }
969 }
970 }
971
972 self.assets_disk_state = assets_disk_state;
973 self.source_files_disk_state = source_files_disk_state;
974
975 }
993
994 fn flush_to_storage(
995 &mut self,
996 edit_context: &mut EditContext,
997 ) {
998 profiling::scope!(&format!(
999 "flush_to_storage {:?}",
1000 self.file_system_root_path
1001 ));
1002
1003 let mut pending_writes = Vec::<AssetId>::default();
1004 let mut pending_deletes = Vec::<AssetId>::default();
1005
1006 for &asset_id in edit_context.assets().keys() {
1007 if asset_id.as_uuid() == *self.asset_source_id.uuid() {
1008 continue;
1010 }
1011
1012 if self.is_asset_owned_by_this_data_source(edit_context, asset_id) {
1013 match self.assets_disk_state.get(&asset_id) {
1014 None => {
1015 pending_writes.push(asset_id);
1017 }
1018 Some(asset_disk_state) => {
1019 let object_hash = edit_context
1020 .data_set()
1021 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationChainNames)
1022 .unwrap();
1023 match asset_disk_state {
1024 AssetDiskState::Generated(_) => {
1025 }
1028 AssetDiskState::Persisted(persisted_asset_disk_state) => {
1029 if persisted_asset_disk_state.object_hash != object_hash {
1030 pending_writes.push(asset_id);
1032 }
1033 }
1034 }
1035 }
1036 }
1037 }
1038 }
1039
1040 for (&asset_id, asset_disk_state) in &self.assets_disk_state {
1042 match asset_disk_state {
1043 AssetDiskState::Generated(_) => {
1044 }
1047 AssetDiskState::Persisted(_) => {
1048 if !edit_context.has_asset(asset_id)
1049 || !self.is_asset_owned_by_this_data_source(edit_context, asset_id)
1050 {
1051 pending_deletes.push(asset_id);
1053 }
1054 }
1055 }
1056 }
1057
1058 for asset_id in &pending_writes {
1072 if let Some(asset_info) = edit_context.assets().get(asset_id) {
1073 if self.is_asset_owned_by_this_data_source(edit_context, *asset_id) {
1074 if asset_id.as_uuid() == *self.asset_source_id.uuid() {
1075 continue;
1077 }
1078
1079 if let Some(asset_disk_state) = self.assets_disk_state.get(asset_id) {
1080 if asset_disk_state.is_generated() {
1081 continue;
1084 }
1085 }
1086
1087 let containing_file_path =
1088 self.containing_file_path_for_asset(edit_context, *asset_id);
1089 let is_directory =
1090 asset_info.schema().fingerprint() == self.path_node_schema.fingerprint();
1091 let asset_file_path =
1092 self.path_for_asset(&containing_file_path, *asset_id, asset_info);
1093
1094 if is_directory {
1095 std::fs::create_dir_all(&asset_file_path).unwrap();
1097 } else {
1098 let data = crate::json_storage::AssetJson::save_asset_to_string(
1100 edit_context.schema_set(),
1101 edit_context.assets(),
1102 *asset_id,
1103 true,
1104 None,
1105 );
1106
1107 std::fs::create_dir_all(&containing_file_path).unwrap();
1108 std::fs::write(&asset_file_path, data).unwrap();
1109
1110 let object_hash = edit_context
1111 .data_set()
1112 .hash_object(
1113 *asset_id,
1114 HashObjectMode::FullObjectWithLocationChainNames,
1115 )
1116 .unwrap();
1117
1118 let asset_file_metadata =
1119 FileMetadata::new(&std::fs::metadata(&asset_file_path).unwrap());
1120 self.assets_disk_state.insert(
1121 *asset_id,
1122 AssetDiskState::Persisted(PersistedAssetDiskState {
1123 _asset_file_metadata: asset_file_metadata,
1124 asset_file_path: asset_file_path.clone(),
1125 object_hash,
1126 }),
1127 );
1128
1129 }
1131 }
1132 }
1133 }
1134
1135 let mut deferred_directory_deletes = Vec::default();
1136
1137 for &asset_id in &pending_deletes {
1139 match self.assets_disk_state.get(&asset_id) {
1140 None => {
1141 panic!("assets pending deletion should be on disk");
1143 }
1144 Some(disk_state) => {
1145 match disk_state {
1146 AssetDiskState::Generated(_) => {
1147 panic!("generated assets should not be considered modified and so should not be pending deletion");
1150 }
1151 AssetDiskState::Persisted(disk_state) => {
1152 if disk_state.asset_file_path.is_dir() {
1153 deferred_directory_deletes
1156 .push((asset_id, disk_state.asset_file_path.clone()));
1157 } else {
1158 std::fs::remove_file(&disk_state.asset_file_path).unwrap();
1159 self.assets_disk_state.remove(&asset_id);
1160 }
1161 }
1162 }
1163 }
1164 }
1165 }
1166
1167 deferred_directory_deletes.sort_by(|(_, lhs), (_, rhs)| rhs.cmp(lhs));
1169
1170 for (_, directory) in deferred_directory_deletes {
1172 let is_empty = directory.read_dir().unwrap().next().is_none();
1173 if is_empty {
1174 std::fs::remove_dir(&directory).unwrap();
1175 }
1176 }
1177 }
1178
1179 fn edit_context_has_unsaved_changes(
1180 &self,
1181 edit_context: &EditContext,
1182 ) -> bool {
1183 for (&asset_id, asset_info) in edit_context.assets() {
1184 if asset_id.as_uuid() == *self.asset_source_id.uuid() {
1185 continue;
1187 }
1188
1189 if self.is_asset_owned_by_this_data_source(edit_context, asset_id) {
1190 match self.assets_disk_state.get(&asset_id) {
1191 None => {
1192 println!("asset name: {:?}", asset_info.asset_name());
1194 return true;
1195 }
1196 Some(asset_disk_state) => {
1197 let object_hash = edit_context
1198 .data_set()
1199 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationChainNames)
1200 .unwrap();
1201 match asset_disk_state {
1202 AssetDiskState::Generated(_) => {
1203 }
1206 AssetDiskState::Persisted(persisted_asset_disk_state) => {
1207 if persisted_asset_disk_state.object_hash != object_hash {
1208 return true;
1210 }
1211 }
1212 }
1213 }
1214 }
1215 }
1216 }
1217
1218 for (&asset_id, asset_disk_state) in &self.assets_disk_state {
1220 match asset_disk_state {
1221 AssetDiskState::Generated(_) => {
1222 }
1225 AssetDiskState::Persisted(_) => {
1226 if !edit_context.has_asset(asset_id)
1227 || !self.is_asset_owned_by_this_data_source(edit_context, asset_id)
1228 {
1229 return true;
1231 }
1232 }
1233 }
1234 }
1235
1236 return false;
1237 }
1238
1239 fn append_pending_file_operations(
1240 &self,
1241 edit_context: &EditContext,
1242 pending_file_operations: &mut PendingFileOperations,
1243 ) {
1244 for (&asset_id, asset_info) in edit_context.assets() {
1245 if asset_id.as_uuid() == *self.asset_source_id.uuid() {
1246 continue;
1248 }
1249
1250 if self.is_asset_owned_by_this_data_source(edit_context, asset_id) {
1251 match self.assets_disk_state.get(&asset_id) {
1252 None => {
1253 let containing_path =
1255 self.containing_file_path_for_asset(edit_context, asset_id);
1256 let asset_path =
1257 self.path_for_asset(&containing_path, asset_id, asset_info);
1258 pending_file_operations
1259 .create_operations
1260 .push((asset_id, asset_path));
1261 }
1262 Some(asset_disk_state) => {
1263 let object_hash = edit_context
1264 .data_set()
1265 .hash_object(asset_id, HashObjectMode::FullObjectWithLocationChainNames)
1266 .unwrap();
1267 match asset_disk_state {
1268 AssetDiskState::Generated(_) => {
1269 }
1272 AssetDiskState::Persisted(persisted_asset_disk_state) => {
1273 if persisted_asset_disk_state.object_hash != object_hash {
1274 pending_file_operations.modify_operations.push((
1276 asset_id,
1277 persisted_asset_disk_state.asset_file_path.clone(),
1278 ));
1279 }
1280 }
1281 }
1282 }
1283 }
1284 }
1285 }
1286
1287 for (&asset_id, asset_disk_state) in &self.assets_disk_state {
1289 match asset_disk_state {
1290 AssetDiskState::Generated(_) => {
1291 }
1294 AssetDiskState::Persisted(persisted_asset_disk_state) => {
1295 if !edit_context.has_asset(asset_id)
1296 || !self.is_asset_owned_by_this_data_source(edit_context, asset_id)
1297 {
1298 pending_file_operations
1300 .delete_operations
1301 .push((asset_id, persisted_asset_disk_state.asset_file_path.clone()));
1302 }
1303 }
1304 }
1305 }
1306 }
1307}