2024-03-10 04:52:50 +01:00
|
|
|
use jobdispatcher::{JobDispatcher, JobOrder};
|
2024-03-11 11:15:14 +01:00
|
|
|
use rusqlite::{Connection, OptionalExtension};
|
2024-03-10 04:52:50 +01:00
|
|
|
use std::sync::{mpsc::Receiver, Arc};
|
2024-03-07 16:18:47 +01:00
|
|
|
|
2024-03-10 04:52:50 +01:00
|
|
|
pub struct DatabaseBackend {
|
|
|
|
pub file: String,
|
|
|
|
pub conn: Connection,
|
|
|
|
pub dispatcher: Arc<JobDispatcher<Query, Out>>,
|
|
|
|
pub recv: Receiver<JobOrder<Query, Out>>,
|
2024-03-07 16:18:47 +01:00
|
|
|
}
|
|
|
|
|
2024-03-10 04:52:50 +01:00
|
|
|
impl DatabaseBackend {
|
2024-03-07 16:18:47 +01:00
|
|
|
pub fn new(file: &str) -> Self {
|
2024-03-10 04:52:50 +01:00
|
|
|
let (dispatcher, recv) = jobdispatcher::JobDispatcher::<Query, Out>::new();
|
2024-03-07 16:18:47 +01:00
|
|
|
let conn = Connection::open(file).unwrap();
|
2024-03-10 04:52:50 +01:00
|
|
|
|
2024-03-07 16:18:47 +01:00
|
|
|
conn.execute(
|
|
|
|
"CREATE TABLE IF NOT EXISTS urls (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
url TEXT NOT NULL,
|
|
|
|
timestamp TEXT NOT NULL
|
|
|
|
)",
|
|
|
|
[],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
2024-03-11 11:15:14 +01:00
|
|
|
conn.execute(
|
|
|
|
"CREATE TABLE IF NOT EXISTS item_log (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
module TEXT NOT NULL,
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
url TEXT NOT NULL,
|
|
|
|
timestamp TEXT NOT NULL
|
|
|
|
)",
|
|
|
|
[],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
2024-03-10 04:52:50 +01:00
|
|
|
let dispatcher = Arc::new(dispatcher);
|
2024-03-07 16:18:47 +01:00
|
|
|
Self {
|
|
|
|
file: file.to_string(),
|
2024-03-10 04:52:50 +01:00
|
|
|
conn,
|
|
|
|
dispatcher,
|
|
|
|
recv,
|
2024-03-07 16:18:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-10 04:52:50 +01:00
|
|
|
pub fn take_db(&self) -> Database {
|
|
|
|
Database::new(self.dispatcher.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run(&self) {
|
|
|
|
while let Ok(job) = self.recv.recv() {
|
|
|
|
match job.param {
|
|
|
|
Query::InsertUrl(ref url) => {
|
|
|
|
let timestamp = chrono::Local::now().to_rfc3339();
|
|
|
|
self.conn
|
|
|
|
.execute(
|
|
|
|
"INSERT INTO urls (url, timestamp) VALUES (?, ?)",
|
|
|
|
[url, ×tamp],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
job.done(Out::Ok);
|
|
|
|
}
|
|
|
|
Query::CheckForUrl(ref url) => {
|
2024-03-11 11:15:14 +01:00
|
|
|
let mut stmt = self
|
|
|
|
.conn
|
2024-03-10 04:52:50 +01:00
|
|
|
.prepare("SELECT COUNT(*) FROM urls WHERE url = ?")
|
|
|
|
.unwrap();
|
|
|
|
let count: i64 = stmt.query_row([url], |row| row.get(0)).unwrap();
|
|
|
|
job.done(Out::Bool(count > 0));
|
|
|
|
}
|
2024-03-11 11:15:14 +01:00
|
|
|
Query::UpdateNewDownloads(ref module, ref name, ref url) => {
|
|
|
|
let timestamp = chrono::Local::now().to_rfc3339();
|
|
|
|
|
|
|
|
// Check if the entry exists
|
|
|
|
let existing_timestamp: Option<String> = self.conn.query_row(
|
|
|
|
"SELECT timestamp FROM item_log WHERE module = ? AND name = ? AND url = ?",
|
|
|
|
[module, name, url],
|
|
|
|
|row| row.get(0)
|
|
|
|
).optional().unwrap();
|
|
|
|
|
|
|
|
if existing_timestamp.is_some() {
|
|
|
|
// Entry exists, update timestamp
|
|
|
|
self.conn.execute(
|
|
|
|
"UPDATE item_log SET timestamp = ? WHERE module = ? AND name = ? AND url = ?",
|
|
|
|
[×tamp, module, name, url]
|
|
|
|
).unwrap();
|
|
|
|
} else {
|
|
|
|
// Entry doesn't exist, insert new row
|
|
|
|
self.conn.execute(
|
|
|
|
"INSERT INTO item_log (module, name, url, timestamp) VALUES (?, ?, ?, ?)",
|
|
|
|
[module, name, url, ×tamp]
|
|
|
|
).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
job.done(Out::Ok);
|
|
|
|
}
|
2024-03-10 04:52:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum Query {
|
|
|
|
InsertUrl(String),
|
|
|
|
CheckForUrl(String),
|
2024-03-11 11:15:14 +01:00
|
|
|
UpdateNewDownloads(String, String, String),
|
2024-03-10 04:52:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum Out {
|
|
|
|
Ok,
|
|
|
|
Bool(bool),
|
|
|
|
// Rows(Vec<String>),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Database {
|
|
|
|
conn: Arc<JobDispatcher<Query, Out>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Database {
|
|
|
|
pub fn new(conn: Arc<JobDispatcher<Query, Out>>) -> Self {
|
|
|
|
Self { conn }
|
2024-03-07 16:18:47 +01:00
|
|
|
}
|
|
|
|
|
2024-06-02 23:04:09 +02:00
|
|
|
/// Insert a URL into the database as already downloaded
|
2024-03-10 04:52:50 +01:00
|
|
|
pub fn insert_url(&self, url: &str) {
|
|
|
|
self.conn.send(Query::InsertUrl(url.to_string()));
|
|
|
|
}
|
|
|
|
|
2024-06-02 23:04:09 +02:00
|
|
|
/// Check if a URL is already in the database
|
|
|
|
///
|
|
|
|
/// # Return
|
|
|
|
/// Returns `true` if already present, `false` otherwise
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// You could use this function like that:
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// if !db.check_for_url(some_url) {
|
|
|
|
/// // do download
|
|
|
|
/// }
|
|
|
|
/// ```
|
2024-03-10 04:52:50 +01:00
|
|
|
pub fn check_for_url(&self, url: &str) -> bool {
|
|
|
|
match self.conn.send(Query::CheckForUrl(url.to_string())) {
|
|
|
|
Out::Ok => false,
|
|
|
|
Out::Bool(b) => b,
|
|
|
|
}
|
2024-03-07 16:18:47 +01:00
|
|
|
}
|
2024-03-11 11:15:14 +01:00
|
|
|
|
2024-06-02 23:04:09 +02:00
|
|
|
/// Keep a record on when download happen.
|
|
|
|
/// This takes a `module`, `name` and `url` and saves a timestamp to the db.
|
2024-03-11 11:15:14 +01:00
|
|
|
pub fn update_new_downloads(&self, module: &str, name: &str, url: &str) {
|
|
|
|
self.conn.send(Query::UpdateNewDownloads(
|
|
|
|
module.to_string(),
|
|
|
|
name.to_string(),
|
|
|
|
url.to_string(),
|
|
|
|
));
|
|
|
|
}
|
2024-03-07 16:18:47 +01:00
|
|
|
}
|