From e8106f7c7c8d06060380d8a88d8366f1f454ea73 Mon Sep 17 00:00:00 2001 From: Muhammad-Ikhwan-Fathulloh Date: Sun, 7 Jun 2026 01:38:33 +0700 Subject: [PATCH 1/3] Fix logical bugs in event bus and bulk session deletion --- routes/session_routes.py | 17 ++++++++++------- src/event_bus.py | 6 ------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/routes/session_routes.py b/routes/session_routes.py index 4dbacde0d..267dbe4b7 100644 --- a/routes/session_routes.py +++ b/routes/session_routes.py @@ -543,22 +543,25 @@ def setup_session_routes(session_manager: SessionManager, config: dict, webhook_ ids = body.get("ids", []) except Exception: ids = [] + deleted_count = 0 for sid in ids: try: _verify_session_owner(request, sid, session_manager) - session_manager.delete_session(sid) + + # Enforce "starred" protection consistent with single-session delete db = SessionLocal() try: - db.query(_CM).filter(_CM.session_id == sid).delete() - db.query(DbSession).filter(DbSession.id == sid).delete() - db.commit() - except Exception: - db.rollback() + db_sess = db.query(DbSession).filter(DbSession.id == sid).first() + if db_sess and db_sess.is_important: + continue finally: db.close() + + if session_manager.delete_session(sid): + deleted_count += 1 except Exception: pass - return {"deleted": len(ids)} + return {"deleted": deleted_count} @router.delete("/session/{sid}") def delete_session(request: Request, sid: str): diff --git a/src/event_bus.py b/src/event_bus.py index dea8b3cf8..8bdb889a0 100644 --- a/src/event_bus.py +++ b/src/event_bus.py @@ -105,12 +105,6 @@ async def _handle_event(event_name: str, owner: Optional[str] = None): db.commit() # Fire the task if _task_scheduler: - if task.next_run and task.next_run > datetime.utcnow(): - logger.info( - f"Event '{event_name}' reached task '{task.name}', " - f"but it is already deferred until {task.next_run}" - ) - continue logger.info(f"Event '{event_name}' triggered task '{task.name}' (every {threshold})") await _task_scheduler.run_task_now(task.id) else: From b3ed60e95a84606bab6fd93c45487154b0905fc5 Mon Sep 17 00:00:00 2001 From: Muhammad-Ikhwan-Fathulloh Date: Tue, 16 Jun 2026 23:11:30 +0700 Subject: [PATCH 2/3] fix: optimize upload manifest performance and fix owner rename bug --- src/upload_handler.py | 61 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/upload_handler.py b/src/upload_handler.py index 4c4e526bc..78575588b 100644 --- a/src/upload_handler.py +++ b/src/upload_handler.py @@ -112,6 +112,10 @@ class UploadHandler: except Exception: self.file_detector = None logger.warning("python-magic not available, falling back to basic detection") + + # In-memory index cache to avoid O(N) disk I/O on every request + self._index_cache: Optional[Dict[str, Any]] = None + self._index_mtime: float = 0.0 def inside_base_dir(self, path: str) -> bool: """Check if path is inside base directory""" @@ -317,6 +321,13 @@ class UploadHandler: except OSError: pass os.replace(tmp, path) + # Update cache if this is the main index + if path.endswith("uploads.json"): + self._index_cache = data + try: + self._index_mtime = os.path.getmtime(path) + except OSError: + self._index_mtime = time.time() except Exception: try: os.unlink(tmp) @@ -325,22 +336,40 @@ class UploadHandler: raise def _load_upload_index(self) -> Dict[str, Any]: + """Load the upload index from disk/cache. Uses mtime-based validation + to avoid redundant parsing on hot paths. + """ uploads_db_path = os.path.join(self.upload_dir, "uploads.json") if not os.path.exists(uploads_db_path): + self._index_cache = {} + self._index_mtime = 0.0 return {} + + # Check cache validity + try: + mtime = os.path.getmtime(uploads_db_path) + if self._index_cache is not None and mtime <= self._index_mtime: + return self._index_cache + except OSError: + mtime = 0.0 + # Try the live file first, fall back to the .bak sibling if the - # live file is truncated/corrupted (e.g. a previous writer was - # SIGKILL'd mid-rename before the new code path was deployed). + # live file is truncated/corrupted. for candidate in (uploads_db_path, uploads_db_path + ".bak"): if not os.path.exists(candidate): continue try: with open(candidate, "r", encoding="utf-8") as f: data = json.load(f) - return data if isinstance(data, dict) else {} + if isinstance(data, dict): + self._index_cache = data + self._index_mtime = mtime + return data except Exception as e: logger.warning(f"Failed to read uploads database ({candidate}): {e}") continue + + self._index_cache = {} return {} def get_upload_info(self, upload_id: str) -> Optional[Dict[str, Any]]: @@ -353,14 +382,23 @@ class UploadHandler: return None def _renamed_upload_index_key(self, key: str, info: Dict[str, Any], old_owner: str, new_owner: str) -> str: - """Return the storage key to use after renaming an owned upload row.""" - if isinstance(key, str) and ":" in key: - owner_part, rest = key.split(":", 1) - if owner_part.strip().lower() == old_owner: - return f"{new_owner}:{rest}" + """Return the storage key to use after renaming an owned upload row. + + Harden against usernames with colons by using the explicit metadata + fields instead of trying to parse the key string. + """ file_hash = info.get("hash") if file_hash: return f"{new_owner}:{file_hash}" + + # Fallback for rows without an explicit hash (should not happen in modern Odysseus) + if isinstance(key, str) and ":" in key: + # Join all but the last part if there are multiple colons + parts = key.rsplit(":", 1) + if len(parts) == 2: + owner_part, rest = parts[0], parts[1] + if owner_part.strip().lower() == old_owner.strip().lower(): + return f"{new_owner}:{rest}" return key def _unique_upload_index_key(self, base_key: str, used_keys: set, reserved_keys: set, info: Dict[str, Any]) -> str: @@ -543,11 +581,8 @@ class UploadHandler: total_size = 0 file_types = {} - uploads_db_path = os.path.join(self.upload_dir, "uploads.json") - if os.path.exists(uploads_db_path): - with open(uploads_db_path, "r", encoding="utf-8") as f: - files = json.load(f) - + files = self._load_upload_index() + if files: total_files = len(files) for file_info in files.values(): total_size += file_info.get("size", 0) From 626414584b1efd16b872ec83ff10f5ed872f21ac Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira Date: Fri, 26 Jun 2026 18:01:04 +0100 Subject: [PATCH 3/3] fix(upload): remove trailing whitespace --- src/upload_handler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/upload_handler.py b/src/upload_handler.py index 78575588b..1f24c6263 100644 --- a/src/upload_handler.py +++ b/src/upload_handler.py @@ -112,7 +112,7 @@ class UploadHandler: except Exception: self.file_detector = None logger.warning("python-magic not available, falling back to basic detection") - + # In-memory index cache to avoid O(N) disk I/O on every request self._index_cache: Optional[Dict[str, Any]] = None self._index_mtime: float = 0.0 @@ -368,7 +368,7 @@ class UploadHandler: except Exception as e: logger.warning(f"Failed to read uploads database ({candidate}): {e}") continue - + self._index_cache = {} return {} @@ -390,7 +390,7 @@ class UploadHandler: file_hash = info.get("hash") if file_hash: return f"{new_owner}:{file_hash}" - + # Fallback for rows without an explicit hash (should not happen in modern Odysseus) if isinstance(key, str) and ":" in key: # Join all but the last part if there are multiple colons