diff --git a/migrations/mysql/20240304104106_torrust_add_http_seeds_to_torrent.sql b/migrations/mysql/20240304104106_torrust_add_http_seeds_to_torrent.sql new file mode 100644 index 00000000..696f5ad6 --- /dev/null +++ b/migrations/mysql/20240304104106_torrust_add_http_seeds_to_torrent.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_http_seeds ( + http_seed_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + torrent_id INTEGER NOT NULL, + seed_url VARCHAR(256) NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) diff --git a/migrations/sqlite3/20240304104106_torrust_add_http_seeds_to_torrent.sql b/migrations/sqlite3/20240304104106_torrust_add_http_seeds_to_torrent.sql new file mode 100644 index 00000000..04fa7713 --- /dev/null +++ b/migrations/sqlite3/20240304104106_torrust_add_http_seeds_to_torrent.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_http_seeds ( + http_seed_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + torrent_id INTEGER NOT NULL, + seed_url TEXT NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) diff --git a/src/databases/database.rs b/src/databases/database.rs index 18b2a56a..010d208a 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -206,7 +206,14 @@ pub trait Database: Sync + Send { let torrent_announce_urls = self.get_torrent_announce_urls_from_id(db_torrent.torrent_id).await?; - Ok(Torrent::from_database(&db_torrent, &torrent_files, torrent_announce_urls)) + let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + + Ok(Torrent::from_database( + &db_torrent, + &torrent_files, + torrent_announce_urls, + torrent_http_seed_urls, + )) } /// Get `Torrent` from `torrent_id`. @@ -217,7 +224,14 @@ pub trait Database: Sync + Send { let torrent_announce_urls = self.get_torrent_announce_urls_from_id(torrent_id).await?; - Ok(Torrent::from_database(&db_torrent, &torrent_files, torrent_announce_urls)) + let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + + Ok(Torrent::from_database( + &db_torrent, + &torrent_files, + torrent_announce_urls, + torrent_http_seed_urls, + )) } /// It returns the list of all infohashes producing the same canonical @@ -257,6 +271,9 @@ pub trait Database: Sync + Send { /// Get all torrent's announce urls as `Vec>` from `torrent_id`. async fn get_torrent_announce_urls_from_id(&self, torrent_id: i64) -> Result>, Error>; + /// Get all torrent's HTTP seed urls as `Vec>` from `torrent_id`. + async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, Error>; + /// Get `TorrentListing` from `torrent_id`. async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index bb1a0fb4..87f48e26 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -13,7 +13,7 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, Torrent, TorrentFile}; +use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -582,7 +582,31 @@ impl Database for Mysql { return Err(e); } - // Insert tags + // add HTTP seeds + + let insert_torrent_http_seeds_result: Result<(), database::Error> = if let Some(http_seeds) = &torrent.httpseeds { + for seed_url in http_seeds { + let () = query("INSERT INTO torrust_torrent_http_seeds (torrent_id, seed_url) VALUES (?, ?)") + .bind(torrent_id) + .bind(seed_url) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_http_seeds_result { + drop(tx.rollback().await); + return Err(e); + } + + // add tags for tag_id in &metadata.tags { let insert_torrent_tag_result = query("INSERT INTO torrust_torrent_tag_links (torrent_id, tag_id) VALUES (?, ?)") @@ -740,6 +764,15 @@ impl Database for Mysql { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentHttpSeedUrl>("SELECT seed_url FROM torrust_torrent_http_seeds WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| a.seed_url.to_string()).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 4b47dd10..5fe1d398 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -13,7 +13,7 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, Torrent, TorrentFile}; +use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -504,6 +504,8 @@ impl Database for Sqlite { return Err(e); } + // add torrent files + let insert_torrent_files_result = if let Some(length) = torrent.info.length { query("INSERT INTO torrust_torrent_files (md5sum, torrent_id, length) VALUES (?, ?, ?)") .bind(torrent.info.md5sum.clone()) @@ -538,6 +540,8 @@ impl Database for Sqlite { return Err(e); } + // add announce URLs + let insert_torrent_announce_urls_result: Result<(), database::Error> = if let Some(announce_urls) = &torrent.announce_list { // flatten the nested vec (this will however remove the) @@ -572,7 +576,31 @@ impl Database for Sqlite { return Err(e); } - // Insert tags + // add HTTP seeds + + let insert_torrent_http_seeds_result: Result<(), database::Error> = if let Some(http_seeds) = &torrent.httpseeds { + for seed_url in http_seeds { + let () = query("INSERT INTO torrust_torrent_http_seeds (torrent_id, seed_url) VALUES (?, ?)") + .bind(torrent_id) + .bind(seed_url) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_http_seeds_result { + drop(tx.rollback().await); + return Err(e); + } + + // add tags for tag_id in &metadata.tags { let insert_torrent_tag_result = query("INSERT INTO torrust_torrent_tag_links (torrent_id, tag_id) VALUES (?, ?)") @@ -730,6 +758,15 @@ impl Database for Sqlite { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentHttpSeedUrl>("SELECT seed_url FROM torrust_torrent_http_seeds WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| a.seed_url.to_string()).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 35bb3a6a..8c0503d9 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -70,7 +70,12 @@ impl Torrent { /// This function will panic if the `torrent_info.pieces` is not a valid /// hex string. #[must_use] - pub fn from_database(db_torrent: &DbTorrent, torrent_files: &[TorrentFile], torrent_announce_urls: Vec>) -> Self { + pub fn from_database( + db_torrent: &DbTorrent, + torrent_files: &[TorrentFile], + torrent_announce_urls: Vec>, + torrent_http_seed_urls: Vec, + ) -> Self { let info_dict = TorrentInfoDictionary::with( &db_torrent.name, db_torrent.piece_length, @@ -85,7 +90,11 @@ impl Torrent { announce: None, nodes: None, encoding: db_torrent.encoding.clone(), - httpseeds: None, + httpseeds: if torrent_http_seed_urls.is_empty() { + None + } else { + Some(torrent_http_seed_urls) + }, announce_list: Some(torrent_announce_urls), creation_date: db_torrent.creation_date, comment: db_torrent.comment.clone(), @@ -345,6 +354,11 @@ pub struct DbTorrentAnnounceUrl { pub tracker_url: String, } +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct DbTorrentHttpSeedUrl { + pub seed_url: String, +} + #[cfg(test)] mod tests {