77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
import os
|
|
import json
|
|
from typing import List, Optional
|
|
from pathlib import Path
|
|
from .models import RippedThread, HistorySummary
|
|
|
|
class ThreadStorage:
|
|
def __init__(self, data_dir: str = "data"):
|
|
self.data_dir = Path(data_dir)
|
|
self.threads_dir = self.data_dir / "threads"
|
|
self.threads_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def save(self, thread: RippedThread) -> str:
|
|
"""Saves a ripped thread to disk in JSON, MD, and HTML formats."""
|
|
file_path = self.threads_dir / f"{thread.id}.json"
|
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
f.write(thread.model_dump_json(indent=2))
|
|
|
|
# Also save standalone .md and .html files for direct browsing
|
|
md_path = self.threads_dir / f"{thread.id}.md"
|
|
with open(md_path, "w", encoding="utf-8") as f:
|
|
f.write(thread.stitched_markdown)
|
|
|
|
html_path = self.threads_dir / f"{thread.id}.html"
|
|
with open(html_path, "w", encoding="utf-8") as f:
|
|
f.write(thread.stitched_html)
|
|
|
|
txt_path = self.threads_dir / f"{thread.id}.txt"
|
|
with open(txt_path, "w", encoding="utf-8") as f:
|
|
f.write(thread.stitched_text)
|
|
|
|
return thread.id
|
|
|
|
def get(self, thread_id: str) -> Optional[RippedThread]:
|
|
"""Loads a saved ripped thread by ID."""
|
|
file_path = self.threads_dir / f"{thread_id}.json"
|
|
if not file_path.exists():
|
|
return None
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
return RippedThread(**data)
|
|
|
|
def list_history(self) -> List[HistorySummary]:
|
|
"""Lists summaries of all previously ripped threads, newest first."""
|
|
history = []
|
|
for file in self.threads_dir.glob("*.json"):
|
|
try:
|
|
with open(file, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
history.append(
|
|
HistorySummary(
|
|
id=data["id"],
|
|
title=data.get("title", "Untitled Thread"),
|
|
author_handle=data.get("author_handle", "unknown"),
|
|
author_name=data.get("author_name", "Unknown"),
|
|
segment_count=data.get("segment_count", len(data.get("segments", []))),
|
|
total_words=data.get("total_words", 0),
|
|
saved_at=str(data.get("saved_at", ""))[:19],
|
|
original_url=data.get("original_url", "")
|
|
)
|
|
)
|
|
except Exception:
|
|
continue
|
|
# Sort by saved_at descending
|
|
history.sort(key=lambda x: x.saved_at, reverse=True)
|
|
return history
|
|
|
|
def delete(self, thread_id: str) -> bool:
|
|
"""Deletes a saved thread and all associated format files."""
|
|
deleted = False
|
|
for ext in [".json", ".md", ".html", ".txt"]:
|
|
f = self.threads_dir / f"{thread_id}{ext}"
|
|
if f.exists():
|
|
f.unlink()
|
|
deleted = True
|
|
return deleted
|