lib.rs 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  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 std::{
  19. collections::{HashMap, HashSet},
  20. io::ErrorKind,
  21. path::{Path, PathBuf},
  22. sync::Arc,
  23. };
  24. use sled_overlay::sled;
  25. use smol::{
  26. channel,
  27. fs::{self, OpenOptions},
  28. lock::RwLock,
  29. };
  30. use tracing::{error, info, warn};
  31. use darkfi::{
  32. dht::{tasks as dht_tasks, Dht, DhtHandler, DhtSettings},
  33. geode::{hash_to_string, Chunk, ChunkedStorage, FileSequence, Geode, MAX_CHUNK_SIZE},
  34. net::P2pPtr,
  35. system::{ExecutorPtr, PublisherPtr, StoppableTask},
  36. util::{path::expand_path, time::Timestamp},
  37. Error, Result,
  38. };
  39. use darkfi_sdk::crypto::{schnorr::SchnorrSecret, SecretKey};
  40. use darkfi_serial::{deserialize_async, serialize_async};
  41. /// P2P protocols
  42. pub mod proto;
  43. use proto::FudAnnounce;
  44. /// FudEvent
  45. pub mod event;
  46. use event::{notify_event, FudEvent};
  47. /// Resource definition
  48. pub mod resource;
  49. use resource::{Resource, ResourceStatus, ResourceType};
  50. /// Scrap definition
  51. pub mod scrap;
  52. use scrap::Scrap;
  53. /// JSON-RPC related methods
  54. pub mod rpc;
  55. /// Background tasks
  56. pub mod tasks;
  57. use tasks::start_task;
  58. /// Bitcoin
  59. pub mod bitcoin;
  60. /// PoW
  61. pub mod pow;
  62. use pow::{FudPow, VerifiableNodeData};
  63. /// Equi-X
  64. pub mod equix;
  65. /// Settings and args
  66. pub mod settings;
  67. use settings::Args;
  68. /// Utils
  69. pub mod util;
  70. use util::{create_all_files, get_all_files, FileSelection};
  71. /// Download methods
  72. mod download;
  73. use download::{fetch_chunks, fetch_metadata};
  74. /// [`DhtHandler`] implementation and fud-specific DHT structs
  75. pub mod dht;
  76. use dht::FudSeeder;
  77. use crate::{dht::FudNode, pow::PowSettings};
  78. const SLED_PATH_TREE: &[u8] = b"_fud_paths";
  79. const SLED_FILE_SELECTION_TREE: &[u8] = b"_fud_file_selections";
  80. const SLED_SCRAP_TREE: &[u8] = b"_fud_scraps";
  81. #[derive(Clone, Debug)]
  82. pub struct FudState {
  83. /// Our own [`VerifiableNodeData`]
  84. node_data: VerifiableNodeData,
  85. /// Our secret key (the public key is in `node_data`)
  86. secret_key: SecretKey,
  87. }
  88. pub struct Fud {
  89. state: Arc<RwLock<Option<FudState>>>,
  90. /// The Geode instance
  91. geode: Geode,
  92. /// Default download directory
  93. downloads_path: PathBuf,
  94. /// Chunk transfer timeout in seconds
  95. chunk_timeout: u64,
  96. /// The [`FudPow`] instance
  97. pub pow: Arc<RwLock<FudPow>>,
  98. /// The DHT instance
  99. dht: Arc<Dht<Fud>>,
  100. /// Resources (current status of all downloads/seeds)
  101. resources: Arc<RwLock<HashMap<blake3::Hash, Resource>>>,
  102. /// Sled tree containing "resource hash -> path on the filesystem"
  103. path_tree: sled::Tree,
  104. /// Sled tree containing "resource hash -> file selection". If the file
  105. /// selection is all files of the resource (or if the resource is not a
  106. /// directory), the resource does not store its file selection in the tree.
  107. file_selection_tree: sled::Tree,
  108. /// Sled tree containing scraps which are chunks containing data the user
  109. /// did not want to save to files. They also contain data the user wanted
  110. /// otherwise we would not have downloaded the chunk at all.
  111. /// We save scraps to be able to verify integrity even if part of the chunk
  112. /// is not saved to the filesystem in the downloaded files.
  113. /// "chunk/scrap hash -> chunk content"
  114. scrap_tree: sled::Tree,
  115. /// Get requests sender
  116. get_tx: channel::Sender<(blake3::Hash, PathBuf, FileSelection)>,
  117. /// Get requests receiver
  118. get_rx: channel::Receiver<(blake3::Hash, PathBuf, FileSelection)>,
  119. /// Put requests sender
  120. put_tx: channel::Sender<PathBuf>,
  121. /// Put requests receiver
  122. put_rx: channel::Receiver<PathBuf>,
  123. /// Lookup requests sender
  124. lookup_tx: channel::Sender<blake3::Hash>,
  125. /// Lookup requests receiver
  126. lookup_rx: channel::Receiver<blake3::Hash>,
  127. /// Verify node requests sender
  128. verify_node_tx: channel::Sender<FudNode>,
  129. /// Verify node requests receiver
  130. verify_node_rx: channel::Receiver<FudNode>,
  131. /// Currently active downloading tasks (running the `fud.fetch_resource()` method)
  132. fetch_tasks: Arc<RwLock<HashMap<blake3::Hash, Arc<StoppableTask>>>>,
  133. /// Currently active put tasks (running the `fud.insert_resource()` method)
  134. put_tasks: Arc<RwLock<HashMap<PathBuf, Arc<StoppableTask>>>>,
  135. /// Currently active lookup tasks (running the `fud.lookup_value()` method)
  136. lookup_tasks: Arc<RwLock<HashMap<blake3::Hash, Arc<StoppableTask>>>>,
  137. /// Currently active tasks (defined in `tasks`, started with the `start_task` macro)
  138. tasks: Arc<RwLock<HashMap<String, Arc<StoppableTask>>>>,
  139. /// Used to send events to fud clients
  140. event_publisher: PublisherPtr<FudEvent>,
  141. /// Pointer to the P2P network instance
  142. p2p: P2pPtr,
  143. /// Global multithreaded executor reference
  144. pub executor: ExecutorPtr,
  145. }
  146. impl Fud {
  147. pub async fn new(
  148. settings: Args,
  149. p2p: P2pPtr,
  150. sled_db: &sled::Db,
  151. event_publisher: PublisherPtr<FudEvent>,
  152. executor: ExecutorPtr,
  153. ) -> Result<Arc<Self>> {
  154. let dht_settings: DhtSettings = settings.dht.into();
  155. let net_settings_lock = p2p.settings();
  156. let mut net_settings = net_settings_lock.write().await;
  157. // We do not need any outbound slot
  158. net_settings.outbound_connections = 0;
  159. // Default GetAddrsMessage's `max` is dht's `k`
  160. net_settings.getaddrs_max =
  161. Some(net_settings.getaddrs_max.unwrap_or(dht_settings.k.min(u32::MAX as usize) as u32));
  162. drop(net_settings);
  163. let basedir = expand_path(&settings.base_dir)?;
  164. let downloads_path = match settings.downloads_path {
  165. Some(downloads_path) => expand_path(&downloads_path)?,
  166. None => basedir.join("downloads"),
  167. };
  168. let pow_settings: PowSettings = settings.pow.into();
  169. let pow = FudPow::new(pow_settings.clone(), executor.clone());
  170. // Geode
  171. info!(target: "fud::new()", "Instantiating Geode instance");
  172. let geode = Geode::new(&basedir).await?;
  173. // DHT
  174. let dht: Arc<Dht<Fud>> =
  175. Arc::new(Dht::<Fud>::new(&dht_settings, p2p.clone(), executor.clone()).await);
  176. let (get_tx, get_rx) = smol::channel::unbounded();
  177. let (put_tx, put_rx) = smol::channel::unbounded();
  178. let (lookup_tx, lookup_rx) = smol::channel::unbounded();
  179. let (verify_node_tx, verify_node_rx) = smol::channel::unbounded();
  180. let fud = Arc::new(Self {
  181. state: Arc::new(RwLock::new(None)),
  182. geode,
  183. downloads_path,
  184. chunk_timeout: settings.chunk_timeout,
  185. pow: Arc::new(RwLock::new(pow)),
  186. dht: dht.clone(),
  187. path_tree: sled_db.open_tree(SLED_PATH_TREE)?,
  188. file_selection_tree: sled_db.open_tree(SLED_FILE_SELECTION_TREE)?,
  189. scrap_tree: sled_db.open_tree(SLED_SCRAP_TREE)?,
  190. resources: Arc::new(RwLock::new(HashMap::new())),
  191. get_tx,
  192. get_rx,
  193. put_tx,
  194. put_rx,
  195. lookup_tx,
  196. lookup_rx,
  197. verify_node_tx,
  198. verify_node_rx,
  199. fetch_tasks: Arc::new(RwLock::new(HashMap::new())),
  200. put_tasks: Arc::new(RwLock::new(HashMap::new())),
  201. lookup_tasks: Arc::new(RwLock::new(HashMap::new())),
  202. tasks: Arc::new(RwLock::new(HashMap::new())),
  203. event_publisher,
  204. p2p,
  205. executor,
  206. });
  207. *dht.handler.write().await = Arc::downgrade(&fud);
  208. Ok(fud)
  209. }
  210. /// Run the PoW and generate a `VerifiableNodeData`, then start tasks
  211. pub async fn start(self: &Arc<Self>) -> Result<()> {
  212. let mut pow = self.pow.write().await;
  213. if pow.settings.read().await.btc_enabled {
  214. pow.bitcoin_hash_cache.update().await?; // Fetch BTC block hashes
  215. }
  216. let (node_data, secret_key) = pow.generate_node().await?;
  217. info!(target: "fud::init()", "Your node ID: {}", hash_to_string(&node_data.id()));
  218. let mut state = self.state.write().await;
  219. *state = Some(FudState { node_data, secret_key });
  220. drop(state);
  221. drop(pow);
  222. self.start_tasks().await;
  223. Ok(())
  224. }
  225. async fn start_tasks(self: &Arc<Self>) {
  226. let mut tasks = self.tasks.write().await;
  227. start_task!(self, "get", tasks::get_task, tasks);
  228. start_task!(self, "put", tasks::put_task, tasks);
  229. start_task!(self, "events", tasks::handle_dht_events, tasks);
  230. start_task!(self, "DHT events", dht_tasks::events_task::<Fud>, tasks);
  231. start_task!(self, "DHT channel", dht_tasks::channel_task::<Fud>, tasks);
  232. start_task!(self, "DHT cleanup channels", dht_tasks::cleanup_channels_task::<Fud>, tasks);
  233. start_task!(self, "DHT add node", dht_tasks::add_node_task::<Fud>, tasks);
  234. start_task!(self, "DHT refinery", dht_tasks::dht_refinery_task::<Fud>, tasks);
  235. start_task!(
  236. self,
  237. "DHT disconnect inbounds",
  238. dht_tasks::disconnect_inbounds_task::<Fud>,
  239. tasks
  240. );
  241. start_task!(self, "lookup", tasks::lookup_task, tasks);
  242. start_task!(self, "verify node", tasks::verify_node_task, tasks);
  243. start_task!(self, "announce", tasks::announce_seed_task, tasks);
  244. start_task!(self, "node ID", tasks::node_id_task, tasks);
  245. }
  246. /// Verify our resources, add ourselves to the seeders (`dht.hash_table`)
  247. /// for the resources we already have, announce our resources.
  248. async fn init(&self) -> Result<()> {
  249. info!(target: "fud::init()", "Finding resources...");
  250. let mut resources_write = self.resources.write().await;
  251. for result in self.path_tree.iter() {
  252. if result.is_err() {
  253. continue;
  254. }
  255. // Parse hash
  256. let (hash, path) = result.unwrap();
  257. let hash_bytes: [u8; 32] = match hash.to_vec().try_into() {
  258. Ok(v) => v,
  259. Err(_) => continue,
  260. };
  261. let hash = blake3::Hash::from_bytes(hash_bytes);
  262. // Parse path
  263. let path_bytes = path.to_vec();
  264. let path_str = match std::str::from_utf8(&path_bytes) {
  265. Ok(v) => v,
  266. Err(_) => continue,
  267. };
  268. let path: PathBuf = match expand_path(path_str) {
  269. Ok(v) => v,
  270. Err(_) => continue,
  271. };
  272. // Get the file selection from sled, fallback on FileSelection::All
  273. let mut file_selection = FileSelection::All;
  274. if let Ok(Some(fs)) = self.file_selection_tree.get(hash.as_bytes()) {
  275. if let Ok(path_list) = deserialize_async::<Vec<Vec<u8>>>(&fs).await {
  276. file_selection = FileSelection::Set(
  277. path_list
  278. .into_iter()
  279. .filter_map(|bytes| {
  280. std::str::from_utf8(&bytes)
  281. .ok()
  282. .and_then(|path_str| expand_path(path_str).ok())
  283. })
  284. .collect(),
  285. );
  286. }
  287. }
  288. // Add resource
  289. resources_write.insert(
  290. hash,
  291. Resource::new(
  292. hash,
  293. ResourceType::Unknown,
  294. &path,
  295. ResourceStatus::Incomplete,
  296. file_selection,
  297. ),
  298. );
  299. }
  300. drop(resources_write);
  301. info!(target: "fud::init()", "Verifying resources...");
  302. let resources = self.verify_resources(None).await?;
  303. let self_node = self.node().await?;
  304. // Stop here if we have no external address
  305. if self_node.addresses.is_empty() {
  306. return Ok(());
  307. }
  308. // Add our own node as a seeder for the resources we are seeding
  309. for resource in &resources {
  310. if let Ok(seeder) = self.new_seeder(&resource.hash).await {
  311. let self_router_items = vec![seeder];
  312. self.add_value(&resource.hash, &self_router_items).await;
  313. }
  314. }
  315. info!(target: "fud::init()", "Announcing resources...");
  316. for resource in resources {
  317. if let Ok(seeder) = self.new_seeder(&resource.hash).await {
  318. let seeders = vec![seeder];
  319. let _ = self
  320. .dht
  321. .announce(
  322. &resource.hash,
  323. &seeders.clone(),
  324. &FudAnnounce { key: resource.hash, seeders },
  325. )
  326. .await;
  327. }
  328. }
  329. Ok(())
  330. }
  331. /// Get a copy of the current resources
  332. pub async fn resources(&self) -> HashMap<blake3::Hash, Resource> {
  333. let resources = self.resources.read().await;
  334. resources.clone()
  335. }
  336. /// Get resource path from hash using the sled db
  337. pub fn hash_to_path(&self, hash: &blake3::Hash) -> Result<Option<PathBuf>> {
  338. if let Some(value) = self.path_tree.get(hash.as_bytes())? {
  339. let path: PathBuf = expand_path(std::str::from_utf8(&value)?)?;
  340. return Ok(Some(path));
  341. }
  342. Ok(None)
  343. }
  344. /// Get resource hash from path using the sled db
  345. pub fn path_to_hash(&self, path: &Path) -> Result<Option<blake3::Hash>> {
  346. let path_string = path.to_string_lossy().to_string();
  347. let path_bytes = path_string.as_bytes();
  348. for path_item in self.path_tree.iter() {
  349. let (key, value) = path_item?;
  350. if value == path_bytes {
  351. let bytes: &[u8] = &key;
  352. if bytes.len() != 32 {
  353. return Err(Error::Custom(format!(
  354. "Expected a 32-byte BLAKE3, got {} bytes",
  355. bytes.len()
  356. )));
  357. }
  358. let array: [u8; 32] = bytes.try_into().unwrap();
  359. return Ok(Some(array.into()))
  360. }
  361. }
  362. Ok(None)
  363. }
  364. /// Create a new [`dht::FudSeeder`] for own node
  365. pub async fn new_seeder(&self, key: &blake3::Hash) -> Result<FudSeeder> {
  366. let state = self.state.read().await;
  367. if state.is_none() {
  368. return Err(Error::Custom("Fud is not ready yet".to_string()))
  369. }
  370. let state_ = state.clone().unwrap();
  371. drop(state);
  372. let node = self.node().await?;
  373. Ok(FudSeeder {
  374. key: *key,
  375. node: node.clone(),
  376. sig: state_
  377. .secret_key
  378. .sign(&[key.as_bytes().to_vec(), serialize_async(&node).await].concat()),
  379. timestamp: Timestamp::current_time().inner(),
  380. })
  381. }
  382. /// Verify if resources are complete and uncorrupted.
  383. /// If a resource is incomplete or corrupted, its status is changed to Incomplete.
  384. /// If a resource is complete, its status is changed to Seeding.
  385. /// Takes an optional list of resource hashes.
  386. /// If no hash is given (None), it verifies all resources.
  387. /// Returns the list of verified and uncorrupted/complete seeding resources.
  388. pub async fn verify_resources(
  389. &self,
  390. hashes: Option<Vec<blake3::Hash>>,
  391. ) -> Result<Vec<Resource>> {
  392. let mut resources_write = self.resources.write().await;
  393. let update_resource = async |resource: &mut Resource,
  394. status: ResourceStatus,
  395. chunked: Option<&ChunkedStorage>,
  396. total_bytes_downloaded: u64,
  397. target_bytes_downloaded: u64| {
  398. let files = match chunked {
  399. Some(chunked) => resource.get_selected_files(chunked, &resource.file_selection),
  400. None => vec![],
  401. };
  402. let chunk_hashes = match chunked {
  403. Some(chunked) => resource.get_selected_chunks(chunked),
  404. None => HashSet::new(),
  405. };
  406. if let Some(chunked) = chunked {
  407. resource.rtype = match chunked.is_dir() {
  408. false => ResourceType::File,
  409. true => ResourceType::Directory,
  410. };
  411. }
  412. resource.status = status;
  413. resource.total_chunks_count = match chunked {
  414. Some(chunked) => chunked.len() as u64,
  415. None => 0,
  416. };
  417. resource.target_chunks_count = chunk_hashes.len() as u64;
  418. resource.total_chunks_downloaded = match chunked {
  419. Some(chunked) => chunked.local_chunks() as u64,
  420. None => 0,
  421. };
  422. resource.target_chunks_downloaded = match chunked {
  423. Some(chunked) => chunked
  424. .iter()
  425. .filter(|chunk| chunk_hashes.contains(&chunk.hash) && chunk.available)
  426. .count() as u64,
  427. None => 0,
  428. };
  429. resource.total_bytes_size = match chunked {
  430. Some(chunked) => chunked.get_fileseq().len(),
  431. None => 0,
  432. };
  433. resource.target_bytes_size = match chunked {
  434. Some(chunked) => chunked
  435. .get_files()
  436. .iter()
  437. .filter(|(path, _)| files.contains(path))
  438. .map(|(_, size)| size)
  439. .sum(),
  440. None => 0,
  441. };
  442. resource.total_bytes_downloaded = total_bytes_downloaded;
  443. resource.target_bytes_downloaded = target_bytes_downloaded;
  444. notify_event!(self, ResourceUpdated, resource);
  445. };
  446. let mut seeding_resources: Vec<Resource> = vec![];
  447. for (_, mut resource) in resources_write.iter_mut() {
  448. if let Some(ref hashes_list) = hashes {
  449. if !hashes_list.contains(&resource.hash) {
  450. continue;
  451. }
  452. }
  453. match resource.status {
  454. ResourceStatus::Seeding => {}
  455. ResourceStatus::Incomplete => {}
  456. _ => continue,
  457. };
  458. // Make sure the resource is not corrupted or incomplete
  459. let resource_path = match self.hash_to_path(&resource.hash) {
  460. Ok(Some(v)) => v,
  461. Ok(None) | Err(_) => {
  462. update_resource(&mut resource, ResourceStatus::Incomplete, None, 0, 0).await;
  463. continue;
  464. }
  465. };
  466. let mut chunked = match self.geode.get(&resource.hash, &resource_path).await {
  467. Ok(v) => v,
  468. Err(_) => {
  469. update_resource(&mut resource, ResourceStatus::Incomplete, None, 0, 0).await;
  470. continue;
  471. }
  472. };
  473. let verify_res =
  474. self.verify_chunks(resource, &mut chunked, &resource.file_selection).await;
  475. if let Err(e) = verify_res {
  476. error!(target: "fud::verify_resources()", "Error while verifying chunks of {}: {e}", hash_to_string(&resource.hash));
  477. update_resource(&mut resource, ResourceStatus::Incomplete, None, 0, 0).await;
  478. continue;
  479. }
  480. let (total_bytes_downloaded, target_bytes_downloaded) = verify_res.unwrap();
  481. if !chunked.is_complete() {
  482. update_resource(
  483. &mut resource,
  484. ResourceStatus::Incomplete,
  485. Some(&chunked),
  486. total_bytes_downloaded,
  487. target_bytes_downloaded,
  488. )
  489. .await;
  490. continue;
  491. }
  492. update_resource(
  493. &mut resource,
  494. ResourceStatus::Seeding,
  495. Some(&chunked),
  496. total_bytes_downloaded,
  497. target_bytes_downloaded,
  498. )
  499. .await;
  500. seeding_resources.push(resource.clone());
  501. }
  502. Ok(seeding_resources)
  503. }
  504. /// Start downloading a file or directory from the network to `path`.
  505. /// This creates a new task in `fetch_tasks` calling `fetch_resource()`.
  506. /// `files` is the list of files (relative paths) you want to download
  507. /// (if the resource is a directory), None means you want all files.
  508. pub async fn get(&self, hash: &blake3::Hash, path: &Path, files: FileSelection) -> Result<()> {
  509. let fetch_tasks = self.fetch_tasks.read().await;
  510. if fetch_tasks.contains_key(hash) {
  511. return Err(Error::Custom(format!(
  512. "Resource {} is already being downloaded",
  513. hash_to_string(hash)
  514. )))
  515. }
  516. drop(fetch_tasks);
  517. self.get_tx.send((*hash, path.to_path_buf(), files)).await?;
  518. Ok(())
  519. }
  520. /// Try to get the chunked file or directory from geode, if we don't have it
  521. /// then it is fetched from the network using `fetch_metadata()`.
  522. /// If we need to fetch from the network, the seeders we find are sent to
  523. /// `seeders_pub`.
  524. /// The seeder in the returned result is only defined if we fetched from
  525. /// the network.
  526. pub async fn get_metadata(
  527. &self,
  528. hash: &blake3::Hash,
  529. path: &Path,
  530. ) -> Result<(ChunkedStorage, Option<FudSeeder>)> {
  531. match self.geode.get(hash, path).await {
  532. // We already know the metadata
  533. Ok(v) => Ok((v, None)),
  534. // The metadata in geode is invalid or corrupted
  535. Err(Error::GeodeNeedsGc) => todo!(),
  536. // If we could not find the metadata in geode, get it from the network
  537. Err(Error::GeodeFileNotFound) => {
  538. // Find nodes close to the file hash
  539. info!(target: "fud::get_metadata()", "Requested metadata {} not found in Geode, triggering fetch", hash_to_string(hash));
  540. let dht_sub = self.dht.subscribe().await;
  541. if let Err(e) = self.lookup_tx.send(*hash).await {
  542. dht_sub.unsubscribe().await;
  543. return Err(e.into())
  544. }
  545. // Fetch resource metadata
  546. let fetch_res = fetch_metadata(self, hash, path, &dht_sub).await;
  547. dht_sub.unsubscribe().await;
  548. let seeder = fetch_res?;
  549. Ok((self.geode.get(hash, path).await?, Some(seeder)))
  550. }
  551. Err(e) => Err(e),
  552. }
  553. }
  554. /// Download a file or directory from the network to `path`.
  555. /// Called when `get()` creates a new fetch task.
  556. pub async fn fetch_resource(
  557. &self,
  558. hash: &blake3::Hash,
  559. path: &Path,
  560. files: &FileSelection,
  561. ) -> Result<()> {
  562. let hash_bytes = hash.as_bytes();
  563. let path_string = path.to_string_lossy().to_string();
  564. let path_bytes = path_string.as_bytes();
  565. // Macro that acquires a write lock on `self.resources`, updates a
  566. // resource, and returns the resource (dropping the write lock)
  567. macro_rules! update_resource {
  568. ($hash:ident, { $($field:ident = $value:expr $(,)?)* }) => {{
  569. let mut resources_write = self.resources.write().await;
  570. let resource = match resources_write.get_mut($hash) {
  571. Some(resource) => {
  572. $(resource.$field = $value;)* // Apply the field assignments
  573. resource.clone()
  574. }
  575. None => return Ok(()), // Resource was removed, abort
  576. };
  577. resource
  578. }};
  579. }
  580. // Make sure we don't already have another resource on that path
  581. if let Ok(Some(hash_found)) = self.path_to_hash(path) {
  582. if *hash != hash_found {
  583. return Err(Error::Custom(format!(
  584. "There is already another resource on path {path_string}"
  585. )))
  586. }
  587. }
  588. // Add path to the sled db
  589. self.path_tree.insert(hash_bytes, path_bytes)?;
  590. // Add file selection to the sled db
  591. if let FileSelection::Set(selected_files) = files {
  592. let paths: Vec<Vec<u8>> = selected_files
  593. .iter()
  594. .map(|f| f.to_string_lossy().to_string().as_bytes().to_vec())
  595. .collect();
  596. let serialized_paths = serialize_async(&paths).await;
  597. // Abort if the file selection cannot be inserted into sled
  598. if let Err(e) = self.file_selection_tree.insert(hash_bytes, serialized_paths) {
  599. return Err(Error::SledError(e))
  600. }
  601. }
  602. // Add resource to `self.resources`
  603. let resource = Resource::new(
  604. *hash,
  605. ResourceType::Unknown,
  606. path,
  607. ResourceStatus::Discovering,
  608. files.clone(),
  609. );
  610. let mut resources_write = self.resources.write().await;
  611. resources_write.insert(*hash, resource.clone());
  612. drop(resources_write);
  613. // Subscribe to DHT events early for `fetch_chunks()`
  614. let dht_sub = self.dht.subscribe().await;
  615. // Send a DownloadStarted event
  616. notify_event!(self, DownloadStarted, resource);
  617. // Try to get the chunked file or directory from geode
  618. let metadata_result = self.get_metadata(hash, path).await;
  619. if let Err(e) = metadata_result {
  620. // Set resource status to `Incomplete` and send a `MetadataNotFound` event
  621. let resource = update_resource!(hash, { status = ResourceStatus::Incomplete });
  622. notify_event!(self, MetadataNotFound, resource);
  623. dht_sub.unsubscribe().await;
  624. return Err(e)
  625. }
  626. let (mut chunked, metadata_seeder) = metadata_result.unwrap();
  627. // Get a list of all file paths the user wants to fetch
  628. let resources_read = self.resources.read().await;
  629. let resource = match resources_read.get(hash) {
  630. Some(resource) => resource,
  631. None => {
  632. // Resource was removed, abort
  633. dht_sub.unsubscribe().await;
  634. return Ok(())
  635. }
  636. };
  637. let files_vec: Vec<PathBuf> = resource.get_selected_files(&chunked, files);
  638. drop(resources_read);
  639. // Create all files (and all necessary directories)
  640. if let Err(e) = create_all_files(&files_vec).await {
  641. dht_sub.unsubscribe().await;
  642. return Err(e)
  643. }
  644. // Set resource status to `Verifying` and send a `MetadataDownloadCompleted` event
  645. let resource = update_resource!(hash, {
  646. status = ResourceStatus::Verifying,
  647. total_chunks_count = chunked.len() as u64,
  648. total_bytes_size = chunked.get_fileseq().len(),
  649. rtype = match chunked.is_dir() {
  650. false => ResourceType::File,
  651. true => ResourceType::Directory,
  652. },
  653. });
  654. notify_event!(self, MetadataDownloadCompleted, resource);
  655. // Set of all chunks we need locally (including the ones we already have)
  656. let chunk_hashes = resource.get_chunks_of_selection(&chunked, files);
  657. // Write all scraps to make sure the data on the filesystem is correct
  658. if let Err(e) = self.write_scraps(&mut chunked, &chunk_hashes).await {
  659. dht_sub.unsubscribe().await;
  660. return Err(e)
  661. }
  662. // Mark locally available chunks as such
  663. let verify_res = self.verify_chunks(&resource, &mut chunked, files).await;
  664. if let Err(e) = verify_res {
  665. dht_sub.unsubscribe().await;
  666. error!(target: "fud::fetch_resource()", "Error while verifying chunks: {e}");
  667. return Err(e);
  668. }
  669. let (total_bytes_downloaded, target_bytes_downloaded) = verify_res.unwrap();
  670. // Update `total_bytes_size` if the resource is a file
  671. if let ResourceType::File = resource.rtype {
  672. update_resource!(hash, { total_bytes_size = chunked.get_fileseq().len() });
  673. notify_event!(self, ResourceUpdated, resource);
  674. }
  675. // If `chunked` is a file that is bigger than the all its chunks,
  676. // truncate the file to the chunks.
  677. // This fixes two edge-cases: a file that exactly ends at the end of
  678. // a chunk, and a file with no chunk.
  679. if !chunked.is_dir() {
  680. let fs_metadata = fs::metadata(&path).await;
  681. if let Err(e) = fs_metadata {
  682. dht_sub.unsubscribe().await;
  683. return Err(e.into());
  684. }
  685. if fs_metadata.unwrap().len() > (chunked.len() * MAX_CHUNK_SIZE) as u64 {
  686. if let Ok(file) = OpenOptions::new().write(true).create(true).open(path).await {
  687. let _ = file.set_len((chunked.len() * MAX_CHUNK_SIZE) as u64).await;
  688. }
  689. }
  690. }
  691. // Set of all chunks we need locally and their current availability
  692. let chunks: HashSet<Chunk> =
  693. chunked.iter().filter(|c| chunk_hashes.contains(&c.hash)).cloned().collect();
  694. // Set of the chunks we need to download
  695. let mut missing_chunks: HashSet<blake3::Hash> =
  696. chunks.iter().filter(|&c| !c.available).map(|c| c.hash).collect();
  697. // Update the resource with the chunks/bytes counts
  698. update_resource!(hash, {
  699. target_chunks_count = chunks.len() as u64,
  700. total_chunks_downloaded = chunked.local_chunks() as u64,
  701. target_chunks_downloaded = (chunks.len() - missing_chunks.len()) as u64,
  702. target_bytes_size =
  703. chunked.get_fileseq().subset_len(files_vec.into_iter().collect()),
  704. total_bytes_downloaded = total_bytes_downloaded,
  705. target_bytes_downloaded = target_bytes_downloaded,
  706. });
  707. let download_completed = async |chunked: &ChunkedStorage| -> Result<()> {
  708. // Set resource status to `Seeding` or `Incomplete`
  709. let resource = update_resource!(hash, {
  710. status = match chunked.is_complete() {
  711. true => ResourceStatus::Seeding,
  712. false => ResourceStatus::Incomplete,
  713. },
  714. target_chunks_downloaded = chunks.len() as u64,
  715. total_chunks_downloaded = chunked.local_chunks() as u64,
  716. });
  717. // Announce the resource if we have all chunks
  718. if chunked.is_complete() {
  719. if let Ok(seeder) = self.new_seeder(hash).await {
  720. let seeders = vec![seeder];
  721. let self_announce = FudAnnounce { key: *hash, seeders: seeders.clone() };
  722. let _ = self.dht.announce(hash, &seeders, &self_announce).await;
  723. }
  724. }
  725. // Send a DownloadCompleted event
  726. notify_event!(self, DownloadCompleted, resource);
  727. Ok(())
  728. };
  729. // If we don't need to download any chunk
  730. if missing_chunks.is_empty() {
  731. dht_sub.unsubscribe().await;
  732. return download_completed(&chunked).await;
  733. }
  734. // Set resource status to `Downloading` and send a MetadataDownloadCompleted event
  735. let resource = update_resource!(hash, {
  736. status = ResourceStatus::Downloading,
  737. });
  738. notify_event!(self, MetadataDownloadCompleted, resource);
  739. // Start looking up seeders if we did not need to do it for the metadata
  740. if metadata_seeder.is_none() {
  741. if let Err(e) = self.lookup_tx.send(*hash).await {
  742. dht_sub.unsubscribe().await;
  743. return Err(e.into())
  744. }
  745. }
  746. // Fetch missing chunks from seeders
  747. let _ =
  748. fetch_chunks(self, hash, &mut chunked, &dht_sub, metadata_seeder, &mut missing_chunks)
  749. .await;
  750. // We don't need the DHT events sub anymore
  751. dht_sub.unsubscribe().await;
  752. // Get chunked file from geode
  753. let mut chunked = self.geode.get(hash, path).await?;
  754. // Set resource status to `Verifying` and send FudEvent::ResourceUpdated
  755. let resource = update_resource!(hash, { status = ResourceStatus::Verifying });
  756. notify_event!(self, ResourceUpdated, resource);
  757. // Verify all chunks
  758. self.verify_chunks(&resource, &mut chunked, &resource.last_file_selection).await?;
  759. let is_complete =
  760. chunked.iter().filter(|c| chunk_hashes.contains(&c.hash)).all(|c| c.available);
  761. // We fetched all chunks, but the resource is not complete
  762. // (some chunks were missing from all seeders)
  763. if !is_complete {
  764. // Set resource status to `Incomplete`
  765. let resource = update_resource!(hash, { status = ResourceStatus::Incomplete });
  766. // Send a MissingChunks event
  767. notify_event!(self, MissingChunks, resource);
  768. return Ok(());
  769. }
  770. download_completed(&chunked).await
  771. }
  772. async fn write_scraps(
  773. &self,
  774. chunked: &mut ChunkedStorage,
  775. chunk_hashes: &HashSet<blake3::Hash>,
  776. ) -> Result<()> {
  777. // Get all scraps
  778. let mut scraps = HashMap::new();
  779. // TODO: This can be improved to not loop over all chunks
  780. for chunk_hash in chunk_hashes {
  781. let scrap = self.scrap_tree.get(chunk_hash.as_bytes())?;
  782. if scrap.is_none() {
  783. continue;
  784. }
  785. // Verify the scrap we found
  786. let scrap = deserialize_async(scrap.unwrap().as_ref()).await;
  787. if scrap.is_err() {
  788. continue;
  789. }
  790. let scrap: Scrap = scrap.unwrap();
  791. // Add the scrap to the HashMap
  792. scraps.insert(chunk_hash, scrap);
  793. }
  794. // Write all scraps
  795. if !scraps.is_empty() {
  796. info!(target: "fud::write_scraps()", "Writing {} scraps...", scraps.len());
  797. }
  798. for (scrap_hash, mut scrap) in scraps {
  799. let len = scrap.chunk.len();
  800. let write_res = self.geode.write_chunk(chunked, scrap.chunk.clone()).await;
  801. if let Err(e) = write_res {
  802. error!(target: "fud::write_scraps()", "Error rewriting scrap {}: {e}", hash_to_string(scrap_hash));
  803. continue;
  804. }
  805. let (_, chunk_bytes_written) = write_res.unwrap();
  806. // If the whole scrap was written, we can remove it from sled
  807. if chunk_bytes_written == len {
  808. self.scrap_tree.remove(scrap_hash.as_bytes())?;
  809. continue;
  810. }
  811. // Otherwise update the scrap in sled
  812. let chunk_res = self.geode.get_chunk(chunked, scrap_hash).await;
  813. if let Err(e) = chunk_res {
  814. error!(target: "fud::write_scraps()", "Failed to get scrap {}: {e}", hash_to_string(scrap_hash));
  815. continue;
  816. }
  817. scrap.hash_written = blake3::hash(&chunk_res.unwrap());
  818. if let Err(e) =
  819. self.scrap_tree.insert(scrap_hash.as_bytes(), serialize_async(&scrap).await)
  820. {
  821. error!(target: "fud::write_scraps()", "Failed to save chunk {} as a scrap after rewrite: {e}", hash_to_string(scrap_hash));
  822. }
  823. }
  824. Ok(())
  825. }
  826. /// Iterate over chunks and find which chunks are available locally,
  827. /// either in the filesystem (using geode::verify_chunks()) or in scraps.
  828. /// Return the size in bytes of locally available data (downloaded and
  829. /// downloaded+targeted).
  830. pub async fn verify_chunks(
  831. &self,
  832. resource: &Resource,
  833. chunked: &mut ChunkedStorage,
  834. file_selection: &FileSelection,
  835. ) -> Result<(u64, u64)> {
  836. let chunks = chunked.get_chunks().clone();
  837. let mut bytes: HashMap<blake3::Hash, (usize, usize)> = HashMap::new();
  838. // Gather all available chunks
  839. for (chunk_index, chunk) in chunks.iter().enumerate() {
  840. // Read the chunk using the `FileSequence`
  841. let chunk_data =
  842. match self.geode.read_chunk(&mut chunked.get_fileseq_mut(), &chunk_index).await {
  843. Ok(c) => c,
  844. Err(Error::Io(ErrorKind::NotFound)) => continue,
  845. Err(e) => {
  846. warn!(target: "fud::verify_chunks()", "Error while verifying chunks: {e}");
  847. break
  848. }
  849. };
  850. // Perform chunk consistency check
  851. if self.geode.verify_chunk(&chunk.hash, &chunk_data) {
  852. chunked.get_chunk_mut(chunk_index).available = true;
  853. chunked.get_chunk_mut(chunk_index).size = chunk_data.len();
  854. bytes.insert(
  855. chunk.hash,
  856. (
  857. chunk_data.len(),
  858. resource.get_bytes_of_selection(
  859. chunked,
  860. file_selection,
  861. &chunk.hash,
  862. chunk_data.len(),
  863. ),
  864. ),
  865. );
  866. } else {
  867. chunked.get_chunk_mut(chunk_index).available = false;
  868. }
  869. }
  870. // Look for the chunks that are not on the filesystem
  871. let chunks = chunked.get_chunks().clone();
  872. let missing_on_fs: Vec<_> =
  873. chunks.iter().enumerate().filter(|(_, c)| !c.available).collect();
  874. // Look for scraps
  875. for (chunk_index, chunk) in missing_on_fs {
  876. let scrap = self.scrap_tree.get(chunk.hash.as_bytes())?;
  877. if scrap.is_none() {
  878. continue;
  879. }
  880. // Verify the scrap we found
  881. let scrap = deserialize_async(scrap.unwrap().as_ref()).await;
  882. if scrap.is_err() {
  883. continue;
  884. }
  885. let scrap: Scrap = scrap.unwrap();
  886. if blake3::hash(&scrap.chunk) != chunk.hash {
  887. continue;
  888. }
  889. // Check if the scrap is still written on the filesystem
  890. let scrap_chunk =
  891. self.geode.read_chunk(&mut chunked.get_fileseq_mut(), &chunk_index).await;
  892. if scrap_chunk.is_err() {
  893. continue;
  894. }
  895. let scrap_chunk = scrap_chunk.unwrap();
  896. // The scrap is not available if the chunk on the disk changed
  897. if !self.geode.verify_chunk(&scrap.hash_written, &scrap_chunk) {
  898. continue;
  899. }
  900. // Mark the chunk as available
  901. chunked.get_chunk_mut(chunk_index).available = true;
  902. chunked.get_chunk_mut(chunk_index).size = scrap.chunk.len();
  903. // Update the sums of locally available data
  904. bytes.insert(
  905. chunk.hash,
  906. (
  907. scrap.chunk.len(),
  908. resource.get_bytes_of_selection(
  909. chunked,
  910. file_selection,
  911. &chunk.hash,
  912. scrap.chunk.len(),
  913. ),
  914. ),
  915. );
  916. }
  917. // If the resource is a file: make the `FileSequence`'s file the
  918. // exact file size if we know the last chunk's size. This is not
  919. // needed for directories.
  920. let is_dir = chunked.is_dir();
  921. if let Some(last_chunk) = chunked.iter_mut().last() {
  922. if !is_dir && last_chunk.available {
  923. if let Some((last_chunk_size, _)) = bytes.get(&last_chunk.hash) {
  924. last_chunk.size = *last_chunk_size;
  925. let exact_file_size =
  926. chunked.len() * MAX_CHUNK_SIZE - (MAX_CHUNK_SIZE - last_chunk_size);
  927. chunked.get_fileseq_mut().set_file_size(0, exact_file_size as u64);
  928. }
  929. }
  930. }
  931. let total_bytes_downloaded = bytes.iter().map(|(_, (b, _))| b).sum::<usize>() as u64;
  932. let target_bytes_downloaded = bytes.iter().map(|(_, (_, b))| b).sum::<usize>() as u64;
  933. Ok((total_bytes_downloaded, target_bytes_downloaded))
  934. }
  935. /// Add a resource from the file system.
  936. pub async fn put(&self, path: &Path) -> Result<()> {
  937. let put_tasks = self.put_tasks.read().await;
  938. drop(put_tasks);
  939. self.put_tx.send(path.to_path_buf()).await?;
  940. Ok(())
  941. }
  942. /// Insert a file or directory from the file system.
  943. /// Called when `put()` creates a new put task.
  944. pub async fn insert_resource(&self, path: &PathBuf) -> Result<()> {
  945. let self_node = self.node().await?;
  946. if self_node.addresses.is_empty() {
  947. return Err(Error::Custom(
  948. "Cannot put resource, you don't have any external address".to_string(),
  949. ))
  950. }
  951. let metadata = fs::metadata(path).await?;
  952. // Get the list of files and the resource type (file or directory)
  953. let (files, resource_type) = if metadata.is_file() {
  954. (vec![(path.clone(), metadata.len())], ResourceType::File)
  955. } else if metadata.is_dir() {
  956. let mut files = get_all_files(path).await?;
  957. self.geode.sort_files(&mut files);
  958. (files, ResourceType::Directory)
  959. } else {
  960. return Err(Error::Custom(format!("{} is not a valid path", path.to_string_lossy())))
  961. };
  962. // Read the file or directory and create the chunks
  963. let stream = FileSequence::new(&files, false);
  964. let total_size = stream.len();
  965. let (mut hasher, chunk_hashes) = self.geode.chunk_stream(stream).await?;
  966. // Get the relative file paths included in the metadata and hash of directories
  967. let relative_files = if let ResourceType::Directory = resource_type {
  968. // [(absolute file path, file size)] -> [(relative file path, file size)]
  969. let relative_files = files
  970. .into_iter()
  971. .map(|(file_path, size)| match file_path.strip_prefix(path) {
  972. Ok(rel_path) => Ok((rel_path.to_path_buf(), size)),
  973. Err(_) => Err(Error::Custom("Invalid file path".to_string())),
  974. })
  975. .collect::<Result<Vec<_>>>()?;
  976. // Add the files metadata to the hasher to complete the resource hash
  977. self.geode.hash_files_metadata(&mut hasher, &relative_files);
  978. relative_files
  979. } else {
  980. vec![]
  981. };
  982. // Finalize the resource hash
  983. let hash = hasher.finalize();
  984. // Create the metadata file in geode
  985. if let Err(e) = self.geode.insert_metadata(&hash, &chunk_hashes, &relative_files).await {
  986. error!(target: "fud::put()", "Failed inserting {path:?} to geode: {e}");
  987. return Err(e)
  988. }
  989. // Add path to the sled db
  990. if let Err(e) =
  991. self.path_tree.insert(hash.as_bytes(), path.to_string_lossy().to_string().as_bytes())
  992. {
  993. error!(target: "fud::put()", "Failed inserting new resource into sled: {e}");
  994. return Err(e.into())
  995. }
  996. // Add resource
  997. let mut resources_write = self.resources.write().await;
  998. resources_write.insert(
  999. hash,
  1000. Resource {
  1001. hash,
  1002. rtype: resource_type,
  1003. path: path.to_path_buf(),
  1004. status: ResourceStatus::Seeding,
  1005. file_selection: FileSelection::All,
  1006. last_file_selection: FileSelection::All,
  1007. total_chunks_count: chunk_hashes.len() as u64,
  1008. target_chunks_count: chunk_hashes.len() as u64,
  1009. total_chunks_downloaded: chunk_hashes.len() as u64,
  1010. target_chunks_downloaded: chunk_hashes.len() as u64,
  1011. total_bytes_size: total_size,
  1012. target_bytes_size: total_size,
  1013. total_bytes_downloaded: total_size,
  1014. target_bytes_downloaded: total_size,
  1015. speeds: vec![],
  1016. },
  1017. );
  1018. drop(resources_write);
  1019. // Announce the new resource
  1020. if let Ok(seeder) = self.new_seeder(&hash).await {
  1021. let seeders = vec![seeder];
  1022. let fud_announce = FudAnnounce { key: hash, seeders: seeders.clone() };
  1023. let _ = self.dht.announce(&hash, &seeders, &fud_announce).await;
  1024. }
  1025. // Send InsertCompleted event
  1026. notify_event!(self, InsertCompleted, {
  1027. hash,
  1028. path: path.to_path_buf()
  1029. });
  1030. Ok(())
  1031. }
  1032. /// Removes:
  1033. /// - a resource
  1034. /// - its metadata in geode
  1035. /// - its path in the sled path tree
  1036. /// - its file selection in the sled file selection tree
  1037. /// - and any related scrap in the sled scrap tree,
  1038. ///
  1039. /// then sends a `ResourceRemoved` fud event.
  1040. pub async fn remove(&self, hash: &blake3::Hash) {
  1041. // Remove the resource
  1042. let mut resources_write = self.resources.write().await;
  1043. resources_write.remove(hash);
  1044. drop(resources_write);
  1045. // Remove the scraps in sled
  1046. if let Ok(Some(path)) = self.hash_to_path(hash) {
  1047. let chunked = self.geode.get(hash, &path).await;
  1048. if let Ok(chunked) = chunked {
  1049. for chunk in chunked.iter() {
  1050. let _ = self.scrap_tree.remove(chunk.hash.as_bytes());
  1051. }
  1052. }
  1053. }
  1054. // Remove the metadata in geode
  1055. let hash_str = hash_to_string(hash);
  1056. let _ = fs::remove_file(self.geode.files_path.join(&hash_str)).await;
  1057. let _ = fs::remove_file(self.geode.dirs_path.join(&hash_str)).await;
  1058. // Remove the path in sled
  1059. let _ = self.path_tree.remove(hash.as_bytes());
  1060. // Remove the file selection in sled
  1061. let _ = self.file_selection_tree.remove(hash.as_bytes());
  1062. // Send a `ResourceRemoved` event
  1063. notify_event!(self, ResourceRemoved, { hash: *hash });
  1064. }
  1065. /// Remove seeders that are older than `expiry_secs`
  1066. pub async fn prune_seeders(&self, expiry_secs: u32) {
  1067. let expiry_timestamp = Timestamp::current_time().inner() - (expiry_secs as u64);
  1068. let mut seeders_write = self.dht.hash_table.write().await;
  1069. let keys: Vec<_> = seeders_write.keys().cloned().collect();
  1070. for key in keys {
  1071. let items = seeders_write.get_mut(&key).unwrap();
  1072. items.retain(|item| item.timestamp > expiry_timestamp);
  1073. if items.is_empty() {
  1074. seeders_write.remove(&key);
  1075. }
  1076. }
  1077. }
  1078. /// Stop all tasks.
  1079. pub async fn stop(&self) {
  1080. info!("Stopping fetch tasks...");
  1081. // Create a clone of fetch_tasks because `task.stop()` needs a write lock
  1082. let fetch_tasks = self.fetch_tasks.read().await;
  1083. let cloned_fetch_tasks: HashMap<blake3::Hash, Arc<StoppableTask>> =
  1084. fetch_tasks.iter().map(|(key, value)| (*key, value.clone())).collect();
  1085. drop(fetch_tasks);
  1086. for task in cloned_fetch_tasks.values() {
  1087. task.stop().await;
  1088. }
  1089. info!("Stopping put tasks...");
  1090. let put_tasks = self.put_tasks.read().await;
  1091. let cloned_put_tasks: HashMap<PathBuf, Arc<StoppableTask>> =
  1092. put_tasks.iter().map(|(key, value)| (key.clone(), value.clone())).collect();
  1093. drop(put_tasks);
  1094. for task in cloned_put_tasks.values() {
  1095. task.stop().await;
  1096. }
  1097. info!("Stopping lookup tasks...");
  1098. let lookup_tasks = self.lookup_tasks.read().await;
  1099. let cloned_lookup_tasks: HashMap<blake3::Hash, Arc<StoppableTask>> =
  1100. lookup_tasks.iter().map(|(key, value)| (*key, value.clone())).collect();
  1101. drop(lookup_tasks);
  1102. for task in cloned_lookup_tasks.values() {
  1103. task.stop().await;
  1104. }
  1105. // Stop all other tasks
  1106. let mut tasks = self.tasks.write().await;
  1107. for (name, task) in tasks.clone() {
  1108. info!("Stopping {name} task...");
  1109. task.stop().await;
  1110. }
  1111. *tasks = HashMap::new();
  1112. }
  1113. }