Spaces:
Running
Running
Download comment_store.py from ZHIWEI666/ComfyUI-Ranking-API: direct link, hf CLI and curl.
- Browser
- Download file 8.67 kB
-
https://huggingface.co/spaces/ZHIWEI666/ComfyUI-Ranking-API/resolve/main/comment_store.py
- Command line
-
hf download hf://spaces/ZHIWEI666/ComfyUI-Ranking-API/comment_store.py
-
curl -L -o comment_store.py https://huggingface.co/spaces/ZHIWEI666/ComfyUI-Ranking-API/resolve/main/comment_store.py
8.67 kB
| """评论记录的锁内修改;路由负责认证、内容存在性及响应契约。""" | |
| import copy | |
| import asyncio | |
| import logging | |
| import threading | |
| from owned_thread import run_owned_thread | |
| from fastapi import HTTPException | |
| import 数据库连接 as db | |
| from resource_deletion import resource_mutation_lock, assert_comment_target_active | |
| from resource_store import ResourceStoreError | |
| COMMENT_MAX_LENGTH = 5000 | |
| MAX_COMMENT_READS = 8 | |
| _comment_read_capacity = threading.BoundedSemaphore(MAX_COMMENT_READS) | |
| _comment_read_gate = threading.Lock() | |
| _comment_read_accepting = True | |
| _comment_read_pending = 0 | |
| _comment_read_closers = 0 | |
| _comment_read_drained = threading.Event() | |
| _comment_read_drained.set() | |
| def start_comment_reads(): | |
| global _comment_read_accepting | |
| with _comment_read_gate: | |
| if _comment_read_pending or _comment_read_closers: | |
| raise RuntimeError("comment reads are still pending") | |
| _comment_read_accepting = True | |
| def stop_comment_reads(): | |
| global _comment_read_accepting | |
| with _comment_read_gate: | |
| _comment_read_accepting = False | |
| async def close_comment_reads(): | |
| global _comment_read_accepting, _comment_read_closers | |
| with _comment_read_gate: | |
| _comment_read_accepting = False | |
| _comment_read_closers += 1 | |
| try: | |
| cancelled = False | |
| while not _comment_read_drained.is_set(): | |
| try: | |
| await asyncio.sleep(.01) | |
| except asyncio.CancelledError: | |
| cancelled = True | |
| if cancelled: | |
| raise asyncio.CancelledError | |
| finally: | |
| with _comment_read_gate: | |
| _comment_read_closers -= 1 | |
| def _item_comments(data, item_id): | |
| if not isinstance(data, dict): | |
| raise HTTPException(status_code=503, detail="评论数据暂不可用") | |
| records = data.get(item_id, []) | |
| if not isinstance(records, list): | |
| raise HTTPException(status_code=503, detail="评论数据暂不可用") | |
| return records | |
| def append_comment(item_id, record, parent_id=None, prepend=False): | |
| def updater(data): | |
| records = _item_comments(data, item_id) | |
| if parent_id: | |
| parent = next((entry for entry in records if isinstance(entry, dict) | |
| and entry.get("id") == parent_id), None) | |
| if parent is None: | |
| raise HTTPException(status_code=404, detail="找不到要回复的评论") | |
| replies = parent.setdefault("replies", []) | |
| if not isinstance(replies, list): | |
| raise HTTPException(status_code=503, detail="评论数据暂不可用") | |
| replies.append(copy.deepcopy(record)) | |
| elif prepend: | |
| records.insert(0, copy.deepcopy(record)) | |
| else: | |
| records.append(copy.deepcopy(record)) | |
| data[item_id] = records | |
| try: | |
| with resource_mutation_lock: | |
| assert_comment_target_active(db, item_id) | |
| db.atomic_update("comments.json", updater, default_data={}) | |
| except ResourceStoreError as error: | |
| raise HTTPException(status_code=error.status_code, detail=error.message) from None | |
| def _find_comment(records, comment_id, depth=0): | |
| if depth > 20 or not isinstance(records, list): | |
| return None | |
| for record in records: | |
| if not isinstance(record, dict): | |
| continue | |
| if record.get("id") == comment_id: | |
| return record | |
| found = _find_comment(record.get("replies", []), comment_id, depth + 1) | |
| if found is not None: | |
| return found | |
| return None | |
| def delete_comment(item_id, comment_id, account, can_moderate=False): | |
| def updater(data): | |
| target = _find_comment(_item_comments(data, item_id), comment_id) | |
| if target is None: | |
| raise HTTPException(status_code=404, detail="找不到该评论") | |
| # 在同一锁内检查最新目标并执行软删除,不能使用先前读取的快照。 | |
| if target.get("author") != account and not can_moderate: | |
| raise HTTPException(status_code=403, detail="无权删除此评论") | |
| target["isDeleted"] = True | |
| target["content"] = "" | |
| db.atomic_update("comments.json", updater, default_data={}) | |
| def increment_comment_count(file_name, item_id): | |
| """派生计数独立更新;失败不能把已提交评论误报为失败或重放评论。""" | |
| def updater(records): | |
| for record in records: | |
| if record.get("id") == item_id: | |
| old_count = record.get("comments", 0) | |
| record["comments"] = (old_count if type(old_count) is int and old_count >= 0 else 0) + 1 | |
| break | |
| try: | |
| db.atomic_update(file_name, updater, default_data=[]) | |
| return True | |
| except Exception: | |
| logging.getLogger(__name__).warning("评论已提交,派生计数暂未更新") | |
| return False | |
| def _visible_count(records, depth=0): | |
| if not isinstance(records, list) or depth > 20: | |
| raise ValueError("Invalid comment thread") | |
| count = 0 | |
| for record in records: | |
| if not isinstance(record, dict) or not isinstance(record.get("id"), str) or not record["id"].strip(): | |
| raise ValueError("Invalid comment record") | |
| deleted = record.get("isDeleted", False) | |
| if type(deleted) is not bool: | |
| raise ValueError("Invalid comment state") | |
| if not deleted: | |
| count += 1 | |
| count += _visible_count(record.get("replies", []), depth + 1) | |
| return count | |
| def _validate_comment_text(records): | |
| for record in records: | |
| if not isinstance(record.get("author"), str) or not isinstance(record.get("content"), str): | |
| raise ValueError("Invalid comment text") | |
| _validate_comment_text(record.get("replies", [])) | |
| def read_comment_thread(item_id): | |
| """严格读取评论快照;存储故障不冒充成功空列表,不改历史主记录。""" | |
| try: | |
| snapshot = db.load_data("comments.json", default_data={}, skip_cache=True, strict=True) | |
| records = _item_comments(snapshot, item_id) | |
| _visible_count(records) | |
| _validate_comment_text(records) | |
| return copy.deepcopy(records) | |
| except Exception: | |
| logging.getLogger(__name__).warning("评论列表无法核实") | |
| raise HTTPException(status_code=503, detail="评论暂时无法读取") from None | |
| def _read_comment_view(item_id): | |
| records = read_comment_thread(item_id) | |
| users = db.load_data("users.json", default_data={}) | |
| result = [] | |
| for record in records: | |
| author_info = users.get(record["author"], {}) | |
| result.append({**record, "author_name": author_info.get("name", record["author"]), | |
| "author_avatar": author_info.get("avatarDataUrl", "")}) | |
| return result | |
| async def read_comment_view(item_id): | |
| """整次评论读取在所属后台工作中执行;繁忙不提交线程池队列。""" | |
| global _comment_read_pending | |
| with _comment_read_gate: | |
| if not _comment_read_accepting or not _comment_read_capacity.acquire(blocking=False): | |
| raise HTTPException(status_code=503, detail="评论暂时无法读取") | |
| _comment_read_pending += 1 | |
| _comment_read_drained.clear() | |
| try: | |
| return await run_owned_thread(_read_comment_view, item_id) | |
| except HTTPException: | |
| raise | |
| except Exception: | |
| logging.getLogger(__name__).warning("评论展示无法核实") | |
| raise HTTPException(status_code=503, detail="评论暂时无法读取") from None | |
| finally: | |
| with _comment_read_gate: | |
| _comment_read_capacity.release() | |
| _comment_read_pending -= 1 | |
| if not _comment_read_pending: | |
| _comment_read_drained.set() | |
| def attach_comment_counts(records): | |
| """只校对响应副本,以评论主记录恢复展示计数,不写内容或资金数据。""" | |
| try: | |
| snapshot = db.load_data("comments.json", default_data={}, skip_cache=True, strict=True) | |
| if not isinstance(snapshot, dict): | |
| raise ValueError("Invalid comment data") | |
| except Exception: | |
| logging.getLogger(__name__).warning("评论计数无法校对,保留已有计数") | |
| return records | |
| for record in records: | |
| try: | |
| count = _visible_count(snapshot.get(record["id"], [])) | |
| except (KeyError, TypeError, ValueError): | |
| continue | |
| record["comments"] = count | |
| return records | |