fud.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use darkfi::{
  19. net::{
  20. session::{SESSION_DIRECT, SESSION_INBOUND},
  21. settings::{MagicBytes, NetworkProfile, Settings as NetSettings},
  22. P2p, P2pPtr,
  23. },
  24. system::{sleep, Publisher, PublisherPtr},
  25. };
  26. use darkfi_serial::{Decodable, Encodable};
  27. use fud::{
  28. event::FudEvent,
  29. proto::ProtocolFud,
  30. resource::ResourceStatus,
  31. settings::Args as FudSettings,
  32. util::{hash_to_string, FileSelection},
  33. Fud,
  34. };
  35. use sled_overlay::sled;
  36. use smol::lock::Mutex;
  37. use std::{
  38. collections::HashSet,
  39. io::Cursor,
  40. path::PathBuf,
  41. sync::{Arc, OnceLock, Weak},
  42. };
  43. use url::Url;
  44. use crate::{
  45. error::{Error, Result},
  46. prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyBool, Role},
  47. scene::{
  48. MethodCall, MethodCallSub, Pimpl, SceneNode, SceneNodePtr, SceneNodeType, SceneNodeWeak,
  49. },
  50. ui::{chatview::FileMessageStatus, OnModify},
  51. ExecutorPtr,
  52. };
  53. use super::PluginSettings;
  54. const P2P_RETRY_TIME: u64 = 20;
  55. #[cfg(target_os = "android")]
  56. mod paths {
  57. use crate::android::{get_appdata_path, get_external_storage_path};
  58. use std::path::PathBuf;
  59. pub fn get_base_path() -> PathBuf {
  60. get_external_storage_path().join("fud")
  61. }
  62. pub fn get_db_path() -> PathBuf {
  63. get_external_storage_path().join("fud/db")
  64. }
  65. pub fn get_downloads_path() -> PathBuf {
  66. get_external_storage_path().join("fud/downloads")
  67. }
  68. pub fn get_use_tor_filename() -> PathBuf {
  69. get_external_storage_path().join("use_tor.txt")
  70. }
  71. pub fn p2p_datastore_path() -> PathBuf {
  72. get_appdata_path().join("fud/p2p")
  73. }
  74. pub fn hostlist_path() -> PathBuf {
  75. get_appdata_path().join("fud/hostlist.tsv")
  76. }
  77. }
  78. #[cfg(not(target_os = "android"))]
  79. mod paths {
  80. use std::path::PathBuf;
  81. pub fn get_base_path() -> PathBuf {
  82. dirs::data_local_dir().unwrap().join("darkfi/app/fud")
  83. }
  84. pub fn get_db_path() -> PathBuf {
  85. dirs::data_local_dir().unwrap().join("darkfi/app/fud/db")
  86. }
  87. pub fn get_downloads_path() -> PathBuf {
  88. dirs::data_local_dir().unwrap().join("darkfi/app/fud/downloads")
  89. }
  90. pub fn get_use_tor_filename() -> PathBuf {
  91. dirs::data_local_dir().unwrap().join("darkfi/app/use_tor.txt")
  92. }
  93. pub fn p2p_datastore_path() -> PathBuf {
  94. dirs::cache_dir().unwrap().join("darkfi/app/fud/p2p")
  95. }
  96. pub fn hostlist_path() -> PathBuf {
  97. dirs::cache_dir().unwrap().join("darkfi/app/fud/hostlist.tsv")
  98. }
  99. }
  100. use paths::*;
  101. macro_rules! t { ($($arg:tt)*) => { trace!(target: "plugin::fud", $($arg)*); } }
  102. macro_rules! d { ($($arg:tt)*) => { debug!(target: "plugin::fud", $($arg)*); } }
  103. macro_rules! i { ($($arg:tt)*) => { info!(target: "plugin::fud", $($arg)*); } }
  104. macro_rules! e { ($($arg:tt)*) => { error!(target: "plugin::fud", $($arg)*); } }
  105. pub type FudPluginPtr = Arc<FudPlugin>;
  106. pub struct FudPlugin {
  107. node: SceneNodeWeak,
  108. sg_root: SceneNodePtr,
  109. tasks: OnceLock<Vec<smol::Task<()>>>,
  110. p2p: P2pPtr,
  111. event_pub: PublisherPtr<FudEvent>,
  112. fud: Arc<Fud>,
  113. tracked_files: Arc<Mutex<HashSet<Url>>>,
  114. settings: PluginSettings,
  115. }
  116. impl FudPlugin {
  117. pub async fn new(node: SceneNodeWeak, sg_root: SceneNodePtr, ex: ExecutorPtr) -> Result<Pimpl> {
  118. let node_ref = &node.upgrade().unwrap();
  119. // let fud_node_id = PropertyStr::wrap(node_ref, Role::Internal, "node_id", 0).unwrap();
  120. let fud_ready = PropertyBool::wrap(node_ref, Role::Internal, "ready", 0).unwrap();
  121. fud_ready.set(&mut PropertyAtomicGuard::none(), false);
  122. let setting_root = Arc::new(SceneNode::new("setting", SceneNodeType::SettingRoot));
  123. node_ref.clone().link(setting_root.clone());
  124. let basedir = get_base_path();
  125. i!("Starting Fud backend");
  126. let db_path = get_db_path();
  127. let db = match sled::open(&db_path) {
  128. Ok(db) => db,
  129. Err(err) => {
  130. e!("Sled database '{}' failed to open: {err}!", db_path.display());
  131. return Err(Error::SledDbErr)
  132. }
  133. };
  134. let setting_tree = db.open_tree("settings")?;
  135. let settings = PluginSettings { setting_root, sled_tree: setting_tree };
  136. let mut fud_settings: FudSettings = Default::default();
  137. fud_settings.base_dir = basedir.to_string_lossy().to_string();
  138. let mut p2p_settings: NetSettings = Default::default();
  139. p2p_settings.magic_bytes = MagicBytes([73, 59, 41, 23]);
  140. p2p_settings.app_version = semver::Version::parse("0.5.0").unwrap();
  141. p2p_settings.app_name = "fud".to_string();
  142. if get_use_tor_filename().exists() {
  143. i!("Setup P2P network [tor]");
  144. let mut tor_profile = NetworkProfile::tor_default();
  145. tor_profile.outbound_connect_timeout = 60;
  146. p2p_settings.profiles.insert("tor".to_string(), tor_profile);
  147. p2p_settings.outbound_peer_discovery_cooloff_time = 60;
  148. p2p_settings.seeds.push(
  149. url::Url::parse(
  150. "tor://g7fxelebievvpr27w7gt24lflptpw3jeeuvafovgliq5utdst6xyruyd.onion:24442",
  151. )
  152. .unwrap(),
  153. );
  154. p2p_settings.seeds.push(
  155. url::Url::parse(
  156. "tor://yvklzjnfmwxhyodhrkpomawjcdvcaushsj6torjz2gyd7e25f3gfunyd.onion:24442",
  157. )
  158. .unwrap(),
  159. );
  160. p2p_settings.active_profiles = vec!["tor".to_string()];
  161. fud_settings.pow.btc_electrum_nodes.push(
  162. url::Url::parse(
  163. "tor://hezojf7rda2c33yxgcgcvvsxflechdz5vkm64gwlszgx2r4gc5e42kqd.onion:50001",
  164. )
  165. .unwrap(),
  166. );
  167. fud_settings.pow.btc_electrum_nodes.push(
  168. url::Url::parse(
  169. "tor://n4widoxtm3xpo2fjvtdffhb63q5td3utaxkolaegnpzb5khbwxvdrlad.onion:50001",
  170. )
  171. .unwrap(),
  172. );
  173. fud_settings.pow.btc_electrum_nodes.push(
  174. url::Url::parse(
  175. "tor://duras25aqnp3tnn2zgma7pusms6c7umtunyu2sp6e5byotr3c4c6rzad.onion:50001",
  176. )
  177. .unwrap(),
  178. );
  179. fud_settings.pow.btc_electrum_nodes.push(
  180. url::Url::parse(
  181. "tor://n3dz6thzxobyphuosoftgtf36rnsxlsjknke4yrbdys55zvd7nsx7qid.onion:50001",
  182. )
  183. .unwrap(),
  184. );
  185. } else {
  186. i!("Setup P2P network [clearnet]");
  187. let mut profile = NetworkProfile::default();
  188. profile.outbound_connect_timeout = 40;
  189. profile.channel_handshake_timeout = 30;
  190. p2p_settings.profiles.insert("tcp+tls".to_string(), profile);
  191. p2p_settings.active_profiles = vec!["tcp+tls".to_string()];
  192. p2p_settings.seeds.push(url::Url::parse("tcp+tls://lilith0.dark.fi:24441").unwrap());
  193. p2p_settings.seeds.push(url::Url::parse("tcp+tls://lilith1.dark.fi:24441").unwrap());
  194. fud_settings
  195. .pow
  196. .btc_electrum_nodes
  197. .push(url::Url::parse("tcp+tls://erbium1.sytes.net:50002").unwrap());
  198. fud_settings
  199. .pow
  200. .btc_electrum_nodes
  201. .push(url::Url::parse("tcp+tls://ecdsa.net:110").unwrap());
  202. fud_settings
  203. .pow
  204. .btc_electrum_nodes
  205. .push(url::Url::parse("tcp+tls://electrum.no-ip.org:50002").unwrap());
  206. fud_settings
  207. .pow
  208. .btc_electrum_nodes
  209. .push(url::Url::parse("tcp+tls://electrumx.not.fyi:50002").unwrap());
  210. }
  211. p2p_settings.p2p_datastore = p2p_datastore_path().into_os_string().into_string().ok();
  212. p2p_settings.hostlist = hostlist_path().into_os_string().into_string().ok();
  213. settings.add_p2p_settings(&p2p_settings);
  214. // TODO: add other fud settings
  215. settings.load_settings();
  216. settings.update_p2p_settings(&mut p2p_settings);
  217. let p2p = match P2p::new(p2p_settings.clone(), ex.clone()).await {
  218. Ok(p2p) => p2p,
  219. Err(err) => {
  220. e!("Create p2p network failed: {err}!");
  221. return Err(Error::ServiceFailed)
  222. }
  223. };
  224. p2p.session_direct().start_peer_discovery();
  225. let event_pub = Publisher::new();
  226. let fud: Arc<Fud> =
  227. match Fud::new(fud_settings, p2p.clone(), &db, event_pub.clone(), ex.clone()).await {
  228. Ok(fud) => fud,
  229. Err(err) => {
  230. e!("Cannot create fud instance: {err}");
  231. return Err(Error::ServiceFailed)
  232. }
  233. };
  234. let self_ = Arc::new(Self {
  235. node: node.clone(),
  236. sg_root,
  237. tasks: OnceLock::new(),
  238. p2p,
  239. event_pub,
  240. fud,
  241. tracked_files: Arc::new(Mutex::new(HashSet::new())),
  242. settings,
  243. });
  244. self_.clone().start(ex).await;
  245. Ok(Pimpl::Fud(self_))
  246. }
  247. async fn apply_settings(self_: Arc<Self>, _batch: BatchGuardPtr) {
  248. self_.settings.save_settings();
  249. let p2p_settings = self_.p2p.settings();
  250. let mut write_guard = p2p_settings.write().await;
  251. self_.settings.update_p2p_settings(&mut write_guard);
  252. // TODO: add other fud settings
  253. }
  254. async fn start(self: Arc<Self>, ex: ExecutorPtr) {
  255. i!("Registering Fud protocol");
  256. let registry = self.p2p.protocol_registry();
  257. let fud = self.fud.clone();
  258. let p2p = self.p2p.clone();
  259. registry
  260. .register(SESSION_DIRECT | SESSION_INBOUND, move |channel, _| {
  261. let fud_ = fud.clone();
  262. let p2p_ = p2p.clone();
  263. async move { ProtocolFud::init(fud_, channel, p2p_).await.unwrap() }
  264. })
  265. .await;
  266. let me = Arc::downgrade(&self);
  267. let node = &self.node.upgrade().unwrap();
  268. let method_sub = node.subscribe_method_call("get").unwrap();
  269. let me2 = me.clone();
  270. let get_method_task =
  271. ex.spawn(async move { while Self::process_get(&me2, &method_sub).await {} });
  272. let method_sub = node.subscribe_method_call("track_file").unwrap();
  273. let me2 = me.clone();
  274. let track_file_method_task =
  275. ex.spawn(async move { while Self::process_track_file(&me2, &method_sub).await {} });
  276. let event_pub = self.event_pub.clone();
  277. let me2 = me.clone();
  278. let ev_task = ex.spawn(async move {
  279. Self::process_events(&me2, event_pub).await;
  280. });
  281. let mut on_modify = OnModify::new(ex.clone(), self.node.clone(), me.clone());
  282. // `apply_settings` is triggered if any setting changes
  283. for setting_node in self.settings.setting_root.get_children().iter() {
  284. on_modify.when_change(
  285. setting_node.get_property("value").clone().unwrap(),
  286. Self::apply_settings,
  287. );
  288. }
  289. let fud = self.fud.clone();
  290. let start_task = ex.spawn(async move {
  291. while fud.start().await.is_err() {
  292. sleep(10).await;
  293. }
  294. });
  295. let mut tasks = vec![get_method_task, track_file_method_task, ev_task, start_task];
  296. tasks.append(&mut on_modify.tasks);
  297. self.tasks.set(tasks).unwrap();
  298. i!("Starting Fud P2P");
  299. while let Err(err) = self.p2p.clone().start().await {
  300. // This usually means we cannot listen on the inbound ports
  301. e!("Failed to start fud's p2p network: {err}!");
  302. e!("Usually this means there is another process listening on the same ports.");
  303. e!("Trying again in {P2P_RETRY_TIME} secs");
  304. sleep(P2P_RETRY_TIME).await;
  305. }
  306. }
  307. fn string_to_hash(str: &str) -> std::io::Result<blake3::Hash> {
  308. let mut hash_buf = vec![];
  309. match bs58::decode(str).onto(&mut hash_buf) {
  310. Ok(_) => {}
  311. Err(_) => {
  312. return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud hash"))
  313. }
  314. }
  315. if hash_buf.len() != 32 {
  316. return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud hash"))
  317. }
  318. let mut hash_buf_arr = [0u8; 32];
  319. hash_buf_arr.copy_from_slice(&hash_buf);
  320. Ok(blake3::Hash::from_bytes(hash_buf_arr))
  321. }
  322. fn parse_url(url: &Url) -> std::io::Result<(String, blake3::Hash)> {
  323. let hash_string = url
  324. .host_str()
  325. .map(|s| s.to_string())
  326. .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "Missing fud hash"))?;
  327. let hash = Self::string_to_hash(&hash_string)?;
  328. Ok((hash_string, hash))
  329. }
  330. fn url_to_file_selection(url: &Url) -> FileSelection {
  331. match url.path() {
  332. "/" | "" => FileSelection::All,
  333. path => {
  334. let mut selection = HashSet::new();
  335. selection.insert(PathBuf::from(path.strip_prefix("/").unwrap_or(path)));
  336. FileSelection::Set(selection)
  337. }
  338. }
  339. }
  340. async fn find_urls_by_hash(&self, hash: &blake3::Hash) -> Vec<Url> {
  341. let tracked = self.tracked_files.lock().await;
  342. let hash_str = hash_to_string(hash);
  343. tracked.iter().filter(|url| url.host_str() == Some(hash_str.as_str())).cloned().collect()
  344. }
  345. fn decode_data(
  346. &self,
  347. method_call: &MethodCall,
  348. ) -> (Option<String>, std::io::Result<(blake3::Hash, Url, Option<String>)>) {
  349. fn decode_data(data: &[u8]) -> std::io::Result<(String, Url, Option<String>)> {
  350. let mut cur = Cursor::new(&data);
  351. let url = Url::decode(&mut cur)?;
  352. let Some(hash_string) = url.host_str() else {
  353. return Err(std::io::Error::new(std::io::ErrorKind::Other, "Missing fud hash"))
  354. };
  355. let hash_string = hash_string.to_string();
  356. let err_msg = String::decode(&mut cur).ok();
  357. Ok((hash_string, url, err_msg))
  358. }
  359. let Ok((hash_string, url, err_msg)) = decode_data(&method_call.data) else {
  360. return (None, Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud url")))
  361. };
  362. let Ok(hash) = FudPlugin::string_to_hash(&hash_string) else {
  363. return (
  364. Some(hash_string),
  365. Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud url")),
  366. )
  367. };
  368. (Some(hash_string), Ok((hash, url, err_msg)))
  369. }
  370. async fn process_get(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  371. let Ok(method_call) = sub.receive().await else {
  372. d!("Fud event relayer closed");
  373. return false
  374. };
  375. t!("method called: get({method_call:?})");
  376. assert!(method_call.send_res.is_none());
  377. let Some(self_) = me.upgrade() else {
  378. // Should not happen
  379. panic!("self destroyed before get_method_task was stopped!");
  380. };
  381. let (hash_string, data) = self_.decode_data(&method_call);
  382. if let Err(e) = data {
  383. e!("get() method invalid arg data: {e}");
  384. return true
  385. };
  386. let hash_string = hash_string.unwrap();
  387. let (hash, url, _) = data.unwrap();
  388. if self_.node.upgrade().unwrap().get_property_bool("ready").unwrap() {
  389. let file_selection = Self::url_to_file_selection(&url);
  390. let _ = self_
  391. .fud
  392. .get(&hash, &get_downloads_path().join(&hash_string), file_selection)
  393. .await;
  394. }
  395. true
  396. }
  397. /// Get the current file status for a fileurl, a `None` means it should not
  398. /// be updated
  399. async fn get_status(&self, hash: &blake3::Hash, url: &Url) -> Option<FileMessageStatus> {
  400. let resources = self.fud.resources().await;
  401. let resource = resources.get(hash);
  402. if resource.is_none() {
  403. return Some(FileMessageStatus::Idle)
  404. }
  405. let resource = resource.unwrap();
  406. let mut path = resource.path.clone();
  407. let file_selection = Self::url_to_file_selection(url);
  408. if let FileSelection::Set(selection) = &file_selection {
  409. if let Some(rel_path) = selection.iter().next() {
  410. path = path.join(rel_path);
  411. }
  412. }
  413. let path = path.to_string_lossy().to_string();
  414. if file_selection.is_disjoint(&resource.last_file_selection) {
  415. return None::<FileMessageStatus>
  416. }
  417. let (bytes_downloaded, bytes_total) = self.fud.get_progress(hash, &file_selection).await;
  418. let progress =
  419. if bytes_total != 0 { bytes_downloaded as f32 / bytes_total as f32 * 100. } else { 0. };
  420. match resource.status {
  421. ResourceStatus::Discovering => Some(FileMessageStatus::Downloading { progress }),
  422. ResourceStatus::Downloading => {
  423. if progress < 100. {
  424. Some(FileMessageStatus::Downloading { progress })
  425. } else {
  426. Some(FileMessageStatus::Downloaded { path })
  427. }
  428. }
  429. ResourceStatus::Incomplete(ref err) => {
  430. if progress < 100. {
  431. if let Some(msg) = err {
  432. Some(FileMessageStatus::Error { msg: msg.clone(), progress })
  433. } else {
  434. Some(FileMessageStatus::Error { msg: "incomplete".to_string(), progress })
  435. }
  436. } else {
  437. Some(FileMessageStatus::Downloaded { path })
  438. }
  439. }
  440. ResourceStatus::Verifying => None,
  441. // Seeding status means we have the full resource
  442. // (partial seeding is not supported by fud)
  443. ResourceStatus::Seeding => Some(FileMessageStatus::Downloaded { path }),
  444. }
  445. }
  446. async fn process_track_file(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  447. let Ok(method_call) = sub.receive().await else {
  448. d!("Fud event relayer closed");
  449. return false
  450. };
  451. t!("method called: track_file({method_call:?})");
  452. assert!(method_call.send_res.is_none());
  453. let Some(self_) = me.upgrade() else {
  454. // Should not happen
  455. panic!("self destroyed before track_file_method_task was stopped!");
  456. };
  457. let mut cur = Cursor::new(&method_call.data);
  458. let Ok(url) = Url::decode(&mut cur) else {
  459. e!("track_file() method invalid arg data");
  460. return true
  461. };
  462. self_.track_file(url).await;
  463. true
  464. }
  465. /// Emit file_status_updated signal to all ChatViews
  466. async fn emit_file_status(&self, url: &Url, status: &FileMessageStatus) {
  467. let mut data = vec![];
  468. url.encode(&mut data).unwrap();
  469. status.encode(&mut data).unwrap();
  470. let _ = self.node.upgrade().unwrap().trigger("file_status_updated", data).await;
  471. }
  472. /// Emit error status for a URL
  473. async fn emit_error(&self, url: &Url, msg: String) {
  474. self.emit_file_status(url, &FileMessageStatus::Error { msg, progress: 0. }).await;
  475. }
  476. /// Update tracked files and emit status signal
  477. async fn update_resource(&self, hash: &blake3::Hash) {
  478. let urls = self.find_urls_by_hash(hash).await;
  479. for url in urls {
  480. self.update_fileurl(&url).await;
  481. }
  482. }
  483. async fn update_fileurl(&self, url: &Url) -> bool {
  484. let (_hash_string, hash) = match Self::parse_url(url) {
  485. Ok(h) => h,
  486. Err(err) => {
  487. self.emit_error(url, err.to_string()).await;
  488. return true
  489. }
  490. };
  491. let status = self.get_status(&hash, url).await;
  492. // Emit signal
  493. if let Some(status) = status {
  494. self.emit_file_status(url, &status).await;
  495. return true
  496. }
  497. false
  498. }
  499. /// Emit status for all tracked files
  500. async fn ready_files(&self) {
  501. let tracked = self.tracked_files.lock().await;
  502. let urls: Vec<Url> = tracked.iter().cloned().collect();
  503. drop(tracked);
  504. for url in urls {
  505. let (_hash_string, hash) = match Self::parse_url(&url) {
  506. Ok(h) => h,
  507. Err(err) => {
  508. self.emit_error(&url, err.to_string()).await;
  509. continue
  510. }
  511. };
  512. let status = self.get_status(&hash, &url).await;
  513. if let Some(status) = status {
  514. self.emit_file_status(&url, &status).await;
  515. } else {
  516. self.emit_file_status(&url, &FileMessageStatus::Idle).await;
  517. }
  518. }
  519. }
  520. /// Track a file URL (called when the fileurl_detected signal is emitted)
  521. async fn track_file(&self, url: Url) {
  522. let (_hash_string, _hash) = match Self::parse_url(&url) {
  523. Ok(h) => h,
  524. Err(err) => {
  525. self.emit_error(&url, err.to_string()).await;
  526. return
  527. }
  528. };
  529. if self.node.upgrade().unwrap().get_property_bool("ready").unwrap() {
  530. let updated = self.update_fileurl(&url).await;
  531. if !updated {
  532. self.emit_file_status(&url, &FileMessageStatus::Idle).await;
  533. }
  534. }
  535. let mut tracked = self.tracked_files.lock().await;
  536. tracked.insert(url);
  537. }
  538. async fn process_events(me: &Weak<Self>, publisher: PublisherPtr<FudEvent>) {
  539. let Some(self_) = me.upgrade() else {
  540. // Should not happen
  541. panic!("self destroyed before ev_task was stopped!");
  542. };
  543. let sub = publisher.subscribe().await;
  544. loop {
  545. match sub.receive().await {
  546. FudEvent::Ready => {
  547. let atom = &mut PropertyAtomicGuard::none();
  548. self_
  549. .node
  550. .upgrade()
  551. .unwrap()
  552. .set_property_bool(atom, Role::App, "ready", true)
  553. .unwrap();
  554. self_.ready_files().await;
  555. }
  556. FudEvent::DownloadStarted(ev) => {
  557. self_.update_resource(&ev.resource.hash).await;
  558. }
  559. FudEvent::ChunkDownloadCompleted(ev) => {
  560. self_.update_resource(&ev.resource.hash).await;
  561. }
  562. FudEvent::DownloadCompleted(ev) => {
  563. self_.update_resource(&ev.resource.hash).await;
  564. }
  565. FudEvent::ResourceUpdated(ev) => {
  566. self_.update_resource(&ev.resource.hash).await;
  567. }
  568. FudEvent::DownloadError(ev) => {
  569. self_.update_resource(&ev.hash).await;
  570. }
  571. FudEvent::MissingChunks(ev) => {
  572. self_.update_resource(&ev.hash).await;
  573. }
  574. FudEvent::MetadataNotFound(ev) => {
  575. self_.update_resource(&ev.hash).await;
  576. }
  577. _ => {}
  578. };
  579. }
  580. }
  581. }