Files
ares-next/server/src/store.rs
T

116 lines
4.0 KiB
Rust

use rusqlite::{params, Connection};
use crate::config::Config;
use crate::error::AppResult;
use crate::models::{AnnounceRequest, FileRecord};
pub fn open(cfg: &Config) -> AppResult<Connection> {
if let Some(parent) = std::path::Path::new(&cfg.db_path).parent() {
std::fs::create_dir_all(parent).ok();
}
let db = Connection::open(&cfg.db_path)?;
db.pragma_update(None, "journal_mode", "WAL")?;
db.pragma_update(None, "synchronous", "NORMAL")?;
migrate(&db)?;
Ok(db)
}
pub fn migrate(db: &Connection) -> rusqlite::Result<()> {
db.execute_batch(
r#"
CREATE TABLE IF NOT EXISTS files (
id TEXT PRIMARY KEY,
peer_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
ftype TEXT NOT NULL,
extension TEXT NOT NULL DEFAULT '',
size_bytes INTEGER NOT NULL,
hash TEXT NOT NULL UNIQUE,
license TEXT NOT NULL DEFAULT 'authored',
tags TEXT NOT NULL DEFAULT '[]',
created_at INTEGER NOT NULL
);
CREATE VIRTUAL TABLE IF NOT EXISTS files_fts USING fts5(
name, description, tags,
content='files', content_rowid='rowid',
tokenize='unicode61'
);
CREATE TRIGGER IF NOT EXISTS files_ai AFTER INSERT ON files BEGIN
INSERT INTO files_fts(rowid, name, description, tags)
VALUES (new.rowid, new.name, new.description, new.tags);
END;
CREATE TRIGGER IF NOT EXISTS files_ad AFTER DELETE ON files BEGIN
INSERT INTO files_fts(files_fts, rowid, name, description, tags)
VALUES ('delete', old.rowid, old.name, old.description, old.tags);
END;
"#,
)
}
pub fn insert_file(db: &Connection, req: &AnnounceRequest) -> rusqlite::Result<String> {
let id = format!("{}::{}", req.peer_id, &req.hash[..12]);
let tags_json = serde_json::to_string(&req.tags).unwrap_or_else(|_| "[]".into());
db.execute(
"INSERT INTO files (id, peer_id, name, description, ftype, extension, size_bytes, hash, license, tags, created_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
params![
id, req.peer_id, req.name, req.description, req.ftype, req.extension,
req.size_bytes, req.hash, req.license, tags_json,
chrono::Utc::now().timestamp(),
],
)?;
Ok(id)
}
pub fn get_file(db: &Connection, file_id: &str) -> rusqlite::Result<FileRecord> {
db.query_row(
"SELECT id, peer_id, name, description, ftype, extension, size_bytes, hash, license, tags, created_at
FROM files WHERE id = ?1",
[file_id],
FileRecord::from_row,
)
}
pub fn search_files(
db: &Connection,
q: &str,
ftype: Option<&str>,
limit: i64,
offset: i64,
) -> rusqlite::Result<Vec<FileRecord>> {
let mut sql = String::from(
"SELECT f.id, f.peer_id, f.name, f.description, f.ftype, f.extension,
f.size_bytes, f.hash, f.license, f.tags, f.created_at
FROM files f",
);
let mut params: Vec<Box<dyn rusqlite::ToSql>> = Vec::new();
let mut clauses: Vec<String> = Vec::new();
if let Some(query) = crate::search::fts_query(q) {
sql.push_str(" JOIN files_fts ON files_fts.rowid = f.rowid");
clauses.push("files_fts MATCH ?".to_string());
params.push(Box::new(query));
}
if let Some(ft) = ftype {
params.push(Box::new(ft.to_string()));
clauses.push(format!("f.ftype = ?{}", params.len()));
}
if !clauses.is_empty() {
sql.push_str(" WHERE ");
sql.push_str(&clauses.join(" AND "));
}
params.push(Box::new(limit));
sql.push_str(&format!(" ORDER BY f.created_at DESC LIMIT ?{}", params.len()));
params.push(Box::new(offset));
sql.push_str(&format!(" OFFSET ?{}", params.len()));
let mut stmt = db.prepare(&sql)?;
let rows = stmt.query_map(
rusqlite::params_from_iter(params.iter().map(|p| p.as_ref())),
FileRecord::from_row,
)?;
rows.collect()
}