From: Scott Gasch Date: Fri, 15 May 2026 16:48:18 +0000 (-0700) Subject: Fix race condition? X-Git-Url: https://git.acknak.org/gitweb/?a=commitdiff_plain;h=0649cc4ae9335a743c8110f381ac95ea9645e7ab;p=kiosk.git Fix race condition? --- diff --git a/immich_photo_renderer.py b/immich_photo_renderer.py index 1f2fea4..2c18773 100644 --- a/immich_photo_renderer.py +++ b/immich_photo_renderer.py @@ -60,7 +60,6 @@ def parse_exif_date(exif_data: dict[str, Any]) -> Optional[datetime.datetime]: except ValueError: try: # Attempt 2: Handle common EXIF format 'YYYY:MM:DD HH:MM:SS' - clean_date = date_str.split('+')[0].split('-')[0] return datetime.datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%f%z") except ValueError: # Final Fallback: Return current time or a 'null' date to prevent crash @@ -108,7 +107,9 @@ class immich_photo_renderer(renderer.abstaining_renderer): if key == "Fetch Photos": return self.index_photos() elif key == "Rotate Current Photo": - return self.choose_photo() + result = self.choose_photo() + self._cleanup_old_jpegs("/var/www/kiosk/pages/mfs") + return result else: raise Exception("Unexpected operation") @@ -122,10 +123,13 @@ class immich_photo_renderer(renderer.abstaining_renderer): ) ) print(f"{len(all_assets)} kiosk assets.") + + new_uuids = set() for lite_asset in all_assets: asset_uuid = lite_asset.get('id') if asset_uuid: - self.candidate_photo_uuids.add(asset_uuid) + new_uuids.add(asset_uuid) + self.candidate_photo_uuids = new_uuids return True @staticmethod @@ -141,319 +145,330 @@ class immich_photo_renderer(renderer.abstaining_renderer): text ).strip() -def choose_photo(self): - """Pick one of the cached URLs and build a page.""" - if len(self.candidate_photo_uuids) == 0: - print("No photos!") - return False - - selected_uuid = random.sample(list(self.candidate_photo_uuids), 1)[0] - asset = self.immich_cli.get_full_asset(selected_uuid) - if not asset: - print(f"Asset {selected_uuid} is unknown?!") - return False - - # --- Fetch ALL network data first, before touching disk --- - try: - jpeg = self.immich_cli.get_asset_thumbnail(selected_uuid, size="fullsize") - except Exception: - print("Failed to get image!") - return False + def _cleanup_old_jpegs(self, directory: str, max_age_seconds: int = 300) -> None: + now = time.time() + with os.scandir(directory) as entries: + for entry in entries: + if entry.name.endswith('.jpeg') and entry.is_file(): + if now - entry.stat().st_mtime > max_age_seconds: + try: + os.remove(entry.path) + except FileNotFoundError: + pass + + def choose_photo(self): + """Pick one of the cached URLs and build a page.""" + if len(self.candidate_photo_uuids) == 0: + print("No photos!") + return False + + selected_uuid = random.sample(list(self.candidate_photo_uuids), 1)[0] + asset = self.immich_cli.get_full_asset(selected_uuid) + if not asset: + print(f"Asset {selected_uuid} is unknown?!") + return False + + # --- Fetch ALL network data first, before touching disk --- + try: + jpeg = self.immich_cli.get_asset_thumbnail(selected_uuid, size="fullsize") + except Exception: + print("Failed to get image!") + return False - try: - albums = self.immich_cli.get_asset_albums(selected_uuid) - except Exception: - print("Failed to get albums!") - albums = [] - - album_covers = {} - for album in albums: - album_cover_uuid = album.get('albumThumbnailAssetId') - if album_cover_uuid: - try: - album_covers[album_cover_uuid] = self.immich_cli.get_asset_thumbnail( - album_cover_uuid, size="thumbnail" - ) - except Exception: - print(f"Failed to get album cover for {album_cover_uuid}, skipping.") - - # --- All network calls done. Now compute derived values. --- - layout_class = get_layout_class(asset) - exif = asset.get('exifInfo', {}) - date = None - location = "Unknown location" - descr = '' - if exif: - date = parse_exif_date(exif) - city = exif.get("city", "") - state = exif.get("state", "") - country = exif.get("country", "") - lat = exif.get("latitude", "") - lon = exif.get("longitude", "") - - if city: - location = city - if state: - location = f"{location}, {state}" if location else state - if country: - location = f"{location}, {country}" if location else country - if lat and lon: - location = f"{location} @ {format_coords(lat, lon)}" if location else format_coords(lat, lon) - descr = exif.get("description", "") - - if not descr: - people = asset.get('people', []) - if people: - descr = "A photo of " + natural_join([p.get("name", "") for p in people]) - else: - descr = "No description data available. #needs description" - - descr = self._highlight_hashtags(descr) - date_str = date.strftime("%B %-d, %Y") if date else "Unknown date" - album_html = '' - for album in albums: - album_name = album.get('albumName') - album_cover_uuid = album.get('albumThumbnailAssetId') - if album_cover_uuid and album_cover_uuid in album_covers: - album_html += f""" -
- Album Art - {album_name} -
-""" - - # --- All data ready. Now write to disk atomically. --- - with open("/var/www/kiosk/pages/mfs/current.jpeg", "wb") as f: - f.write(jpeg) - f.flush() - os.fsync(f.fileno()) - - for cover_uuid, cover_data in album_covers.items(): - with open(f"/var/www/kiosk/pages/mfs/{cover_uuid}.jpeg", "wb") as f: - f.write(cover_data) - - cache_buster = int(time.time()) - with file_writer.file_writer("photo_33_3600.html") as f: - f.write( - f""" - -
- -
- Current kiosk photo + try: + albums = self.immich_cli.get_asset_albums(selected_uuid) + except Exception: + print("Failed to get albums!") + albums = [] + + album_covers = {} + for album in albums: + album_cover_uuid = album.get('albumThumbnailAssetId') + if album_cover_uuid: + try: + album_covers[album_cover_uuid] = self.immich_cli.get_asset_thumbnail( + album_cover_uuid, size="thumbnail" + ) + except Exception: + print(f"Failed to get album cover for {album_cover_uuid}, skipping.") + + # --- All network calls done. Now compute derived values. --- + layout_class = get_layout_class(asset) + exif = asset.get('exifInfo', {}) + date = None + location = "Unknown location" + descr = '' + if exif: + date = parse_exif_date(exif) + city = exif.get("city", "") + state = exif.get("state", "") + country = exif.get("country", "") + lat = exif.get("latitude", "") + lon = exif.get("longitude", "") + + if city: + location = city + if state: + location = f"{location}, {state}" if location else state + if country: + location = f"{location}, {country}" if location else country + if lat and lon: + location = f"{location} @ {format_coords(lat, lon)}" if location else format_coords(lat, lon) + descr = exif.get("description", "") + + if not descr: + people = asset.get('people', []) + if people: + descr = "A photo of " + natural_join([p.get("name", "") for p in people]) + else: + descr = "No description data available. #needs description" + + descr = self._highlight_hashtags(descr) + date_str = date.strftime("%B %-d, %Y") if date else "Unknown date" + album_html = '' + for album in albums: + album_name = album.get('albumName') + album_cover_uuid = album.get('albumThumbnailAssetId') + if album_cover_uuid and album_cover_uuid in album_covers: + album_html += f""" +
+ Album Art + {album_name}
- -
-
- {descr} + """ + + # --- All data ready. Now write to disk atomically. --- + with open(f"/var/www/kiosk/pages/mfs/{selected_uuid}.jpeg", "wb") as f: + f.write(jpeg) + f.flush() + os.fsync(f.fileno()) + + for cover_uuid, cover_data in album_covers.items(): + with open(f"/var/www/kiosk/pages/mfs/{cover_uuid}.jpeg", "wb") as f: + f.write(cover_data) + + cache_buster = int(time.time()) + with file_writer.file_writer("photo_33_3600.html") as f: + f.write( + f""" + +
+ +
+ Current kiosk photo
-
-
-
- {location} + +
+
+ {descr}
-
- {date_str} +
+
+
+ {location} +
+
+ {date_str} +
+
+
Appears In
+
+ {album_html}
-
-
Appears In
-
- {album_html}
-
- """) - return True + """) + return True # Test code x = immich_photo_renderer({"Fetch Photos": (60 * 60 * 12),