@@ -15,7 +15,7 @@ use crate::{
1515 actor_request_response,
1616 errors:: EngineError ,
1717 hashes:: InfoHash ,
18- metainfo:: MetaInfo ,
18+ metainfo:: { MetaInfo , TorrentFile } ,
1919 peer:: { Peer , PeerId } ,
2020 protocol:: {
2121 messages:: PeerMessages ,
@@ -41,8 +41,8 @@ actor_request_response!(
4141) ;
4242
4343/// The "top level" struct for torrenting. Handles all
44- /// [Torrent](crate::torrent::Torrent) actors. Note that the engine itself also
45- /// implements the [Actor](kameo::Actor) trait, and consequently behaves like an
44+ /// [Torrent] actors. Note that the engine itself also
45+ /// implements the [Actor] trait, and consequently behaves like an
4646/// actor.
4747pub struct Engine {
4848 /// Listener to wait for incoming TCP connections from peers
@@ -67,6 +67,91 @@ pub struct Engine {
6767 actor_ref : ActorRef < Engine > ,
6868}
6969
70+ impl Engine {
71+ /// Starts the torrenting process for a given torrent. This function
72+ /// automatically contacts trackers and connects to peers. The spawned
73+ /// [Torrent Actor](Torrent) will be controlled by the [Engine].
74+ ///
75+ /// This function accepts the following as input:
76+ /// - A remote URL to a torrent file over HTTP/HTTPS
77+ /// - The path, either absolute or relative, to a local torrent file
78+ /// - A magnet URI
79+ ///
80+ /// If the inputted value is a remote url to a torrent file, this function
81+ /// requests the bytes and deserializes them into a [TorrentFile]. If
82+ /// it isn't, we assume that it is either a magnet URI or a path to a
83+ /// torrent file, and pass the string to [MetaInfo::new].
84+ ///
85+ ///
86+ /// # Examples
87+ ///
88+ /// With a remote torrent file
89+ /// ```no_run
90+ /// use libtortillas::Engine;
91+ ///
92+ /// #[tokio::main]
93+ /// async fn main() {
94+ /// let mut engine = Engine::new(todo!());
95+ /// let torrent_link = "https://example.com/example.torrent";
96+ /// let torrent_key = engine
97+ /// .add_torrent(torrent_link)
98+ /// .await
99+ /// .expect("Failed to add torrent");
100+ ///
101+ /// println!("Started Torrenting: {}", torrent_key);
102+ /// }
103+ /// ```
104+ ///
105+ /// With a magnet URI
106+ /// ```no_run
107+ /// use libtortillas::Engine;
108+ ///
109+ /// #[tokio::main]
110+ /// async fn main() {
111+ /// let mut engine = Engine::new(todo!());
112+ /// let magnet_uri = "magnet:?xt=?????";
113+ /// let torrent_key = engine
114+ /// .add_torrent(magnet_uri)
115+ /// .await
116+ /// .expect("Failed to add torrent");
117+ ///
118+ /// println!("Started Torrenting: {}", torrent_key);
119+ /// }
120+ /// ```
121+ pub async fn add_torrent ( & self , metainfo : & str ) -> Result < InfoHash , EngineError > {
122+ // File paths should either start with "/" or "./", and magnet URIs start
123+ // with "magnet:", so a check like this should be entirely appropriate.
124+ let metainfo = if metainfo. starts_with ( "http" ) {
125+ let torrent_file_bytes = reqwest:: get ( metainfo)
126+ . await
127+ . map_err ( EngineError :: MetaInfoFetchError ) ?
128+ . bytes ( )
129+ . await
130+ . map_err ( EngineError :: MetaInfoFetchError ) ?;
131+ let torrent_file = TorrentFile :: parse ( & torrent_file_bytes) ;
132+ torrent_file. map_err ( |_| {
133+ error ! ( remote_url = metainfo) ;
134+ EngineError :: MetaInfoDeserializeError
135+ } ) ?
136+ } else {
137+ MetaInfo :: new ( metainfo. into ( ) ) . await . map_err ( |_| {
138+ error ! ( magnet_uri_or_file = metainfo) ;
139+ EngineError :: MetaInfoDeserializeError
140+ } ) ?
141+ } ;
142+
143+ let info_hash = metainfo. info_hash ( ) . map_err ( EngineError :: Other ) ?;
144+
145+ self
146+ . actor_ref
147+ . tell ( EngineMessage :: Torrent ( Box :: new ( metainfo) ) )
148+ . await
149+ . expect ( "Failed to add torrent" ) ;
150+
151+ Ok ( info_hash)
152+ }
153+ }
154+
70155impl Actor for Engine {
71156 /// TCP socket address for incoming peers, uTP socket address for incoming
72157 /// peers, UDP socket address for UDP trackers.
0 commit comments