Skip to content

Commit d98c61a

Browse files
committed
feat: [#438] persist metainfo field httpseeds. BEP 17
The field `httpseeds` was included in the `Torrent` struct but not persisted into or loaded from database.
1 parent 11519b4 commit d98c61a

6 files changed

+121
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE IF NOT EXISTS torrust_torrent_http_seeds (
2+
http_seed_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
3+
torrent_id INTEGER NOT NULL,
4+
seed_url VARCHAR(256) NOT NULL,
5+
FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE
6+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE IF NOT EXISTS torrust_torrent_http_seeds (
2+
http_seed_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
3+
torrent_id INTEGER NOT NULL,
4+
seed_url TEXT NOT NULL,
5+
FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE
6+
)

src/databases/database.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,14 @@ pub trait Database: Sync + Send {
206206

207207
let torrent_announce_urls = self.get_torrent_announce_urls_from_id(db_torrent.torrent_id).await?;
208208

209-
Ok(Torrent::from_database(&db_torrent, &torrent_files, torrent_announce_urls))
209+
let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?;
210+
211+
Ok(Torrent::from_database(
212+
&db_torrent,
213+
&torrent_files,
214+
torrent_announce_urls,
215+
torrent_http_seed_urls,
216+
))
210217
}
211218

212219
/// Get `Torrent` from `torrent_id`.
@@ -217,7 +224,14 @@ pub trait Database: Sync + Send {
217224

218225
let torrent_announce_urls = self.get_torrent_announce_urls_from_id(torrent_id).await?;
219226

220-
Ok(Torrent::from_database(&db_torrent, &torrent_files, torrent_announce_urls))
227+
let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?;
228+
229+
Ok(Torrent::from_database(
230+
&db_torrent,
231+
&torrent_files,
232+
torrent_announce_urls,
233+
torrent_http_seed_urls,
234+
))
221235
}
222236

223237
/// It returns the list of all infohashes producing the same canonical
@@ -257,6 +271,9 @@ pub trait Database: Sync + Send {
257271
/// Get all torrent's announce urls as `Vec<Vec<String>>` from `torrent_id`.
258272
async fn get_torrent_announce_urls_from_id(&self, torrent_id: i64) -> Result<Vec<Vec<String>>, Error>;
259273

274+
/// Get all torrent's HTTP seed urls as `Vec<Vec<String>>` from `torrent_id`.
275+
async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result<Vec<String>, Error>;
276+
260277
/// Get `TorrentListing` from `torrent_id`.
261278
async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result<TorrentListing, Error>;
262279

src/databases/mysql.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::models::category::CategoryId;
1313
use crate::models::info_hash::InfoHash;
1414
use crate::models::response::TorrentsResponse;
1515
use crate::models::torrent::{Metadata, TorrentListing};
16-
use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, Torrent, TorrentFile};
16+
use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile};
1717
use crate::models::torrent_tag::{TagId, TorrentTag};
1818
use crate::models::tracker_key::TrackerKey;
1919
use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile};
@@ -582,7 +582,31 @@ impl Database for Mysql {
582582
return Err(e);
583583
}
584584

585-
// Insert tags
585+
// add HTTP seeds
586+
587+
let insert_torrent_http_seeds_result: Result<(), database::Error> = if let Some(http_seeds) = &torrent.httpseeds {
588+
for seed_url in http_seeds {
589+
let () = query("INSERT INTO torrust_torrent_http_seeds (torrent_id, seed_url) VALUES (?, ?)")
590+
.bind(torrent_id)
591+
.bind(seed_url)
592+
.execute(&mut *tx)
593+
.await
594+
.map(|_| ())
595+
.map_err(|_| database::Error::Error)?;
596+
}
597+
598+
Ok(())
599+
} else {
600+
Ok(())
601+
};
602+
603+
// rollback transaction on error
604+
if let Err(e) = insert_torrent_http_seeds_result {
605+
drop(tx.rollback().await);
606+
return Err(e);
607+
}
608+
609+
// add tags
586610

587611
for tag_id in &metadata.tags {
588612
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 {
740764
.map_err(|_| database::Error::TorrentNotFound)
741765
}
742766

767+
async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result<Vec<String>, database::Error> {
768+
query_as::<_, DbTorrentHttpSeedUrl>("SELECT seed_url FROM torrust_torrent_http_seeds WHERE torrent_id = ?")
769+
.bind(torrent_id)
770+
.fetch_all(&self.pool)
771+
.await
772+
.map(|v| v.iter().map(|a| a.seed_url.to_string()).collect())
773+
.map_err(|_| database::Error::TorrentNotFound)
774+
}
775+
743776
async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result<TorrentListing, database::Error> {
744777
query_as::<_, TorrentListing>(
745778
"SELECT

src/databases/sqlite.rs

+39-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::models::category::CategoryId;
1313
use crate::models::info_hash::InfoHash;
1414
use crate::models::response::TorrentsResponse;
1515
use crate::models::torrent::{Metadata, TorrentListing};
16-
use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, Torrent, TorrentFile};
16+
use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile};
1717
use crate::models::torrent_tag::{TagId, TorrentTag};
1818
use crate::models::tracker_key::TrackerKey;
1919
use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile};
@@ -504,6 +504,8 @@ impl Database for Sqlite {
504504
return Err(e);
505505
}
506506

