Add full-scan mode, persist password in DB, fix channel re-check URLs, SRT subs, cookie clear, UI fixes
- Full-scan download toggle: omits --break-on-existing so all unarchived videos are fetched regardless of order, filling gaps in partial archives - Filter "Aborting remaining downloads" from job logs (break-on-existing noise) - Password hash moved from config.toml to SQLite settings table; survives git checkouts and rebuilds. config.toml and yt-offline.db added to .gitignore; config auto-generated on first run if absent - Channel re-check URLs derived from folder name (/@handle or /channel/UCxxx) instead of info.json's canonical channel_url, preventing duplicate UCxxx folders - SRT subtitles detected by library scanner and served via /api/sub-vtt/* endpoint that converts to WebVTT on the fly; browser <track> element can now play them - Cookie clear button in both desktop and web UI (DELETE /api/cookies) - Per-job X close button on finished download jobs in desktop GUI - Fix widget ID clash when multiple download jobs are shown (push_id per job) - Watched videos excluded from Continue Watching list and badge count - settings table added to SQLite schema (key/value store) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5242b06ee5
commit
1d72069913
10 changed files with 450 additions and 127 deletions
|
|
@ -8,6 +8,7 @@
|
|||
//! |---|---|---|
|
||||
//! | `watched` | `video_id` (PK), `watched_at` | Records videos the user has marked watched |
|
||||
//! | `positions` | `video_id` (PK), `position_secs`, `updated_at` | Stores resume positions |
|
||||
//! | `settings` | `key` (PK), `value` | Persistent app settings (password hash, etc.) |
|
||||
|
||||
use rusqlite::{Connection, Result};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
|
@ -45,11 +46,36 @@ impl Database {
|
|||
video_id TEXT PRIMARY KEY,
|
||||
position_secs REAL NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_setting(&self, key: &str) -> Result<Option<String>> {
|
||||
let mut stmt = self.conn.prepare("SELECT value FROM settings WHERE key = ?1")?;
|
||||
let mut rows = stmt.query([key])?;
|
||||
Ok(rows.next()?.map(|r| r.get(0)).transpose()?)
|
||||
}
|
||||
|
||||
pub fn set_setting(&self, key: &str, value: Option<&str>) -> Result<()> {
|
||||
match value {
|
||||
Some(v) => {
|
||||
self.conn.execute(
|
||||
"INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
|
||||
[key, v],
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
self.conn.execute("DELETE FROM settings WHERE key = ?1", [key])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_watched(&self, video_id: &str, watched: bool) -> Result<()> {
|
||||
if watched {
|
||||
self.conn.execute(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue