import bpy from pathlib import Path from asset_library.common.file_utils import read_file, write_file from copy import deepcopy import time from itertools import groupby class AssetCache: def __init__(self, file_cache, data): self.file_cache = file_cache self._data = data self.catalog = data["catalog"] self.author = data.get("author", "") self.description = data.get("description", "") self.tags = data.get("tags", []) self.type = data.get("type") self.name = data["name"] self._metadata = data.get("metadata", {}) @property def filepath(self): return self.file_cache.filepath @property def metadata(self): metadata = {".library_id": self.library.id, ".filepath": self.filepath} metadata.update(self.metadata) return metadata @property def norm_name(self): return self.name.replace(" ", "_").lower() def to_dict(self): return dict( catalog=self.catalog, author=self.author, metadata=self.metadata, description=self.description, tags=self.tags, type=self.type, name=self.name, ) def __str__(self): return f"AssetCache(name={self.name}, type={self.type}, catalog={self.catalog})" class FileCache: def __init__(self, library_cache, data): self.library_cache = library_cache self.filepath = data["filepath"] self.modified = data.get("modified", time.time_ns()) self._data = [] for asset_cache_data in data.get("assets", []): self.add(asset_cache_data) def add(self, asset_cache_data): asset_cache = AssetCache(self, asset_cache_data) self._data.append(asset_cache) def to_dict(self): return dict( filepath=self.filepath.as_posix(), modified=self.modified, library_id=self.library_cache.library.id, assets=[asset_cache.to_dict() for asset_cache in self], ) def __iter__(self): return self._data.__iter__() def __getitem__(self, key): return self._data[key] def __str__(self): return f"FileCache(filepath={self.filepath})" class AssetCacheDiff: def __init__(self, library_diff, asset_cache, operation): self.library_cache = library_cache self.filepath = data["filepath"] self.operation = operation class LibraryCacheDiff: def __init__(self, filepath=None): self.filepath = filepath self._data = [] def add(self, asset_diff): asset_diff = AssetCacheDiff(self, asset_diff) self._data.append(asset_cache_diff) def set(self, asset_diffs): for asset_diff in asset_diffs: self.add(asset_diff) def read(self): print(f"Read cache from {self.filepath}") for asset_diff_data in read_file(self.filepath): self.add(asset_diff_data) return self def group_by(self, key): """Return groups of file cache diff using the key provided""" data = list(self).sort(key=key) return groupby(data, key=key) def __iter__(self): return iter(self._data) def __len__(self): return len(self._data) def __getitem__(self, key): return self._data[key] class LibraryCache: def __init__(self, directory, id): self.directory = directory self.id = id self._data = [] @classmethod def from_library(cls, library): return cls(library.library_path, library.id) @property def filename(self): return f"blender_assets.{self.id}.json" @property def filepath(self): """Get the filepath of the library json file relative to the library""" return self.directory / self.filename @property def asset_caches(self): """Return an iterator to get all asset caches""" return (asset_cache for file_cache in self for asset_cache in file_cache) @property def tmp_filepath(self): return Path(bpy.app.tempdir) / self.filename def read(self): print(f"Read cache from {self.filepath}") for file_cache_data in read_file(self.filepath): self.add(file_cache_data) return self def write(self, temp=False): filepath = self.filepath if temp: filepath = self.tmp_filepath print(f"Write cache file to {filepath}") write_file(filepath, self._data) return filepath def add(self, file_cache_data): file_cache = FileCache(self, file_cache_data) self._data.append(file_cache) def unflatten_cache(self, cache): """Return a new unflattten list of asset data grouped by filepath""" new_cache = [] cache = deepcopy(cache) cache.sort(key=lambda x: x["filepath"]) groups = groupby(cache, key=lambda x: x["filepath"]) keys = ["filepath", "modified", "library_id"] for _, asset_datas in groups: asset_datas = list(asset_datas) # print(asset_datas[0]) asset_info = {k: asset_datas[0][k] for k in keys} asset_info["assets"] = [ {k: v for k, v in a.items() if k not in keys + ["operation"]} for a in asset_datas ] new_cache.append(asset_info) return new_cache def diff(self, new_cache): """Compare the library cache with it current state and return the cache differential""" cache = self.read() cache_dict = {f"{a['filepath']}/{a['name']}": a for a in cache.asset_caches} new_cache_dict = { f"{a['filepath']}/{a['name']}": a for a in new_cache.asset_caches } assets_added = [ AssetCacheDiff(v, "ADD") for k, v in new_cache.items() if k not in cache ] assets_removed = [ AssetCacheDiff(v, "REMOVED") for k, v in cache.items() if k not in new_cache ] assets_modified = [ AssetCacheDiff(v, "MODIFIED") for k, v in cache.items() if v not in assets_removed and v != new_cache[k] ] if assets_added: print( f"{len(assets_added)} Assets Added \n{tuple(a.name for a in assets_added[:10])}...\n" ) if assets_removed: print( f"{len(assets_removed)} Assets Removed \n{tuple(a.name for a in assets_removed[:10])}...\n" ) if assets_modified: print( f"{len(assets_modified)} Assets Modified \n{tuple(a.name for a in assets_modified[:10])}...\n" ) cache_diff = LibraryCacheDiff() cache_diff.set(assets_added + assets_removed + assets_modified) if not len(LibraryCacheDiff): print("No change in the library") return cache_diff def __len__(self): return len(self._data) def __iter__(self): return iter(self._data) def __getitem__(self, key): return self._data[key] def __str__(self): return f"LibraryCache(library={self.library.name})" print() prefs = bpy.context.preferences.addons["asset_library"].preferences library = prefs.env_libraries[0] library_cache = LibraryCache.from_library(library).read() data = library.library_type.fetch() print(data) print(library_cache[0][0]) # library_cache.diff(library.library_type.fetch()) # print(library_cache[0])