fud.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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://wgxxaifz5gv4iggcflyl67lgmsihffs6bbwobqah4np52t3y3olrnpid.onion:9701",
  151. )
  152. .unwrap(),
  153. );
  154. p2p_settings.seeds.push(
  155. url::Url::parse(
  156. "tor://inx5s3pdzddvgb5ii3oydutmbvw6fvor3oqu65wtxl3pyevtvrdn4had.onion:9701",
  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:9700").unwrap());
  193. p2p_settings.seeds.push(url::Url::parse("tcp+tls://lilith1.dark.fi:9700").unwrap());
  194. fud_settings
  195. .pow
  196. .btc_electrum_nodes
  197. .push(url::Url::parse("tcp://fulcrum.grey.pw:50001").unwrap());
  198. fud_settings
  199. .pow
  200. .btc_electrum_nodes
  201. .push(url::Url::parse("tcp://blockstream.info:110").unwrap());
  202. fud_settings
  203. .pow
  204. .btc_electrum_nodes
  205. .push(url::Url::parse("tcp://btc.electroncash.dk:60001").unwrap());
  206. fud_settings
  207. .pow
  208. .btc_electrum_nodes
  209. .push(url::Url::parse("tcp://electrum.direwolfm14.com:50001").unwrap());
  210. fud_settings
  211. .pow
  212. .btc_electrum_nodes
  213. .push(url::Url::parse("tcp://electrum.blockstream.info:50001").unwrap());
  214. }
  215. p2p_settings.p2p_datastore = p2p_datastore_path().into_os_string().into_string().ok();
  216. p2p_settings.hostlist = hostlist_path().into_os_string().into_string().ok();
  217. settings.add_p2p_settings(&p2p_settings);
  218. // TODO: add other fud settings
  219. settings.load_settings();
  220. settings.update_p2p_settings(&mut p2p_settings);
  221. let p2p = match P2p::new(p2p_settings.clone(), ex.clone()).await {
  222. Ok(p2p) => p2p,
  223. Err(err) => {
  224. e!("Create p2p network failed: {err}!");
  225. return Err(Error::ServiceFailed)
  226. }
  227. };
  228. p2p.session_direct().start_peer_discovery();
  229. let event_pub = Publisher::new();
  230. let fud: Arc<Fud> =
  231. match Fud::new(fud_settings, p2p.clone(), &db, event_pub.clone(), ex.clone()).await {
  232. Ok(fud) => fud,
  233. Err(err) => {
  234. e!("Cannot create fud instance: {err}");
  235. return Err(Error::ServiceFailed)
  236. }
  237. };
  238. let self_ = Arc::new(Self {
  239. node: node.clone(),
  240. sg_root,
  241. tasks: OnceLock::new(),
  242. p2p,
  243. event_pub,
  244. fud,
  245. tracked_files: Arc::new(Mutex::new(HashSet::new())),
  246. settings,
  247. });
  248. self_.clone().start(ex).await;
  249. Ok(Pimpl::Fud(self_))
  250. }
  251. async fn apply_settings(self_: Arc<Self>, _batch: BatchGuardPtr) {
  252. self_.settings.save_settings();
  253. let p2p_settings = self_.p2p.settings();
  254. let mut write_guard = p2p_settings.write().await;
  255. self_.settings.update_p2p_settings(&mut write_guard);
  256. // TODO: add other fud settings
  257. }
  258. async fn start(self: Arc<Self>, ex: ExecutorPtr) {
  259. i!("Registering Fud protocol");
  260. let registry = self.p2p.protocol_registry();
  261. let fud = self.fud.clone();
  262. let p2p = self.p2p.clone();
  263. registry
  264. .register(SESSION_DIRECT | SESSION_INBOUND, move |channel, _| {
  265. let fud_ = fud.clone();
  266. let p2p_ = p2p.clone();
  267. async move { ProtocolFud::init(fud_, channel, p2p_).await.unwrap() }
  268. })
  269. .await;
  270. let me = Arc::downgrade(&self);
  271. let node = &self.node.upgrade().unwrap();
  272. let method_sub = node.subscribe_method_call("get").unwrap();
  273. let me2 = me.clone();
  274. let get_method_task =
  275. ex.spawn(async move { while Self::process_get(&me2, &method_sub).await {} });
  276. let method_sub = node.subscribe_method_call("track_file").unwrap();
  277. let me2 = me.clone();
  278. let track_file_method_task =
  279. ex.spawn(async move { while Self::process_track_file(&me2, &method_sub).await {} });
  280. let event_pub = self.event_pub.clone();
  281. let me2 = me.clone();
  282. let ev_task = ex.spawn(async move {
  283. Self::process_events(&me2, event_pub).await;
  284. });
  285. let mut on_modify = OnModify::new(ex.clone(), self.node.clone(), me.clone());
  286. // `apply_settings` is triggered if any setting changes
  287. for setting_node in self.settings.setting_root.get_children().iter() {
  288. on_modify.when_change(
  289. setting_node.get_property("value").clone().unwrap(),
  290. Self::apply_settings,
  291. );
  292. }
  293. let fud = self.fud.clone();
  294. let start_task = ex.spawn(async move {
  295. while fud.start().await.is_err() {
  296. sleep(10).await;
  297. }
  298. });
  299. let mut tasks = vec![get_method_task, track_file_method_task, ev_task, start_task];
  300. tasks.append(&mut on_modify.tasks);
  301. self.tasks.set(tasks).unwrap();
  302. i!("Starting Fud P2P");
  303. while let Err(err) = self.p2p.clone().start().await {
  304. // This usually means we cannot listen on the inbound ports
  305. e!("Failed to start fud's p2p network: {err}!");
  306. e!("Usually this means there is another process listening on the same ports.");
  307. e!("Trying again in {P2P_RETRY_TIME} secs");
  308. sleep(P2P_RETRY_TIME).await;
  309. }
  310. }
  311. fn string_to_hash(str: &str) -> std::io::Result<blake3::Hash> {
  312. let mut hash_buf = vec![];
  313. match bs58::decode(str).onto(&mut hash_buf) {
  314. Ok(_) => {}
  315. Err(_) => {
  316. return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud hash"))
  317. }
  318. }
  319. if hash_buf.len() != 32 {
  320. return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud hash"))
  321. }
  322. let mut hash_buf_arr = [0u8; 32];
  323. hash_buf_arr.copy_from_slice(&hash_buf);
  324. Ok(blake3::Hash::from_bytes(hash_buf_arr))
  325. }
  326. fn parse_url(url: &Url) -> std::io::Result<(String, blake3::Hash)> {
  327. let hash_string = url
  328. .host_str()
  329. .map(|s| s.to_string())
  330. .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "Missing fud hash"))?;
  331. let hash = Self::string_to_hash(&hash_string)?;
  332. Ok((hash_string, hash))
  333. }
  334. fn url_to_file_selection(url: &Url) -> FileSelection {
  335. match url.path() {
  336. "/" | "" => FileSelection::All,
  337. path => {
  338. let mut selection = HashSet::new();
  339. selection.insert(PathBuf::from(path.strip_prefix("/").unwrap_or(path)));
  340. FileSelection::Set(selection)
  341. }
  342. }
  343. }
  344. async fn find_urls_by_hash(&self, hash: &blake3::Hash) -> Vec<Url> {
  345. let tracked = self.tracked_files.lock().await;
  346. let hash_str = hash_to_string(hash);
  347. tracked.iter().filter(|url| url.host_str() == Some(hash_str.as_str())).cloned().collect()
  348. }
  349. fn decode_data(
  350. &self,
  351. method_call: &MethodCall,
  352. ) -> (Option<String>, std::io::Result<(blake3::Hash, Url, Option<String>)>) {
  353. fn decode_data(data: &[u8]) -> std::io::Result<(String, Url, Option<String>)> {
  354. let mut cur = Cursor::new(&data);
  355. let url = Url::decode(&mut cur)?;
  356. let Some(hash_string) = url.host_str() else {
  357. return Err(std::io::Error::new(std::io::ErrorKind::Other, "Missing fud hash"))
  358. };
  359. let hash_string = hash_string.to_string();
  360. let err_msg = String::decode(&mut cur).ok();
  361. Ok((hash_string, url, err_msg))
  362. }
  363. let Ok((hash_string, url, err_msg)) = decode_data(&method_call.data) else {
  364. return (None, Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud url")))
  365. };
  366. let Ok(hash) = FudPlugin::string_to_hash(&hash_string) else {
  367. return (
  368. Some(hash_string),
  369. Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid fud url")),
  370. )
  371. };
  372. (Some(hash_string), Ok((hash, url, err_msg)))
  373. }
  374. async fn process_get(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  375. let Ok(method_call) = sub.receive().await else {
  376. d!("Fud event relayer closed");
  377. return false
  378. };
  379. t!("method called: get({method_call:?})");
  380. assert!(method_call.send_res.is_none());
  381. let Some(self_) = me.upgrade() else {
  382. // Should not happen
  383. panic!("self destroyed before get_method_task was stopped!");
  384. };
  385. let (hash_string, data) = self_.decode_data(&method_call);
  386. if let Err(e) = data {
  387. e!("get() method invalid arg data: {e}");
  388. return true
  389. };
  390. let hash_string = hash_string.unwrap();
  391. let (hash, url, _) = data.unwrap();
  392. if self_.node.upgrade().unwrap().get_property_bool("ready").unwrap() {
  393. let file_selection = Self::url_to_file_selection(&url);
  394. let _ = self_
  395. .fud
  396. .get(&hash, &get_downloads_path().join(&hash_string), file_selection)
  397. .await;
  398. }
  399. true
  400. }
  401. /// Get the current file status for a fileurl, a `None` means it should not
  402. /// be updated
  403. async fn get_status(&self, hash: &blake3::Hash, url: &Url) -> Option<FileMessageStatus> {
  404. let resources = self.fud.resources().await;
  405. let resource = resources.get(hash);
  406. if resource.is_none() {
  407. return Some(FileMessageStatus::Idle)
  408. }
  409. let resource = resource.unwrap();
  410. let mut path = resource.path.clone();
  411. let file_selection = Self::url_to_file_selection(url);
  412. if let FileSelection::Set(selection) = &file_selection {
  413. if let Some(rel_path) = selection.iter().next() {
  414. path = path.join(rel_path);
  415. }
  416. }
  417. let path = path.to_string_lossy().to_string();
  418. if file_selection.is_disjoint(&resource.last_file_selection) {
  419. return None::<FileMessageStatus>
  420. }
  421. let (bytes_downloaded, bytes_total) = self.fud.get_progress(hash, &file_selection).await;
  422. let progress =
  423. if bytes_total != 0 { bytes_downloaded as f32 / bytes_total as f32 * 100. } else { 0. };
  424. match resource.status {
  425. ResourceStatus::Discovering => Some(FileMessageStatus::Downloading { progress }),
  426. ResourceStatus::Downloading => {
  427. if progress < 100. {
  428. Some(FileMessageStatus::Downloading { progress })
  429. } else {
  430. Some(FileMessageStatus::Downloaded { path })
  431. }
  432. }
  433. ResourceStatus::Incomplete(ref err) => {
  434. if progress < 100. {
  435. if let Some(msg) = err {
  436. Some(FileMessageStatus::Error { msg: msg.clone(), progress })
  437. } else {
  438. Some(FileMessageStatus::Error { msg: "incomplete".to_string(), progress })
  439. }
  440. } else {
  441. Some(FileMessageStatus::Downloaded { path })
  442. }
  443. }
  444. ResourceStatus::Verifying => None,
  445. // Seeding status means we have the full resource
  446. // (partial seeding is not supported by fud)
  447. ResourceStatus::Seeding => Some(FileMessageStatus::Downloaded { path }),
  448. }
  449. }
  450. async fn process_track_file(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  451. let Ok(method_call) = sub.receive().await else {
  452. d!("Fud event relayer closed");
  453. return false
  454. };
  455. t!("method called: track_file({method_call:?})");
  456. assert!(method_call.send_res.is_none());
  457. let Some(self_) = me.upgrade() else {
  458. // Should not happen
  459. panic!("self destroyed before track_file_method_task was stopped!");
  460. };
  461. let mut cur = Cursor::new(&method_call.data);
  462. let Ok(url) = Url::decode(&mut cur) else {
  463. e!("track_file() method invalid arg data");
  464. return true
  465. };
  466. self_.track_file(url).await;
  467. true
  468. }
  469. /// Emit file_status_updated signal to all ChatViews
  470. async fn emit_file_status(&self, url: &Url, status: &FileMessageStatus) {
  471. let mut data = vec![];
  472. url.encode(&mut data).unwrap();
  473. status.encode(&mut data).unwrap();
  474. let _ = self.node.upgrade().unwrap().trigger("file_status_updated", data).await;
  475. }
  476. /// Emit error status for a URL
  477. async fn emit_error(&self, url: &Url, msg: String) {
  478. self.emit_file_status(url, &FileMessageStatus::Error { msg, progress: 0. }).await;
  479. }
  480. /// Update tracked files and emit status signal
  481. async fn update_resource(&self, hash: &blake3::Hash) {
  482. let urls = self.find_urls_by_hash(hash).await;
  483. for url in urls {
  484. self.update_fileurl(&url).await;
  485. }
  486. }
  487. async fn update_fileurl(&self, url: &Url) -> bool {
  488. let (_hash_string, hash) = match Self::parse_url(url) {
  489. Ok(h) => h,
  490. Err(err) => {
  491. self.emit_error(url, err.to_string()).await;
  492. return true
  493. }
  494. };
  495. let status = self.get_status(&hash, url).await;
  496. // Emit signal
  497. if let Some(status) = status {
  498. self.emit_file_status(url, &status).await;
  499. return true
  500. }
  501. false
  502. }
  503. /// Emit status for all tracked files
  504. async fn ready_files(&self) {
  505. let tracked = self.tracked_files.lock().await;
  506. let urls: Vec<Url> = tracked.iter().cloned().collect();
  507. drop(tracked);
  508. for url in urls {
  509. let (_hash_string, hash) = match Self::parse_url(&url) {
  510. Ok(h) => h,
  511. Err(err) => {
  512. self.emit_error(&url, err.to_string()).await;
  513. continue
  514. }
  515. };
  516. let status = self.get_status(&hash, &url).await;
  517. if let Some(status) = status {
  518. self.emit_file_status(&url, &status).await;
  519. } else {
  520. self.emit_file_status(&url, &FileMessageStatus::Idle).await;
  521. }
  522. }
  523. }
  524. /// Track a file URL (called when the fileurl_detected signal is emitted)
  525. async fn track_file(&self, url: Url) {
  526. let (_hash_string, _hash) = match Self::parse_url(&url) {
  527. Ok(h) => h,
  528. Err(err) => {
  529. self.emit_error(&url, err.to_string()).await;
  530. return
  531. }
  532. };
  533. if self.node.upgrade().unwrap().get_property_bool("ready").unwrap() {
  534. let updated = self.update_fileurl(&url).await;
  535. if !updated {
  536. self.emit_file_status(&url, &FileMessageStatus::Idle).await;
  537. }
  538. }
  539. let mut tracked = self.tracked_files.lock().await;
  540. tracked.insert(url);
  541. }
  542. async fn process_events(me: &Weak<Self>, publisher: PublisherPtr<FudEvent>) {
  543. let Some(self_) = me.upgrade() else {
  544. // Should not happen
  545. panic!("self destroyed before ev_task was stopped!");
  546. };
  547. let sub = publisher.subscribe().await;
  548. loop {
  549. match sub.receive().await {
  550. FudEvent::Ready => {
  551. let atom = &mut PropertyAtomicGuard::none();
  552. self_
  553. .node
  554. .upgrade()
  555. .unwrap()
  556. .set_property_bool(atom, Role::App, "ready", true)
  557. .unwrap();
  558. self_.ready_files().await;
  559. }
  560. FudEvent::DownloadStarted(ev) => {
  561. self_.update_resource(&ev.resource.hash).await;
  562. }
  563. FudEvent::ChunkDownloadCompleted(ev) => {
  564. self_.update_resource(&ev.resource.hash).await;
  565. }
  566. FudEvent::DownloadCompleted(ev) => {
  567. self_.update_resource(&ev.resource.hash).await;
  568. }
  569. FudEvent::ResourceUpdated(ev) => {
  570. self_.update_resource(&ev.resource.hash).await;
  571. }
  572. FudEvent::DownloadError(ev) => {
  573. self_.update_resource(&ev.hash).await;
  574. }
  575. FudEvent::MissingChunks(ev) => {
  576. self_.update_resource(&ev.hash).await;
  577. }
  578. FudEvent::MetadataNotFound(ev) => {
  579. self_.update_resource(&ev.hash).await;
  580. }
  581. _ => {}
  582. };
  583. }
  584. }
  585. }