@@ -7,7 +7,8 @@ use tokio::time::{Duration, sleep};
77use std:: collections:: HashMap ;
88use std:: sync:: Arc ;
99
10- use crate :: database:: magnet:: MagnetOperator ;
10+ use crate :: database:: magnet;
11+ use crate :: routes:: torrent:: TorrentForm ;
1112use crate :: state:: AppState ;
1213
1314/// A magnet link resolver
@@ -21,11 +22,10 @@ use crate::state::AppState;
2122/// for updates on a channel determined on startup. This will
2223/// avoid polluting the logs with queries to read the table.
2324pub struct Resolver {
24- operator : MagnetOperator ,
25+ operator : magnet :: MagnetOperator ,
2526 // Channel to receive new magnets to resolve
2627 receiver : UnboundedReceiver < MagnetLink > ,
2728 session : Arc < Session > ,
28- state : AppState ,
2929 // Keep track of background tasks resolving torrent files from magnets
3030 // In the future, this will allow to cancel/delete tasks.
3131 tasks : HashMap < TorrentID , JoinHandle < TorrentFile > > ,
@@ -46,13 +46,12 @@ impl Resolver {
4646 . unwrap ( ) ;
4747
4848 Self {
49- operator : MagnetOperator {
49+ operator : magnet :: MagnetOperator {
5050 state : state. clone ( ) ,
5151 user : None ,
5252 } ,
5353 receiver,
5454 session,
55- state,
5655 tasks : HashMap :: new ( ) ,
5756 }
5857 }
@@ -131,7 +130,18 @@ impl Resolver {
131130 }
132131 }
133132
134- /// Check if some tasks have finished resolving, and save result in the DB.
133+ /// Check if some magnets have finished resolving, and save result in the DB.
134+ ///
135+ /// In order to avoid losing data, we check in this order:
136+ ///
137+ /// - if the magnet no longer exists, do nothing
138+ /// - if there is already a corresponding torrent, do nothing
139+ /// - save the corresponding torrent
140+ /// - remove the corresponding magnet if the saving was successful
141+ ///
142+ /// On startup, we will further check that no dangling magnets are left due to
143+ /// the program crashing/stopping between saving the torrent and removing
144+ /// the magnet.
135145 pub async fn save_resolved ( & mut self ) {
136146 let finished_ids: Vec < TorrentID > = self
137147 . tasks
@@ -147,6 +157,22 @@ impl Resolver {
147157 for finished_id in finished_ids {
148158 log:: info!( "Magnet {finished_id} has finished resolving. Saving to DB." ) ;
149159
160+ // First verify if someone has in the meantime added the fully resolved torrent?
161+ if let Ok ( _torrent) = self
162+ . operator
163+ . db ( )
164+ . torrent ( )
165+ . get_by_torrent_id ( & finished_id)
166+ . await
167+ {
168+ // Nothing to do, everything is already well and good.
169+ // Well actually, the imported category/folder for the torrent may be different from
170+ // the one requested on the magnet. But since we already have a duplicate check on
171+ // magnet/torrent upload and this case is only for TOCTOU race cases, ignoring
172+ // it seems reasonable.
173+ return ;
174+ }
175+
150176 // Get the raw task handle, removing it from the active tasks
151177 let handle = self . tasks . remove ( & finished_id) . unwrap ( ) ;
152178 let torrent_file = handle. await . unwrap ( ) ;
@@ -164,10 +190,22 @@ impl Resolver {
164190 continue
165191 }
166192
167- }
193+ // Now that we have saved the torrent state, move to the torrent table
194+ let torrent_form = TorrentForm {
195+ category_id : magnet. category_id ,
196+ content_folder_id : magnet. content_folder_id ,
197+ file : axum:: body:: Bytes :: copy_from_slice ( & torrent_file. to_vec ( ) ) ,
198+ } ;
199+
200+ if let Err ( e) = self . operator . db ( ) . torrent ( ) . create ( & torrent_form) . await {
201+ // If the creation was not successful, log it!
202+ log:: error!(
203+ "Failed to move finished magnet {finished_id} to the torrent table: {e}"
204+ ) ;
205+ }
168206
169- // TODO: save to the torrent DB
170- log :: info! ( "Torrent file for magnet {finished_id} has been saved to DB" ) ;
207+ log :: info! ( "Torrent file for magnet {finished_id} has been saved to DB" ) ;
208+ }
171209 }
172210 }
173211}
0 commit comments