507+
// add torrent files
508+
507509
let insert_torrent_files_result = if let Some(length) = torrent.info.length {
508510
query("INSERT INTO torrust_torrent_files (md5sum, torrent_id, length) VALUES (?, ?, ?)")
509511
.bind(torrent.info.md5sum.clone())
@@ -538,6 +540,8 @@ impl Database for Sqlite {
538540
return Err(e);
539541
}
540542

543+
// add announce URLs
544+
541545
let insert_torrent_announce_urls_result: Result<(), database::Error> = if let Some(announce_urls) = &torrent.announce_list
542546
{
543547
// flatten the nested vec (this will however remove the)
@@ -572,7 +576,31 @@ impl Database for Sqlite {
572576
return Err(e);
573577
}
574578

575-
// Insert tags
579+
// add HTTP seeds
580+
581+
let insert_torrent_http_seeds_result: Result<(), database::Error> = if let Some(http_seeds) = &torrent.httpseeds {
582+
for seed_url in http_seeds {
583+
let () = query("INSERT INTO torrust_torrent_http_seeds (torrent_id, seed_url) VALUES (?, ?)")
584+
.bind(torrent_id)
585+
.bind(seed_url)
586+
.execute(&mut *tx)
587+
.await
588+
.map(|_| ())
589+
.map_err(|_| database::Error::Error)?;
590+
}
591+
592+
Ok(())
593+
} else {
594+
Ok(())
595+
};
596+
597+
// rollback transaction on error
598+
if let Err(e) = insert_torrent_http_seeds_result {
599+
drop(tx.rollback().await);
600+
return Err(e);
601+
}
602+
603+
// add tags
576604

577605
for tag_id in &metadata.tags {
578606
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 {
730758
.map_err(|_| database::Error::TorrentNotFound)
731759
}
732760

761+
async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result<Vec<String>, database::Error> {
762+
query_as::<_, DbTorrentHttpSeedUrl>("SELECT seed_url FROM torrust_torrent_http_seeds WHERE torrent_id = ?")
763+
.bind(torrent_id)
764+
.fetch_all(&self.pool)
765+
.await
766+
.map(|v| v.iter().map(|a| a.seed_url.to_string()).collect())
767+
.map_err(|_| database::Error::TorrentNotFound)
768+
}
769+
733770
async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result<TorrentListing, database::Error> {
734771
query_as::<_, TorrentListing>(
735772
"SELECT

src/models/torrent_file.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ impl Torrent {
7070
/// This function will panic if the `torrent_info.pieces` is not a valid
7171
/// hex string.
7272
#[must_use]
73-
pub fn from_database(db_torrent: &DbTorrent, torrent_files: &[TorrentFile], torrent_announce_urls: Vec<Vec<String>>) -> Self {
73+
pub fn from_database(
74+
db_torrent: &DbTorrent,
75+
torrent_files: &[TorrentFile],
76+
torrent_announce_urls: Vec<Vec<String>>,
77+
torrent_http_seed_urls: Vec<String>,
78+
) -> Self {
7479
let info_dict = TorrentInfoDictionary::with(
7580
&db_torrent.name,
7681
db_torrent.piece_length,
@@ -85,7 +90,11 @@ impl Torrent {
8590
announce: None,
8691
nodes: None,
8792
encoding: db_torrent.encoding.clone(),
88-
httpseeds: None,
93+
httpseeds: if torrent_http_seed_urls.is_empty() {
94+
None
95+
} else {
96+
Some(torrent_http_seed_urls)
97+
},
8998
announce_list: Some(torrent_announce_urls),
9099
creation_date: db_torrent.creation_date,
91100
comment: db_torrent.comment.clone(),
@@ -345,6 +354,11 @@ pub struct DbTorrentAnnounceUrl {
345354
pub tracker_url: String,
346355
}
347356

357+
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
358+
pub struct DbTorrentHttpSeedUrl {
359+
pub seed_url: String,
360+
}
361+
348362
#[cfg(test)]
349363
mod tests {
350364

0 commit comments

Comments
 (0)