fud.rs 22 KB

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