|
@@ -16,67 +16,37 @@
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
-use async_trait::async_trait;
|
|
|
|
|
-use futures::{future::FutureExt, pin_mut, select};
|
|
|
|
|
use log::{debug, error, info, warn};
|
|
use log::{debug, error, info, warn};
|
|
|
-use num_bigint::BigUint;
|
|
|
|
|
-use rand::{prelude::IteratorRandom, rngs::OsRng, seq::SliceRandom, RngCore};
|
|
|
|
|
use sled_overlay::sled;
|
|
use sled_overlay::sled;
|
|
|
-use smol::{
|
|
|
|
|
- channel,
|
|
|
|
|
- fs::{File, OpenOptions},
|
|
|
|
|
- io::{AsyncReadExt, AsyncWriteExt},
|
|
|
|
|
- lock::{Mutex, RwLock},
|
|
|
|
|
- stream::StreamExt,
|
|
|
|
|
- Executor,
|
|
|
|
|
-};
|
|
|
|
|
-use std::{
|
|
|
|
|
- collections::{HashMap, HashSet},
|
|
|
|
|
- io::ErrorKind,
|
|
|
|
|
- path::PathBuf,
|
|
|
|
|
- sync::Arc,
|
|
|
|
|
-};
|
|
|
|
|
|
|
+use smol::{stream::StreamExt, Executor};
|
|
|
|
|
+use std::sync::Arc;
|
|
|
use structopt_toml::{structopt::StructOpt, StructOptToml};
|
|
use structopt_toml::{structopt::StructOpt, StructOptToml};
|
|
|
|
|
|
|
|
use darkfi::{
|
|
use darkfi::{
|
|
|
async_daemonize, cli_desc,
|
|
async_daemonize, cli_desc,
|
|
|
- dht::{Dht, DhtHandler, DhtNode, DhtRouterItem, DhtRouterPtr, DhtSettings, DhtSettingsOpt},
|
|
|
|
|
- geode::{hash_to_string, ChunkedFile, Geode},
|
|
|
|
|
- net::{
|
|
|
|
|
- session::SESSION_DEFAULT, settings::SettingsOpt, ChannelPtr, P2p, P2pPtr,
|
|
|
|
|
- Settings as NetSettings,
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ dht::{Dht, DhtHandler, DhtSettings, DhtSettingsOpt},
|
|
|
|
|
+ geode::hash_to_string,
|
|
|
|
|
+ net::{session::SESSION_DEFAULT, settings::SettingsOpt, P2p, Settings as NetSettings},
|
|
|
rpc::{
|
|
rpc::{
|
|
|
jsonrpc::JsonSubscriber,
|
|
jsonrpc::JsonSubscriber,
|
|
|
- p2p_method::HandlerP2p,
|
|
|
|
|
server::{listen_and_serve, RequestHandler},
|
|
server::{listen_and_serve, RequestHandler},
|
|
|
settings::{RpcSettings, RpcSettingsOpt},
|
|
settings::{RpcSettings, RpcSettingsOpt},
|
|
|
},
|
|
},
|
|
|
- system::{Publisher, PublisherPtr, StoppableTask, StoppableTaskPtr},
|
|
|
|
|
|
|
+ system::{Publisher, StoppableTask},
|
|
|
util::path::expand_path,
|
|
util::path::expand_path,
|
|
|
Error, Result,
|
|
Error, Result,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-use event::{ChunkDownloadCompleted, ChunkNotFound, FudEvent, ResourceUpdated};
|
|
|
|
|
-use resource::{Resource, ResourceStatus};
|
|
|
|
|
-use tasks::FetchReply;
|
|
|
|
|
-
|
|
|
|
|
-/// P2P protocols
|
|
|
|
|
-mod proto;
|
|
|
|
|
-use proto::{
|
|
|
|
|
- FudAnnounce, FudChunkReply, FudFileReply, FudFindNodesReply, FudFindNodesRequest,
|
|
|
|
|
- FudFindRequest, FudFindSeedersReply, FudFindSeedersRequest, FudNotFound, FudPingReply,
|
|
|
|
|
- FudPingRequest, ProtocolFud,
|
|
|
|
|
|
|
+use fud::{
|
|
|
|
|
+ get_node_id,
|
|
|
|
|
+ proto::{FudFindNodesReply, ProtocolFud},
|
|
|
|
|
+ rpc::JsonRpcInterface,
|
|
|
|
|
+ tasks::{announce_seed_task, fetch_file_task, get_task},
|
|
|
|
|
+ Fud,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-mod event;
|
|
|
|
|
-mod resource;
|
|
|
|
|
-mod rpc;
|
|
|
|
|
-mod tasks;
|
|
|
|
|
-
|
|
|
|
|
const CONFIG_FILE: &str = "fud_config.toml";
|
|
const CONFIG_FILE: &str = "fud_config.toml";
|
|
|
const CONFIG_FILE_CONTENTS: &str = include_str!("../fud_config.toml");
|
|
const CONFIG_FILE_CONTENTS: &str = include_str!("../fud_config.toml");
|
|
|
-
|
|
|
|
|
const NODE_ID_PATH: &str = "node_id";
|
|
const NODE_ID_PATH: &str = "node_id";
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, StructOpt, StructOptToml)]
|
|
#[derive(Clone, Debug, serde::Deserialize, StructOpt, StructOptToml)]
|
|
@@ -120,631 +90,6 @@ struct Args {
|
|
|
dht: DhtSettingsOpt,
|
|
dht: DhtSettingsOpt,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-pub struct Fud {
|
|
|
|
|
- /// Key -> Seeders
|
|
|
|
|
- seeders_router: DhtRouterPtr,
|
|
|
|
|
-
|
|
|
|
|
- /// Pointer to the P2P network instance
|
|
|
|
|
- p2p: P2pPtr,
|
|
|
|
|
-
|
|
|
|
|
- /// The Geode instance
|
|
|
|
|
- geode: Geode,
|
|
|
|
|
-
|
|
|
|
|
- /// Default download directory
|
|
|
|
|
- downloads_path: PathBuf,
|
|
|
|
|
-
|
|
|
|
|
- /// Chunk transfer timeout in seconds
|
|
|
|
|
- chunk_timeout: u64,
|
|
|
|
|
-
|
|
|
|
|
- /// The DHT instance
|
|
|
|
|
- dht: Arc<Dht>,
|
|
|
|
|
-
|
|
|
|
|
- /// Resources (current status of all downloads/seeds)
|
|
|
|
|
- resources: Arc<RwLock<HashMap<blake3::Hash, Resource>>>,
|
|
|
|
|
-
|
|
|
|
|
- /// Sled tree containing "resource hash -> path on the filesystem"
|
|
|
|
|
- path_tree: sled::Tree,
|
|
|
|
|
-
|
|
|
|
|
- get_tx: channel::Sender<(u16, blake3::Hash, PathBuf, Result<()>)>,
|
|
|
|
|
- get_rx: channel::Receiver<(u16, blake3::Hash, PathBuf, Result<()>)>,
|
|
|
|
|
- file_fetch_tx: channel::Sender<(Vec<DhtNode>, blake3::Hash, PathBuf, Result<()>)>,
|
|
|
|
|
- file_fetch_rx: channel::Receiver<(Vec<DhtNode>, blake3::Hash, PathBuf, Result<()>)>,
|
|
|
|
|
- file_fetch_end_tx: channel::Sender<(blake3::Hash, Result<()>)>,
|
|
|
|
|
- file_fetch_end_rx: channel::Receiver<(blake3::Hash, Result<()>)>,
|
|
|
|
|
-
|
|
|
|
|
- rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
|
|
|
|
|
-
|
|
|
|
|
- /// dnet JSON-RPC subscriber
|
|
|
|
|
- dnet_sub: JsonSubscriber,
|
|
|
|
|
-
|
|
|
|
|
- /// Download JSON-RPC subscriber
|
|
|
|
|
- event_sub: JsonSubscriber,
|
|
|
|
|
-
|
|
|
|
|
- event_publisher: PublisherPtr<FudEvent>,
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-impl HandlerP2p for Fud {
|
|
|
|
|
- fn p2p(&self) -> P2pPtr {
|
|
|
|
|
- self.p2p.clone()
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-#[async_trait]
|
|
|
|
|
-impl DhtHandler for Fud {
|
|
|
|
|
- fn dht(&self) -> Arc<Dht> {
|
|
|
|
|
- self.dht.clone()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async fn ping(&self, channel: ChannelPtr) -> Result<DhtNode> {
|
|
|
|
|
- debug!(target: "fud::DhtHandler::ping()", "Sending ping to channel {}", channel.info.id);
|
|
|
|
|
- let msg_subsystem = channel.message_subsystem();
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudPingReply>().await;
|
|
|
|
|
- let msg_subscriber = channel.subscribe_msg::<FudPingReply>().await.unwrap();
|
|
|
|
|
- let request = FudPingRequest {};
|
|
|
|
|
-
|
|
|
|
|
- channel.send(&request).await?;
|
|
|
|
|
-
|
|
|
|
|
- let reply = msg_subscriber.receive_with_timeout(self.dht().settings.timeout).await?;
|
|
|
|
|
-
|
|
|
|
|
- msg_subscriber.unsubscribe().await;
|
|
|
|
|
-
|
|
|
|
|
- Ok(reply.node.clone())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // TODO: Optimize this
|
|
|
|
|
- async fn on_new_node(&self, node: &DhtNode) -> Result<()> {
|
|
|
|
|
- debug!(target: "fud::DhtHandler::on_new_node()", "New node {}", hash_to_string(&node.id));
|
|
|
|
|
-
|
|
|
|
|
- // If this is the first node we know about, then bootstrap
|
|
|
|
|
- if !self.dht().is_bootstrapped().await {
|
|
|
|
|
- self.dht().set_bootstrapped().await;
|
|
|
|
|
-
|
|
|
|
|
- // Lookup our own node id
|
|
|
|
|
- debug!(target: "fud::DhtHandler::on_new_node()", "DHT bootstrapping {}", hash_to_string(&self.dht().node_id));
|
|
|
|
|
- let _ = self.lookup_nodes(&self.dht().node_id).await;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Send keys that are closer to this node than we are
|
|
|
|
|
- let self_id = self.dht().node_id;
|
|
|
|
|
- let channel = self.get_channel(node).await?;
|
|
|
|
|
- for (key, seeders) in self.seeders_router.read().await.iter() {
|
|
|
|
|
- let node_distance = BigUint::from_bytes_be(&self.dht().distance(key, &node.id));
|
|
|
|
|
- let self_distance = BigUint::from_bytes_be(&self.dht().distance(key, &self_id));
|
|
|
|
|
- if node_distance <= self_distance {
|
|
|
|
|
- let _ = channel
|
|
|
|
|
- .send(&FudAnnounce {
|
|
|
|
|
- key: *key,
|
|
|
|
|
- seeders: seeders.clone().into_iter().collect(),
|
|
|
|
|
- })
|
|
|
|
|
- .await;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Ok(())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async fn fetch_nodes(&self, node: &DhtNode, key: &blake3::Hash) -> Result<Vec<DhtNode>> {
|
|
|
|
|
- debug!(target: "fud::DhtHandler::fetch_value()", "Fetching nodes close to {} from node {}", hash_to_string(key), hash_to_string(&node.id));
|
|
|
|
|
-
|
|
|
|
|
- let channel = self.get_channel(node).await?;
|
|
|
|
|
- let msg_subsystem = channel.message_subsystem();
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudFindNodesReply>().await;
|
|
|
|
|
- let msg_subscriber_nodes = channel.subscribe_msg::<FudFindNodesReply>().await.unwrap();
|
|
|
|
|
-
|
|
|
|
|
- let request = FudFindNodesRequest { key: *key };
|
|
|
|
|
- channel.send(&request).await?;
|
|
|
|
|
-
|
|
|
|
|
- let reply = msg_subscriber_nodes.receive_with_timeout(self.dht().settings.timeout).await?;
|
|
|
|
|
-
|
|
|
|
|
- msg_subscriber_nodes.unsubscribe().await;
|
|
|
|
|
-
|
|
|
|
|
- Ok(reply.nodes.clone())
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-impl Fud {
|
|
|
|
|
- /// Add ourselves to `seeders_router` for the files we already have.
|
|
|
|
|
- /// Skipped if we have no external address.
|
|
|
|
|
- async fn init(&self) -> Result<()> {
|
|
|
|
|
- info!(target: "fud::init()", "Finding resources...");
|
|
|
|
|
- let mut resources_write = self.resources.write().await;
|
|
|
|
|
- for result in self.path_tree.iter() {
|
|
|
|
|
- if result.is_err() {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Parse hash
|
|
|
|
|
- let (hash, path) = result.unwrap();
|
|
|
|
|
- let hash_bytes: [u8; 32] = match hash.to_vec().try_into() {
|
|
|
|
|
- Ok(v) => v,
|
|
|
|
|
- Err(_) => continue,
|
|
|
|
|
- };
|
|
|
|
|
- let hash = blake3::Hash::from_bytes(hash_bytes);
|
|
|
|
|
-
|
|
|
|
|
- // Parse path
|
|
|
|
|
- let path_bytes = path.to_vec();
|
|
|
|
|
- let path_str = match std::str::from_utf8(&path_bytes) {
|
|
|
|
|
- Ok(v) => v,
|
|
|
|
|
- Err(_) => continue,
|
|
|
|
|
- };
|
|
|
|
|
- let path: PathBuf = match expand_path(path_str) {
|
|
|
|
|
- Ok(v) => v,
|
|
|
|
|
- Err(_) => continue,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // Add resource
|
|
|
|
|
- resources_write.insert(
|
|
|
|
|
- hash,
|
|
|
|
|
- Resource {
|
|
|
|
|
- hash,
|
|
|
|
|
- path,
|
|
|
|
|
- status: ResourceStatus::Incomplete,
|
|
|
|
|
- chunks_total: 0,
|
|
|
|
|
- chunks_downloaded: 0,
|
|
|
|
|
- },
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- drop(resources_write);
|
|
|
|
|
-
|
|
|
|
|
- info!(target: "fud::init()", "Verifying resources...");
|
|
|
|
|
- let resources = self.verify_resources(None).await?;
|
|
|
|
|
-
|
|
|
|
|
- let self_node = self.dht().node().await;
|
|
|
|
|
-
|
|
|
|
|
- if self_node.addresses.is_empty() {
|
|
|
|
|
- return Ok(());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- info!(target: "fud::init()", "Start seeding...");
|
|
|
|
|
- let self_router_items: Vec<DhtRouterItem> = vec![self_node.into()];
|
|
|
|
|
-
|
|
|
|
|
- for resource in resources {
|
|
|
|
|
- self.add_to_router(
|
|
|
|
|
- self.seeders_router.clone(),
|
|
|
|
|
- &resource.hash,
|
|
|
|
|
- self_router_items.clone(),
|
|
|
|
|
- )
|
|
|
|
|
- .await;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Ok(())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Get resource path from hash using the sled db
|
|
|
|
|
- fn hash_to_path(&self, hash: &blake3::Hash) -> Result<Option<PathBuf>> {
|
|
|
|
|
- if let Some(value) = self.path_tree.get(hash.as_bytes())? {
|
|
|
|
|
- let path: PathBuf = expand_path(std::str::from_utf8(&value)?)?;
|
|
|
|
|
- return Ok(Some(path));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Ok(None)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Verify if resources are complete and uncorrupted.
|
|
|
|
|
- /// If a resource is incomplete or corrupted, its status is changed to Incomplete.
|
|
|
|
|
- /// If a resource is complete, its status is changed to Seeding.
|
|
|
|
|
- /// Takes an optional list of hashes.
|
|
|
|
|
- /// If no hash is given (None), it verifies all resources.
|
|
|
|
|
- /// Returns the list of verified and uncorrupted/complete seeding resources.
|
|
|
|
|
- async fn verify_resources(&self, hashes: Option<Vec<blake3::Hash>>) -> Result<Vec<Resource>> {
|
|
|
|
|
- let mut resources_write = self.resources.write().await;
|
|
|
|
|
-
|
|
|
|
|
- let update_resource =
|
|
|
|
|
- async |resource: &mut Resource,
|
|
|
|
|
- status: ResourceStatus,
|
|
|
|
|
- chunked_file: Option<&ChunkedFile>| {
|
|
|
|
|
- resource.status = status;
|
|
|
|
|
- resource.chunks_total = match chunked_file {
|
|
|
|
|
- Some(chunked_file) => chunked_file.len() as u64,
|
|
|
|
|
- None => 0,
|
|
|
|
|
- };
|
|
|
|
|
- resource.chunks_downloaded = match chunked_file {
|
|
|
|
|
- Some(chunked_file) => chunked_file.local_chunks() as u64,
|
|
|
|
|
- None => 0,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- self.event_publisher
|
|
|
|
|
- .notify(FudEvent::ResourceUpdated(ResourceUpdated {
|
|
|
|
|
- hash: resource.hash,
|
|
|
|
|
- resource: resource.clone(),
|
|
|
|
|
- }))
|
|
|
|
|
- .await;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- let mut seeding_resources: Vec<Resource> = vec![];
|
|
|
|
|
- for (_, mut resource) in resources_write.iter_mut() {
|
|
|
|
|
- if let Some(ref hashes_list) = hashes {
|
|
|
|
|
- if !hashes_list.contains(&resource.hash) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- match resource.status {
|
|
|
|
|
- ResourceStatus::Seeding => {}
|
|
|
|
|
- ResourceStatus::Incomplete => {}
|
|
|
|
|
- _ => continue,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // Make sure the resource is not corrupted or incomplete
|
|
|
|
|
- let resource_path = match self.hash_to_path(&resource.hash) {
|
|
|
|
|
- Ok(Some(v)) => v,
|
|
|
|
|
- Ok(None) | Err(_) => {
|
|
|
|
|
- update_resource(&mut resource, ResourceStatus::Incomplete, None).await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- let chunked_file = match self.geode.get(&resource.hash, &resource_path).await {
|
|
|
|
|
- Ok(v) => v,
|
|
|
|
|
- Err(_) => {
|
|
|
|
|
- update_resource(&mut resource, ResourceStatus::Incomplete, None).await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- if !chunked_file.is_complete() {
|
|
|
|
|
- update_resource(&mut resource, ResourceStatus::Incomplete, Some(&chunked_file))
|
|
|
|
|
- .await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- update_resource(&mut resource, ResourceStatus::Seeding, Some(&chunked_file)).await;
|
|
|
|
|
- seeding_resources.push(resource.clone());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Ok(seeding_resources)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Query `nodes` to find the seeders for `key`
|
|
|
|
|
- async fn fetch_seeders(
|
|
|
|
|
- &self,
|
|
|
|
|
- nodes: &Vec<DhtNode>,
|
|
|
|
|
- key: &blake3::Hash,
|
|
|
|
|
- ) -> HashSet<DhtRouterItem> {
|
|
|
|
|
- let mut seeders: HashSet<DhtRouterItem> = HashSet::new();
|
|
|
|
|
-
|
|
|
|
|
- for node in nodes {
|
|
|
|
|
- let channel = match self.get_channel(node).await {
|
|
|
|
|
- Ok(channel) => channel,
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- warn!(target: "fud::fetch_seeders()", "Could not get a channel for node {}: {}", hash_to_string(&node.id), e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- let msg_subsystem = channel.message_subsystem();
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudFindSeedersReply>().await;
|
|
|
|
|
-
|
|
|
|
|
- let msg_subscriber = match channel.subscribe_msg::<FudFindSeedersReply>().await {
|
|
|
|
|
- Ok(msg_subscriber) => msg_subscriber,
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- warn!(target: "fud::fetch_seeders()", "Error subscribing to msg: {}", e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- let send_res = channel.send(&FudFindSeedersRequest { key: *key }).await;
|
|
|
|
|
- if let Err(e) = send_res {
|
|
|
|
|
- warn!(target: "fud::fetch_seeders()", "Error while sending FudFindSeedersRequest: {}", e);
|
|
|
|
|
- msg_subscriber.unsubscribe().await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let reply = match msg_subscriber.receive_with_timeout(self.dht().settings.timeout).await
|
|
|
|
|
- {
|
|
|
|
|
- Ok(reply) => reply,
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- warn!(target: "fud::fetch_seeders()", "Error waiting for reply: {}", e);
|
|
|
|
|
- msg_subscriber.unsubscribe().await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- msg_subscriber.unsubscribe().await;
|
|
|
|
|
-
|
|
|
|
|
- seeders.extend(reply.seeders.clone());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- info!(target: "fud::fetch_seeders()", "Found {} seeders for {}", seeders.len(), hash_to_string(key));
|
|
|
|
|
- seeders
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Fetch chunks for a file from `seeders`
|
|
|
|
|
- async fn fetch_chunks(
|
|
|
|
|
- &self,
|
|
|
|
|
- file_path: &PathBuf,
|
|
|
|
|
- file_hash: &blake3::Hash,
|
|
|
|
|
- chunk_hashes: &HashSet<blake3::Hash>,
|
|
|
|
|
- seeders: &HashSet<DhtRouterItem>,
|
|
|
|
|
- ) -> Result<()> {
|
|
|
|
|
- let mut remaining_chunks = chunk_hashes.clone();
|
|
|
|
|
- let mut shuffled_seeders = {
|
|
|
|
|
- let mut vec: Vec<_> = seeders.iter().cloned().collect();
|
|
|
|
|
- vec.shuffle(&mut OsRng);
|
|
|
|
|
- vec
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- while let Some(seeder) = shuffled_seeders.pop() {
|
|
|
|
|
- let channel = match self.get_channel(&seeder.node).await {
|
|
|
|
|
- Ok(channel) => channel,
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- warn!(target: "fud::fetch_chunks()", "Could not get a channel for node {}: {}", hash_to_string(&seeder.node.id), e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- let mut chunks_to_query = remaining_chunks.clone();
|
|
|
|
|
- info!("Requesting chunks from seeder {}", hash_to_string(&seeder.node.id));
|
|
|
|
|
- loop {
|
|
|
|
|
- let msg_subsystem = channel.message_subsystem();
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudChunkReply>().await;
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudNotFound>().await;
|
|
|
|
|
- let msg_subscriber_chunk = channel.subscribe_msg::<FudChunkReply>().await.unwrap();
|
|
|
|
|
- let msg_subscriber_notfound = channel.subscribe_msg::<FudNotFound>().await.unwrap();
|
|
|
|
|
-
|
|
|
|
|
- // Select a chunk to request
|
|
|
|
|
- let mut chunk_hash: Option<blake3::Hash> = None;
|
|
|
|
|
- if let Some(&random_chunk) = chunks_to_query.iter().choose(&mut OsRng) {
|
|
|
|
|
- chunk_hash = Some(random_chunk);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if chunk_hash.is_none() {
|
|
|
|
|
- // No more chunks to request from this seeder
|
|
|
|
|
- break; // Switch to another seeder
|
|
|
|
|
- }
|
|
|
|
|
- let chunk_hash = chunk_hash.unwrap();
|
|
|
|
|
- chunks_to_query.remove(&chunk_hash);
|
|
|
|
|
-
|
|
|
|
|
- let send_res =
|
|
|
|
|
- channel.send(&FudFindRequest { info: Some(*file_hash), key: chunk_hash }).await;
|
|
|
|
|
- if let Err(e) = send_res {
|
|
|
|
|
- warn!(target: "fud::fetch_chunks()", "Error while sending FudFindRequest: {}", e);
|
|
|
|
|
- break; // Switch to another seeder
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let chunk_recv =
|
|
|
|
|
- msg_subscriber_chunk.receive_with_timeout(self.chunk_timeout).fuse();
|
|
|
|
|
- let notfound_recv =
|
|
|
|
|
- msg_subscriber_notfound.receive_with_timeout(self.chunk_timeout).fuse();
|
|
|
|
|
-
|
|
|
|
|
- pin_mut!(chunk_recv, notfound_recv);
|
|
|
|
|
-
|
|
|
|
|
- // Wait for a FudChunkReply or FudNotFound
|
|
|
|
|
- select! {
|
|
|
|
|
- chunk_reply = chunk_recv => {
|
|
|
|
|
- if let Err(e) = chunk_reply {
|
|
|
|
|
- warn!(target: "fud::fetch_chunks()", "Error waiting for chunk reply: {}", e);
|
|
|
|
|
- break; // Switch to another seeder
|
|
|
|
|
- }
|
|
|
|
|
- let reply = chunk_reply.unwrap();
|
|
|
|
|
-
|
|
|
|
|
- match self.geode.write_chunk(file_hash, file_path, &reply.chunk).await {
|
|
|
|
|
- Ok(inserted_hash) => {
|
|
|
|
|
- if inserted_hash != chunk_hash {
|
|
|
|
|
- warn!("Received chunk does not match requested chunk");
|
|
|
|
|
- msg_subscriber_chunk.unsubscribe().await;
|
|
|
|
|
- msg_subscriber_notfound.unsubscribe().await;
|
|
|
|
|
- continue; // Skip to next chunk, will retry this chunk later
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Update resource `chunks_downloaded`
|
|
|
|
|
- let mut resources_write = self.resources.write().await;
|
|
|
|
|
- let resource = match resources_write.get_mut(file_hash) {
|
|
|
|
|
- Some(resource) => {
|
|
|
|
|
- resource.status = ResourceStatus::Downloading;
|
|
|
|
|
- resource.chunks_downloaded += 1;
|
|
|
|
|
- resource.clone()
|
|
|
|
|
- }
|
|
|
|
|
- None => return Ok(()) // Resource was removed, abort
|
|
|
|
|
- };
|
|
|
|
|
- drop(resources_write);
|
|
|
|
|
-
|
|
|
|
|
- info!(target: "fud::fetch_chunks()", "Received chunk {} from seeder {}", hash_to_string(&chunk_hash), hash_to_string(&seeder.node.id));
|
|
|
|
|
- self.event_publisher
|
|
|
|
|
- .notify(FudEvent::ChunkDownloadCompleted(ChunkDownloadCompleted {
|
|
|
|
|
- hash: *file_hash,
|
|
|
|
|
- chunk_hash,
|
|
|
|
|
- resource,
|
|
|
|
|
- }))
|
|
|
|
|
- .await;
|
|
|
|
|
- remaining_chunks.remove(&chunk_hash);
|
|
|
|
|
- }
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- error!("Failed inserting chunk {} to Geode: {}", hash_to_string(&chunk_hash), e);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
- notfound_reply = notfound_recv => {
|
|
|
|
|
- if let Err(e) = notfound_reply {
|
|
|
|
|
- warn!(target: "fud::fetch_chunks()", "Error waiting for NOTFOUND reply: {}", e);
|
|
|
|
|
- msg_subscriber_chunk.unsubscribe().await;
|
|
|
|
|
- msg_subscriber_notfound.unsubscribe().await;
|
|
|
|
|
- break; // Switch to another seeder
|
|
|
|
|
- }
|
|
|
|
|
- info!(target: "fud::fetch_chunks()", "Received NOTFOUND {} from seeder {}", hash_to_string(&chunk_hash), hash_to_string(&seeder.node.id));
|
|
|
|
|
- self.event_publisher
|
|
|
|
|
- .notify(FudEvent::ChunkNotFound(ChunkNotFound {
|
|
|
|
|
- hash: *file_hash,
|
|
|
|
|
- chunk_hash,
|
|
|
|
|
- }))
|
|
|
|
|
- .await;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- msg_subscriber_chunk.unsubscribe().await;
|
|
|
|
|
- msg_subscriber_notfound.unsubscribe().await;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Stop when there are no missing chunks
|
|
|
|
|
- if remaining_chunks.is_empty() {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Ok(())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Fetch a single file metadata from `nodes`.
|
|
|
|
|
- /// If the file is smaller than a single chunk then the chunk is returned.
|
|
|
|
|
- /// 1. Request seeders for the file from those nodes
|
|
|
|
|
- /// 2. Request the file from the seeders
|
|
|
|
|
- async fn fetch_file_metadata(
|
|
|
|
|
- &self,
|
|
|
|
|
- nodes: Vec<DhtNode>,
|
|
|
|
|
- file_hash: blake3::Hash,
|
|
|
|
|
- ) -> Option<FetchReply> {
|
|
|
|
|
- let mut queried_seeders: HashSet<blake3::Hash> = HashSet::new();
|
|
|
|
|
- let mut result: Option<FetchReply> = None;
|
|
|
|
|
-
|
|
|
|
|
- for node in nodes {
|
|
|
|
|
- // 1. Request list of seeders
|
|
|
|
|
- let channel = match self.get_channel(&node).await {
|
|
|
|
|
- Ok(channel) => channel,
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Could not get a channel for node {}: {}", hash_to_string(&node.id), e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- let msg_subsystem = channel.message_subsystem();
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudFindSeedersReply>().await;
|
|
|
|
|
-
|
|
|
|
|
- let msg_subscriber = match channel.subscribe_msg::<FudFindSeedersReply>().await {
|
|
|
|
|
- Ok(msg_subscriber) => msg_subscriber,
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Error subscribing to msg: {}", e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- let send_res = channel.send(&FudFindSeedersRequest { key: file_hash }).await;
|
|
|
|
|
- if let Err(e) = send_res {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Error while sending FudFindSeedersRequest: {}", e);
|
|
|
|
|
- msg_subscriber.unsubscribe().await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let reply = match msg_subscriber.receive_with_timeout(self.dht().settings.timeout).await
|
|
|
|
|
- {
|
|
|
|
|
- Ok(reply) => reply,
|
|
|
|
|
- Err(e) => {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Error waiting for reply: {}", e);
|
|
|
|
|
- msg_subscriber.unsubscribe().await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- let mut seeders = reply.seeders.clone();
|
|
|
|
|
- info!(target: "fud::fetch_file_metadata()", "Found {} seeders for {} (from {})", seeders.len(), hash_to_string(&file_hash), hash_to_string(&node.id));
|
|
|
|
|
-
|
|
|
|
|
- msg_subscriber.unsubscribe().await;
|
|
|
|
|
-
|
|
|
|
|
- // 2. Request the file/chunk from the seeders
|
|
|
|
|
- while let Some(seeder) = seeders.pop() {
|
|
|
|
|
- // Only query a seeder once
|
|
|
|
|
- if queried_seeders.iter().any(|s| *s == seeder.node.id) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- queried_seeders.insert(seeder.node.id);
|
|
|
|
|
-
|
|
|
|
|
- if let Ok(channel) = self.get_channel(&seeder.node).await {
|
|
|
|
|
- let msg_subsystem = channel.message_subsystem();
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudChunkReply>().await;
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudFileReply>().await;
|
|
|
|
|
- msg_subsystem.add_dispatch::<FudNotFound>().await;
|
|
|
|
|
- let msg_subscriber_chunk =
|
|
|
|
|
- channel.subscribe_msg::<FudChunkReply>().await.unwrap();
|
|
|
|
|
- let msg_subscriber_file =
|
|
|
|
|
- channel.subscribe_msg::<FudFileReply>().await.unwrap();
|
|
|
|
|
- let msg_subscriber_notfound =
|
|
|
|
|
- channel.subscribe_msg::<FudNotFound>().await.unwrap();
|
|
|
|
|
-
|
|
|
|
|
- let send_res =
|
|
|
|
|
- channel.send(&FudFindRequest { info: None, key: file_hash }).await;
|
|
|
|
|
- if let Err(e) = send_res {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Error while sending FudFindRequest: {}", e);
|
|
|
|
|
- msg_subscriber_chunk.unsubscribe().await;
|
|
|
|
|
- msg_subscriber_file.unsubscribe().await;
|
|
|
|
|
- msg_subscriber_notfound.unsubscribe().await;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let chunk_recv =
|
|
|
|
|
- msg_subscriber_chunk.receive_with_timeout(self.chunk_timeout).fuse();
|
|
|
|
|
- let file_recv =
|
|
|
|
|
- msg_subscriber_file.receive_with_timeout(self.chunk_timeout).fuse();
|
|
|
|
|
- let notfound_recv =
|
|
|
|
|
- msg_subscriber_notfound.receive_with_timeout(self.chunk_timeout).fuse();
|
|
|
|
|
-
|
|
|
|
|
- pin_mut!(chunk_recv, file_recv, notfound_recv);
|
|
|
|
|
-
|
|
|
|
|
- let cleanup = async || {
|
|
|
|
|
- msg_subscriber_chunk.unsubscribe().await;
|
|
|
|
|
- msg_subscriber_file.unsubscribe().await;
|
|
|
|
|
- msg_subscriber_notfound.unsubscribe().await;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // Wait for a FudChunkReply, FudFileReply, or FudNotFound
|
|
|
|
|
- select! {
|
|
|
|
|
- // Received a chunk while requesting a file, this is allowed to
|
|
|
|
|
- // optimize fetching files smaller than a single chunk
|
|
|
|
|
- chunk_reply = chunk_recv => {
|
|
|
|
|
- cleanup().await;
|
|
|
|
|
- if let Err(e) = chunk_reply {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Error waiting for chunk reply: {}", e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- let reply = chunk_reply.unwrap();
|
|
|
|
|
- let chunk_hash = blake3::hash(&reply.chunk);
|
|
|
|
|
- // Check that this is the only chunk in the file
|
|
|
|
|
- if !self.geode.verify_file(&file_hash, &[chunk_hash]) {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Received a chunk while fetching a file, the chunk did not match the file hash");
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- info!(target: "fud::fetch_file_metadata()", "Received chunk {} (for file {}) from seeder {}", hash_to_string(&chunk_hash), hash_to_string(&file_hash), hash_to_string(&seeder.node.id));
|
|
|
|
|
- result = Some(FetchReply::Chunk((*reply).clone()));
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- file_reply = file_recv => {
|
|
|
|
|
- cleanup().await;
|
|
|
|
|
- if let Err(e) = file_reply {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Error waiting for file reply: {}", e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- let reply = file_reply.unwrap();
|
|
|
|
|
- if !self.geode.verify_file(&file_hash, &reply.chunk_hashes) {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Received invalid file metadata");
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- info!(target: "fud::fetch_file_metadata()", "Received file {} from seeder {}", hash_to_string(&file_hash), hash_to_string(&seeder.node.id));
|
|
|
|
|
- result = Some(FetchReply::File((*reply).clone()));
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- notfound_reply = notfound_recv => {
|
|
|
|
|
- cleanup().await;
|
|
|
|
|
- if let Err(e) = notfound_reply {
|
|
|
|
|
- warn!(target: "fud::fetch_file_metadata()", "Error waiting for NOTFOUND reply: {}", e);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- info!(target: "fud::fetch_file_metadata()", "Received NOTFOUND {} from seeder {}", hash_to_string(&file_hash), hash_to_string(&seeder.node.id));
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if result.is_some() {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- result
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// TODO: This is not Sybil-resistant
|
|
|
|
|
-fn generate_node_id() -> Result<blake3::Hash> {
|
|
|
|
|
- let mut rng = OsRng;
|
|
|
|
|
- let mut random_data = [0u8; 32];
|
|
|
|
|
- rng.fill_bytes(&mut random_data);
|
|
|
|
|
- let node_id = blake3::Hash::from_bytes(random_data);
|
|
|
|
|
- Ok(node_id)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
async_daemonize!(realmain);
|
|
async_daemonize!(realmain);
|
|
|
async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
|
// The working directory for this daemon and geode.
|
|
// The working directory for this daemon and geode.
|
|
@@ -756,16 +101,10 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
|
None => basedir.join("downloads"),
|
|
None => basedir.join("downloads"),
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // Hashmap used for routing
|
|
|
|
|
- let seeders_router = Arc::new(RwLock::new(HashMap::new()));
|
|
|
|
|
-
|
|
|
|
|
// Sled database init
|
|
// Sled database init
|
|
|
info!("Instantiating database");
|
|
info!("Instantiating database");
|
|
|
let sled_db = sled::open(basedir.join("db"))?;
|
|
let sled_db = sled::open(basedir.join("db"))?;
|
|
|
|
|
|
|
|
- info!("Instantiating Geode instance");
|
|
|
|
|
- let geode = Geode::new(&basedir /*, sled_db, "geode"*/).await?;
|
|
|
|
|
-
|
|
|
|
|
info!("Instantiating P2P network");
|
|
info!("Instantiating P2P network");
|
|
|
let net_settings: NetSettings = args.net.into();
|
|
let net_settings: NetSettings = args.net.into();
|
|
|
let p2p = P2p::new(net_settings.clone(), ex.clone()).await?;
|
|
let p2p = P2p::new(net_settings.clone(), ex.clone()).await?;
|
|
@@ -794,71 +133,36 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
|
ex.clone(),
|
|
ex.clone(),
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // Get or generate the node id
|
|
|
|
|
- let node_id: Result<blake3::Hash> = {
|
|
|
|
|
- let mut node_id_path: PathBuf = basedir.clone();
|
|
|
|
|
- node_id_path.push(NODE_ID_PATH);
|
|
|
|
|
- match File::open(node_id_path.clone()).await {
|
|
|
|
|
- Ok(mut file) => {
|
|
|
|
|
- let mut buffer = Vec::new();
|
|
|
|
|
- file.read_to_end(&mut buffer).await?;
|
|
|
|
|
- let mut out_buf = [0u8; 32];
|
|
|
|
|
- bs58::decode(buffer).onto(&mut out_buf)?;
|
|
|
|
|
- let node_id = blake3::Hash::from_bytes(out_buf);
|
|
|
|
|
- Ok(node_id)
|
|
|
|
|
- }
|
|
|
|
|
- Err(e) if e.kind() == ErrorKind::NotFound => {
|
|
|
|
|
- let node_id = generate_node_id()?;
|
|
|
|
|
- let mut file =
|
|
|
|
|
- OpenOptions::new().write(true).create(true).open(node_id_path).await?;
|
|
|
|
|
- file.write_all(&bs58::encode(node_id.as_bytes()).into_vec()).await?;
|
|
|
|
|
- file.flush().await?;
|
|
|
|
|
- Ok(node_id)
|
|
|
|
|
- }
|
|
|
|
|
- Err(e) => Err(e.into()),
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- let node_id_ = node_id?;
|
|
|
|
|
|
|
+ let mut node_id_path = basedir.to_path_buf();
|
|
|
|
|
+ node_id_path.push(NODE_ID_PATH);
|
|
|
|
|
+ let node_id = get_node_id(&node_id_path).await?;
|
|
|
|
|
|
|
|
- info!(target: "fud", "Your node ID: {}", hash_to_string(&node_id_));
|
|
|
|
|
|
|
+ info!(target: "fud", "Your node ID: {}", hash_to_string(&node_id));
|
|
|
|
|
|
|
|
// Daemon instantiation
|
|
// Daemon instantiation
|
|
|
- let event_sub = JsonSubscriber::new("event");
|
|
|
|
|
- let (get_tx, get_rx) = smol::channel::unbounded();
|
|
|
|
|
- let (file_fetch_tx, file_fetch_rx) = smol::channel::unbounded();
|
|
|
|
|
- let (file_fetch_end_tx, file_fetch_end_rx) = smol::channel::unbounded();
|
|
|
|
|
|
|
+ let event_pub = Publisher::new();
|
|
|
let dht_settings: DhtSettings = args.dht.into();
|
|
let dht_settings: DhtSettings = args.dht.into();
|
|
|
- let dht: Arc<Dht> = Arc::new(Dht::new(&node_id_, &dht_settings, p2p.clone(), ex.clone()).await);
|
|
|
|
|
- let fud = Arc::new(Fud {
|
|
|
|
|
- seeders_router,
|
|
|
|
|
- p2p: p2p.clone(),
|
|
|
|
|
- geode,
|
|
|
|
|
- downloads_path,
|
|
|
|
|
- chunk_timeout: args.chunk_timeout,
|
|
|
|
|
- dht: dht.clone(),
|
|
|
|
|
- path_tree: sled_db.open_tree("path")?,
|
|
|
|
|
- resources: Arc::new(RwLock::new(HashMap::new())),
|
|
|
|
|
- get_tx,
|
|
|
|
|
- get_rx,
|
|
|
|
|
- file_fetch_tx,
|
|
|
|
|
- file_fetch_rx,
|
|
|
|
|
- file_fetch_end_tx,
|
|
|
|
|
- file_fetch_end_rx,
|
|
|
|
|
- rpc_connections: Mutex::new(HashSet::new()),
|
|
|
|
|
- dnet_sub,
|
|
|
|
|
- event_sub: event_sub.clone(),
|
|
|
|
|
- event_publisher: Publisher::new(),
|
|
|
|
|
- });
|
|
|
|
|
- fud.init().await?;
|
|
|
|
|
|
|
+ let dht: Arc<Dht> = Arc::new(Dht::new(&node_id, &dht_settings, p2p.clone(), ex.clone()).await);
|
|
|
|
|
+ let fud: Arc<Fud> = Arc::new(
|
|
|
|
|
+ Fud::new(
|
|
|
|
|
+ p2p.clone(),
|
|
|
|
|
+ basedir,
|
|
|
|
|
+ downloads_path,
|
|
|
|
|
+ args.chunk_timeout,
|
|
|
|
|
+ dht.clone(),
|
|
|
|
|
+ sled_db.open_tree("path")?,
|
|
|
|
|
+ event_pub.clone(),
|
|
|
|
|
+ )
|
|
|
|
|
+ .await?,
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
info!(target: "fud", "Starting download subs task");
|
|
info!(target: "fud", "Starting download subs task");
|
|
|
|
|
+ let event_sub = JsonSubscriber::new("event");
|
|
|
let event_sub_ = event_sub.clone();
|
|
let event_sub_ = event_sub.clone();
|
|
|
- let fud_ = fud.clone();
|
|
|
|
|
let event_task = StoppableTask::new();
|
|
let event_task = StoppableTask::new();
|
|
|
event_task.clone().start(
|
|
event_task.clone().start(
|
|
|
async move {
|
|
async move {
|
|
|
- let event_sub = fud_.event_publisher.clone().subscribe().await;
|
|
|
|
|
|
|
+ let event_sub = event_pub.clone().subscribe().await;
|
|
|
loop {
|
|
loop {
|
|
|
let event = event_sub.receive().await;
|
|
let event = event_sub.receive().await;
|
|
|
debug!(target: "fud", "Got event: {:?}", event);
|
|
debug!(target: "fud", "Got event: {:?}", event);
|
|
@@ -878,7 +182,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
|
info!(target: "fud", "Starting fetch file task");
|
|
info!(target: "fud", "Starting fetch file task");
|
|
|
let file_task = StoppableTask::new();
|
|
let file_task = StoppableTask::new();
|
|
|
file_task.clone().start(
|
|
file_task.clone().start(
|
|
|
- tasks::fetch_file_task(fud.clone()),
|
|
|
|
|
|
|
+ fetch_file_task(fud.clone()),
|
|
|
|res| async {
|
|
|res| async {
|
|
|
match res {
|
|
match res {
|
|
|
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
|
|
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
|
|
@@ -892,7 +196,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
|
info!(target: "fud", "Starting get task");
|
|
info!(target: "fud", "Starting get task");
|
|
|
let get_task_ = StoppableTask::new();
|
|
let get_task_ = StoppableTask::new();
|
|
|
get_task_.clone().start(
|
|
get_task_.clone().start(
|
|
|
- tasks::get_task(fud.clone()),
|
|
|
|
|
|
|
+ get_task(fud.clone()),
|
|
|
|res| async {
|
|
|res| async {
|
|
|
match res {
|
|
match res {
|
|
|
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
|
|
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
|
|
@@ -905,13 +209,14 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
|
|
|
|
|
|
let rpc_settings: RpcSettings = args.rpc.into();
|
|
let rpc_settings: RpcSettings = args.rpc.into();
|
|
|
info!(target: "fud", "Starting JSON-RPC server on {}", rpc_settings.listen);
|
|
info!(target: "fud", "Starting JSON-RPC server on {}", rpc_settings.listen);
|
|
|
|
|
+ let rpc_interface = Arc::new(JsonRpcInterface::new(fud.clone(), dnet_sub, event_sub));
|
|
|
let rpc_task = StoppableTask::new();
|
|
let rpc_task = StoppableTask::new();
|
|
|
- let fud_ = fud.clone();
|
|
|
|
|
|
|
+ let rpc_interface_ = rpc_interface.clone();
|
|
|
rpc_task.clone().start(
|
|
rpc_task.clone().start(
|
|
|
- listen_and_serve(rpc_settings, fud.clone(), None, ex.clone()),
|
|
|
|
|
|
|
+ listen_and_serve(rpc_settings, rpc_interface, None, ex.clone()),
|
|
|
|res| async move {
|
|
|res| async move {
|
|
|
match res {
|
|
match res {
|
|
|
- Ok(()) | Err(Error::RpcServerStopped) => fud_.stop_connections().await,
|
|
|
|
|
|
|
+ Ok(()) | Err(Error::RpcServerStopped) => rpc_interface_.stop_connections().await,
|
|
|
Err(e) => error!(target: "fud", "Failed starting sync JSON-RPC server: {}", e),
|
|
Err(e) => error!(target: "fud", "Failed starting sync JSON-RPC server: {}", e),
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -967,7 +272,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
|
|
let announce_task = StoppableTask::new();
|
|
let announce_task = StoppableTask::new();
|
|
|
let fud_ = fud.clone();
|
|
let fud_ = fud.clone();
|
|
|
announce_task.clone().start(
|
|
announce_task.clone().start(
|
|
|
- async move { tasks::announce_seed_task(fud_.clone()).await },
|
|
|
|
|
|
|
+ async move { announce_seed_task(fud_.clone()).await },
|
|
|
|res| async {
|
|
|res| async {
|
|
|
match res {
|
|
match res {
|
|
|
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
|
|
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
|