asset_library/common/library_cache.py
2026-01-07 16:05:47 +01:00

395 lines
11 KiB
Python

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=None):
self.file_cache = file_cache
self.catalog = None
self.author = None
self.description = None
self.tags = None
self.type = None
self.name = None
self._metadata = None
if data:
self.set_data(data)
@property
def filepath(self):
return self.file_cache.filepath
@property
def library_id(self):
return self.file_cache.library_id
@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 unique_name(self):
return (self.filepath / self.name).as_posix()
def set_data(self, data):
catalog = data["catalog"]
if isinstance(catalog, (list, tuple)):
catalog = "/".join(catalog)
self.catalog = 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", {})
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 __repr__(self):
return f"AssetCache(name={self.name}, catalog={self.catalog})"
def __eq__(self, other):
return self.to_dict() == other.to_dict()
class AssetsCache:
def __init__(self, file_cache):
self.file_cache = file_cache
self._data = []
def add(self, asset_cache_data, **kargs):
asset_cache = AssetCache(self.file_cache, {**asset_cache_data, **kargs})
self._data.append(asset_cache)
return asset_cache
def remove(self, asset_cache):
if isinstance(asset_cache, str):
asset_cache = self.get(asset_cache)
def __iter__(self):
return self._data.__iter__()
def __getitem__(self, key):
if isinstance(key, str):
return self.to_dict()[key]
else:
return self._data[key]
def to_dict(self):
return {a.name: a for a in self}
def get(self, name):
return next((a for a in self if a.name == name), None)
def __repr__(self):
return f"AssetsCache({list(self)})"
class FileCache:
def __init__(self, library_cache, data=None):
self.library_cache = library_cache
self.filepath = None
self.modified = None
self.assets = AssetsCache(self)
if data:
self.set_data(data)
@property
def library_id(self):
return self.library_cache.library_id
def set_data(self, data):
if "filepath" in data:
self.filepath = Path(data["filepath"])
self.modified = data.get("modified", time.time_ns())
if data.get("type") == "FILE":
self.assets.add(data)
for asset_cache_data in data.get("assets", []):
self.assets.add(asset_cache_data)
def to_dict(self):
return dict(
filepath=self.filepath.as_posix(),
modified=self.modified,
library_id=self.library_id,
assets=[asset_cache.to_dict() for asset_cache in self],
)
def __iter__(self):
return self.assets.__iter__()
def __getitem__(self, key):
return self._data[key]
def __repr__(self):
return f"FileCache(filepath={self.filepath})"
class AssetCacheDiff:
def __init__(self, library_cache, asset_cache, operation):
self.library_cache = library_cache
# self.filepath = data['filepath']
self.operation = operation
self.asset_cache = asset_cache
class LibraryCacheDiff:
def __init__(self, old_cache=None, new_cache=None, filepath=None):
self.filepath = filepath
self._data = []
self.compare(old_cache, new_cache)
def add(self, asset_cache_datas, operation):
if not isinstance(asset_cache_datas, (list, tuple)):
asset_cache_datas = [asset_cache_datas]
new_asset_diffs = []
for cache_data in asset_cache_datas:
new_asset_diffs.append(AssetCacheDiff(self, cache_data, operation))
self._data += new_asset_diffs
return new_asset_diffs
def compare(self, old_cache, new_cache):
if old_cache is None or new_cache is None:
print("Cannot Compare cache with None")
cache_dict = {a.unique_name: a for a in old_cache.asset_caches}
new_cache_dict = {a.unique_name: a for a in new_cache.asset_caches}
assets_added = self.add(
[v for k, v in new_cache_dict.items() if k not in cache_dict], "ADD"
)
assets_removed = self.add(
[v for k, v in cache_dict.items() if k not in new_cache_dict], "REMOVED"
)
assets_modified = self.add(
[
v
for k, v in cache_dict.items()
if v not in assets_removed and v != new_cache_dict[k]
],
"MODIFIED",
)
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"
)
if len(self) == 0:
print("No change in the library")
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 __getitem__(self, key):
return self._data[key]
def __len__(self):
return len(self._data)
def __repr__(self):
return f"LibraryCacheDiff(operations={[o for o in self][:2]}...)"
class LibraryCache:
def __init__(self, filepath):
self.filepath = Path(filepath)
self._data = []
@classmethod
def from_library(cls, library):
filepath = library.library_path / f"blender_assets.{library.id}.json"
return cls(filepath)
@property
def filename(self):
return self.filepath.name
@property
def library_id(self):
return self.filepath.stem.split(".")[-1]
# @property
# def filepath(self):
# """Get the filepath of the library json file relative to the library"""
# return self.directory / self.filename
def catalogs(self):
return set(a.catalog for a in self.asset_caches)
@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, tmp=False):
filepath = self.filepath
if tmp:
filepath = self.tmp_filepath
print(f"Write cache file to {filepath}")
write_file(filepath, self._data)
return filepath
def add(self, file_cache_data=None):
file_cache = FileCache(self, file_cache_data)
self._data.append(file_cache)
return file_cache
def add_asset_cache(self, asset_cache_data, filepath=None):
if filepath is None:
filepath = asset_cache_data["filepath"]
file_cache = self.get(filepath)
if not file_cache:
file_cache = self.add()
file_cache.assets.add(asset_cache_data)
# 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=None):
"""Compare the library cache with it current state and return the cache differential"""
old_cache = self.read()
if new_cache is None:
new_cache = self
return LibraryCacheDiff(old_cache, new_cache)
def update(self, cache_diff):
# Update the cache with the operations
for asset_cache_diff in cache_diff:
file_cache = self.get(asset_cache_diff.filepath)
if not asset_cache:
print(f"Filepath {asset_cache_diff.filepath} not in {self}")
continue
asset_cache = file_cache.get(asset_cache_diff.name)
if not asset_cache:
print(f"Asset {asset_cache_diff.name} not in file_cache {file_cache}")
continue
if asset_cache_diff.operation == "REMOVE":
file_cache.assets.remove(asset_cache_diff.name)
elif asset_cache_diff.operation in ("MODIFY", "ADD"):
asset_cache.set_data(asset_cache_diff.asset_cache.to_dict())
return self
def __len__(self):
return len(self._data)
def __iter__(self):
return iter(self._data)
def __getitem__(self, key):
if isinstance(key, str):
return self.to_dict()[key]
else:
return self._data[key]
def to_dict(self):
return {a.filepath: a for a in self}
def get(self, filepath):
return next((a for a in self if a.filepath == filepath), None)
def __repr__(self):
return f"LibraryCache(library_id={self.library_id})"