205 lines
6.4 KiB
Rust
205 lines
6.4 KiB
Rust
#[derive(Clone)]
|
|
pub struct DatabaseBackend {
|
|
pub db_url: String,
|
|
pub sqlite: Option<sqlx::Pool<sqlx::Sqlite>>,
|
|
pub postgres: Option<sqlx::Pool<sqlx::Postgres>>,
|
|
}
|
|
|
|
pub fn ensure_file_exists(path: &str) {
|
|
// Check if the file exists
|
|
if !std::path::Path::new(path).exists() {
|
|
// If the file does not exist, create an empty one
|
|
match std::fs::File::create(path) {
|
|
Ok(_) => log::info!("Created {path}"),
|
|
Err(e) => log::error!("Failed to create file: {}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DatabaseBackend {
|
|
pub async fn new(db_url: &str) -> Self {
|
|
let mut sqlite = None;
|
|
let mut postgres = None;
|
|
|
|
if db_url.starts_with("postgres") {
|
|
postgres = Some(
|
|
sqlx::postgres::PgPoolOptions::new()
|
|
.max_connections(5)
|
|
.connect(&std::env::var("DATABASE_URL").unwrap())
|
|
.await
|
|
.unwrap(),
|
|
);
|
|
sqlx::migrate!("./migrations")
|
|
.run(postgres.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
} else {
|
|
ensure_file_exists(db_url);
|
|
sqlite = Some(sqlx::sqlite::SqlitePool::connect(db_url).await.unwrap());
|
|
sqlx::migrate!("./migrations")
|
|
.run(sqlite.as_ref().unwrap())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
Self {
|
|
db_url: db_url.to_string(),
|
|
sqlite,
|
|
postgres,
|
|
}
|
|
}
|
|
|
|
pub fn take_db(&self) -> Database {
|
|
Database::new(self.clone())
|
|
}
|
|
|
|
pub async fn query(&self, param: Query) -> Out {
|
|
match param {
|
|
Query::InsertUrl(ref url) => {
|
|
if let Some(postgres) = self.postgres.as_ref() {
|
|
sqlx::query("INSERT INTO urls (url, timestamp) VALUES ($1, CURRENT_TIMESTAMP)")
|
|
.bind(url)
|
|
.execute(postgres)
|
|
.await
|
|
.unwrap();
|
|
} else {
|
|
if let Some(sqlite) = self.sqlite.as_ref() {
|
|
sqlx::query(
|
|
"INSERT INTO urls (url, timestamp) VALUES ($1, CURRENT_TIMESTAMP)",
|
|
)
|
|
.bind(url)
|
|
.execute(sqlite)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
return Out::Ok;
|
|
}
|
|
Query::CheckForUrl(ref url) => {
|
|
let res: (i64,) = if let Some(postgres) = self.postgres.as_ref() {
|
|
sqlx::query_as("SELECT COUNT(*) FROM urls WHERE url = $1")
|
|
.bind(url)
|
|
.fetch_one(postgres)
|
|
.await
|
|
.unwrap()
|
|
} else {
|
|
sqlx::query_as("SELECT COUNT(*) FROM urls WHERE url = $1")
|
|
.bind(url)
|
|
.fetch_one(self.sqlite.as_ref().unwrap())
|
|
.await
|
|
.unwrap()
|
|
};
|
|
|
|
let count: i64 = res.0;
|
|
return Out::Bool(count > 0);
|
|
}
|
|
Query::UpdateNewDownloads(ref module, ref name, ref url) => {
|
|
if let Some(postgres) = self.postgres.as_ref() {
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO item_log (module, name, url, CURRENT_TIMESTAMP)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (module, name, url)
|
|
DO UPDATE SET timestamp = CURRENT_TIMESTAMP
|
|
"#,
|
|
)
|
|
.bind(module)
|
|
.bind(name)
|
|
.bind(url)
|
|
.execute(postgres)
|
|
.await
|
|
.unwrap();
|
|
} else {
|
|
if let Some(sqlite) = self.sqlite.as_ref() {
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO item_log (module, name, url, timestamp)
|
|
VALUES ($1, $2, $3, CURRENT_TIMESTAMP)
|
|
ON CONFLICT (module, name, url)
|
|
DO UPDATE SET timestamp = CURRENT_TIMESTAMP
|
|
"#,
|
|
)
|
|
.bind(module)
|
|
.bind(name)
|
|
.bind(url)
|
|
.execute(sqlite)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
return Out::Ok;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum Query {
|
|
InsertUrl(String),
|
|
CheckForUrl(String),
|
|
UpdateNewDownloads(String, String, String),
|
|
}
|
|
|
|
pub enum Out {
|
|
Ok,
|
|
Bool(bool),
|
|
// Rows(Vec<String>),
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct Database {
|
|
conn: DatabaseBackend,
|
|
}
|
|
|
|
impl Database {
|
|
pub fn new(conn: DatabaseBackend) -> Self {
|
|
Self { conn }
|
|
}
|
|
|
|
/// Insert a URL into the database as already downloaded
|
|
pub fn insert_url(&self, url: &str) {
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
self.conn.query(Query::InsertUrl(url.to_string())).await;
|
|
});
|
|
}
|
|
|
|
/// 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
|
|
/// }
|
|
/// ```
|
|
pub fn check_for_url(&self, url: &str) -> bool {
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
match self.conn.query(Query::CheckForUrl(url.to_string())).await {
|
|
Out::Ok => false,
|
|
Out::Bool(b) => b,
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Keep a record on when download happen.
|
|
/// This takes a `module`, `name` and `url` and saves a timestamp to the db.
|
|
pub fn update_new_downloads(&self, module: &str, name: &str, url: &str) {
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
rt.block_on(async {
|
|
self.conn
|
|
.query(Query::UpdateNewDownloads(
|
|
module.to_string(),
|
|
name.to_string(),
|
|
url.to_string(),
|
|
))
|
|
.await;
|
|
});
|
|
}
|
|
}
|