fud.rs 22 KB

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