summaryrefslogtreecommitdiff
path: root/lazycachelist.py
diff options
context:
space:
mode:
Diffstat (limited to 'lazycachelist.py')
-rw-r--r--lazycachelist.py170
1 files changed, 170 insertions, 0 deletions
diff --git a/lazycachelist.py b/lazycachelist.py
new file mode 100644
index 0000000..b4d003a
--- /dev/null
+++ b/lazycachelist.py
@@ -0,0 +1,170 @@
+from dataclasses import dataclass, asdict
+
+from texture import ImageTextureImmichAsset
+from manager import PixMan
+
+@dataclass
+class Album:
+ id: str
+ range_start: int
+ range_end: int
+ assets_list: list[str] = None
+
+ @property
+ def assets_count(self):
+ return end - start
+
+
+@dataclass
+class CallbackStateData:
+ asset_index: int
+ movement: int
+ assets: list[str]
+ current_asset: str
+
+ @classmethod
+ def from_lctl(cls, l):
+ si = (l.asset_index - 5) % l.asset_count
+ ei = (l.asset_index + 6) % l.asset_count
+ sa = l.assets[si:ei] if si < ei else l.assets[si:] + l.assets[:ei]
+ assets = [ a["id"] for a in sa ]
+ return cls(
+ asset_index=l.asset_index,
+ movement=l.last_movement,
+ assets=assets,
+ current_asset=assets[5],
+ )
+
+
+class LazyCachingTextureList():
+ def __init__(self, album_ids, max_cache_items=100, change_callback=None):
+ self.immich_connector = PixMan().immich_connector
+ self.void = False
+ assert max_cache_items >= 20, "Minimum cache items is 20" # Double small radius
+
+ # Ring buffer
+ self.max_cache_items = max_cache_items
+ self.radius_small = 10
+ self.cache_items_behind = 0
+ self.cache_items_ahead = 0
+ self.cache_index = 0
+ self.asset_index = 0
+ self.asset_count = 0
+ self.last_movement = 0
+
+ self.cached_items = 0
+
+ self.album_keys = album_ids
+ # TODO simplify album handling, dont need classes, etc.
+ self.albums = self._get_albums()
+ self.assets = self._get_album_assets()
+
+ self.change_callback = change_callback
+
+ @property
+ def max_cache_items(self):
+ return self.cache_length
+
+ @max_cache_items.setter
+ def max_cache_items(self, max_cache_items):
+ self.cache_length = max_cache_items
+ self.cache = [None] * max_cache_items
+ self.radius_large = int(max_cache_items / 2)
+
+ def free(self):
+ self.void = True
+
+ def index_in_cache_range(self, index):
+ index_range_low = (self.asset_index - self.cache_items_behind) % self.asset_count
+ index_range_high = (self.asset_index + self.cache_items_ahead ) % self.asset_count
+ if index_range_low > index_range_high:
+ return index_range_low <= index or index <= index_range_high
+ return index_range_low <= index <= index_range_high
+
+ def _get_albums(self):
+ albums = []
+ self.asset_count = i = 0
+ albums_info = self.immich_connector.load_all_albums()
+ for album_info in albums_info:
+ id = album_info["id"]
+ if id not in self.album_keys:
+ continue
+ asset_count = album_info["assetCount"]
+ albums.append(Album(id, i, i + asset_count))
+ i += asset_count + 1
+ self.asset_count += asset_count
+ return albums
+
+ def _get_album_assets(self):
+ assets = []
+ for album in self.albums:
+ assets += self.immich_connector.load_album_assets(album.id)
+ return assets
+
+ def _fill_cache(self, current_radius, final_radius, step):
+ if current_radius >= final_radius:
+ return current_radius
+
+ for i in range(current_radius * step, final_radius * step, step):
+ cache_index = (self.cache_index + i) % self.cache_length
+ asset_index = (self.asset_index + i) % self.asset_count
+ if self.cache[cache_index]:
+ self.cache[cache_index].free() # Since this is a ring buffer, textures outside of range can just get free'd here
+ asset = self.assets[asset_index]
+ tex = ImageTextureImmichAsset(asset, asset_index)
+ self.immich_connector.load_texture_async(self, tex)
+ self.cache[cache_index] = tex
+ self.cached_items += 1
+
+ return max(current_radius, final_radius)
+
+ def _update_cache_get_item(self, asset_index):
+ prev_asset_index = self.asset_index
+ self.asset_index = asset_index % self.asset_count
+ #if prev_asset_index == asset_index:
+ # return self.cache[self.cache_index]
+ # Movement is the distance between the previous and current index in the assets list
+ # Since the list wraps around, fastest method is just to get the abs min of the 3 cases
+ movement = min(
+ asset_index - prev_asset_index, # No list wrap
+ asset_index - self.asset_count, # Wrap backwards (0 -> -1)
+ self.asset_count - prev_asset_index, # Wrap forwards (-1 -> 0)
+ key=abs)
+ self.last_movement = movement
+ self.cache_index = (self.cache_index + movement) % self.cache_length
+
+ ahead = max(0, self.cache_items_ahead - movement)
+ behind = max(0, self.cache_items_behind + movement)
+
+ if ahead + behind > self.cache_length:
+ if movement < 0:
+ ahead += movement
+ else:
+ behind -= movement
+
+ #print("AHEAD/BEHIND/CACHE_I/ASSET_I/MOVEMENT:", ahead, behind, self.cache_index, self.asset_index, movement)
+ # TODO if ahead is 0 then clear queue
+
+ ahead = self._fill_cache(ahead, self.radius_small, +1) # Fill small radius ahead of cache_index
+ behind = self._fill_cache(behind, self.radius_small, -1) # Fill small radius behind cache_index
+ ahead = self._fill_cache(ahead, self.radius_large, +1) # Fill large radius ahead of cache_index
+
+ self.cache_items_ahead = ahead
+ self.cache_items_behind = behind
+
+ # Perform callback
+ if prev_asset_index != asset_index and self.change_callback:
+ self.change_callback(asdict(CallbackStateData.from_lctl(self)))
+
+ return self.cache[self.cache_index]
+
+ def __len__(self):
+ return self.asset_count
+
+ def __getitem__(self, index):
+ if not self.asset_count:
+ raise IndexError("Index out of bounds")
+ i = index % self.asset_count
+ if abs(index) > i:
+ raise IndexError("Index out of bounds")
+ return self._update_cache_get_item(index)