diff --git a/.github/actions/setup-rust/action.yaml b/.github/actions/setup-rust/action.yaml index 8498f184a..844ebe49f 100644 --- a/.github/actions/setup-rust/action.yaml +++ b/.github/actions/setup-rust/action.yaml @@ -34,27 +34,6 @@ runs: shell: bash run: echo '{}' | npx -y mustache - .cargo/config.toml.mustache .cargo/config.toml - - name: Turn Off Debuginfo and bump opt-level - shell: bash - if: ${{ runner.os != 'Windows' }} - run: | - sed '/\[profile.dev]/a\ - debug = 0 - ' Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml - - sed '/\[profile.dev]/a\ - opt-level=1 - ' Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml - - - name: Turn Off Debuginfo and bump opt-level - if: ${{ runner.os == 'Windows' }} - shell: powershell - run: | - (Get-Content Cargo.toml) -replace '\[profile.dev\]', '[profile.dev] - debug = 0' | Set-Content Cargo.toml - (Get-Content Cargo.toml) -replace '\[profile.dev\]', '[profile.dev] - opt-level=1' | Set-Content Cargo.toml - - name: Restore cached Prisma codegen id: cache-prisma-restore uses: actions/cache/restore@v4 diff --git a/.npmrc b/.npmrc index 0bb689b68..b40bf051f 100644 --- a/.npmrc +++ b/.npmrc @@ -1,7 +1,5 @@ ; make all engine requirements (e.g. node version) strictly kept engine-strict=true -; tempfix for pnpm#5909: https://github.com/pnpm/pnpm/issues/5909#issuecomment-1397758156 -prefer-symlinked-executables=false ; necessary for metro + mobile strict-peer-dependencies=false node-linker=hoisted diff --git a/Cargo.toml b/Cargo.toml index 467e90178..572ca0e80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,12 @@ pdfium-render = { git = "https://github.com/fogodev/pdfium-render.git", rev = "e [profile.dev] # Make compilation faster on macOS split-debuginfo = "unpacked" +opt-level = 0 +debug = 0 +strip = "none" +lto = false +codegen-units = 256 +incremental = true # Set the settings for build scripts and proc-macros. [profile.dev.build-override] diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index aaad49eab..546974249 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -63,11 +63,3 @@ default = ["custom-protocol"] devtools = ["tauri/devtools"] ai-models = ["sd-core/ai"] custom-protocol = ["tauri/custom-protocol"] - -# Optimize release builds -[profile.release] -panic = "abort" # Strip expensive panic clean-up logic -codegen-units = 1 # Compile crates one after another so the compiler can optimize better -lto = true # Enables link to optimizations -opt-level = "s" # Optimize for binary size -strip = true # Remove debug symbols diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json index 1f826b93b..191e466f9 100644 --- a/apps/desktop/src-tauri/capabilities/default.json +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -19,6 +19,7 @@ "dialog:allow-open", "window:allow-close", "window:allow-minimize", - "window:allow-toggle-maximize" + "window:allow-toggle-maximize", + "dialog:allow-confirm" ] } diff --git a/apps/mobile/src/components/overview/StatCard.tsx b/apps/mobile/src/components/overview/StatCard.tsx index 5ef84bd64..b2712594c 100644 --- a/apps/mobile/src/components/overview/StatCard.tsx +++ b/apps/mobile/src/components/overview/StatCard.tsx @@ -25,7 +25,7 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => { return { totalSpace, freeSpace, - usedSpaceSpace: humanizeSize(totalSpace.original - freeSpace.original) + usedSpaceSpace: humanizeSize(totalSpace.bytes - freeSpace.bytes) }; }, [stats]); @@ -34,7 +34,7 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => { }, []); const progress = useMemo(() => { - if (!mounted || totalSpace.original === 0) return 0; + if (!mounted || totalSpace.bytes === 0n) return 0; return Math.floor((usedSpaceSpace.value / totalSpace.value) * 100); }, [mounted, totalSpace, usedSpaceSpace]); diff --git a/core/Cargo.toml b/core/Cargo.toml index e16f6f0dc..da0f6ddf2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -42,9 +42,9 @@ sd-images = { path = "../crates/images", features = [ ] } sd-media-metadata = { path = "../crates/media-metadata" } sd-p2p = { path = "../crates/p2p", features = ["specta"] } -sd-p2p-block = { path = "../crates/p2p-block" } -sd-p2p-proto = { path = "../crates/p2p-proto" } -sd-p2p-tunnel = { path = "../crates/p2p-tunnel" } +sd-p2p-block = { path = "../crates/p2p/crates/block" } +sd-p2p-proto = { path = "../crates/p2p/crates/proto" } +sd-p2p-tunnel = { path = "../crates/p2p/crates/tunnel" } sd-prisma = { path = "../crates/prisma" } sd-sync = { path = "../crates/sync" } sd-utils = { path = "../crates/utils" } diff --git a/core/crates/sync/src/ingest.rs b/core/crates/sync/src/ingest.rs index 18eaed3aa..aeee7d3b2 100644 --- a/core/crates/sync/src/ingest.rs +++ b/core/crates/sync/src/ingest.rs @@ -152,7 +152,7 @@ impl Actor { shared .actors .declare( - "Sync Ingester", + "Sync Ingest", { let shared = shared.clone(); move || async move { diff --git a/core/src/p2p/manager.rs b/core/src/p2p/manager.rs index 8bffca74c..3ce86a034 100644 --- a/core/src/p2p/manager.rs +++ b/core/src/p2p/manager.rs @@ -67,7 +67,7 @@ impl P2PManager { > { let (tx, rx) = bounded(25); let p2p = P2P::new(SPACEDRIVE_APP_ID, node_config.get().await.identity, tx); - let (quic, lp2p_peer_id) = QuicTransport::spawn(p2p.clone())?; + let (quic, lp2p_peer_id) = QuicTransport::spawn(p2p.clone()).map_err(|e| e.to_string())?; let libraries_hook_id = libraries_hook(p2p.clone(), libraries); let this = Arc::new(Self { p2p: p2p.clone(), @@ -138,13 +138,19 @@ impl P2PManager { pub async fn on_node_config_change(&self) { let config = self.node_config.get().await; - PeerMetadata { - name: config.name.clone(), - operating_system: Some(OperatingSystem::get_os()), - device_model: Some(get_hardware_model_name().unwrap_or(HardwareModel::Other)), - version: Some(env!("CARGO_PKG_VERSION").to_string()), + if config.p2p.discovery == P2PDiscoveryState::ContactsOnly { + PeerMetadata::remove(&mut self.p2p.metadata_mut()); + + // TODO: Hash Spacedrive account ID and put it in the metadata. + } else { + PeerMetadata { + name: config.name.clone(), + operating_system: Some(OperatingSystem::get_os()), + device_model: Some(get_hardware_model_name().unwrap_or(HardwareModel::Other)), + version: Some(env!("CARGO_PKG_VERSION").to_string()), + } + .update(&mut self.p2p.metadata_mut()); } - .update(&mut self.p2p.metadata_mut()); let port = config.p2p.port.get(); @@ -185,9 +191,7 @@ impl P2PManager { } let should_revert = match config.p2p.discovery { - P2PDiscoveryState::Everyone - // TODO: Make `ContactsOnly` work - | P2PDiscoveryState::ContactsOnly => { + P2PDiscoveryState::Everyone | P2PDiscoveryState::ContactsOnly => { let mut mdns = self.mdns.lock().unwrap_or_else(PoisonError::into_inner); if mdns.is_none() { match Mdns::spawn(self.p2p.clone()) { @@ -216,7 +220,7 @@ impl P2PManager { } false - }, + } }; // The `should_revert` bit is weird but we need this future to stay `Send` as rspc requires. diff --git a/core/src/p2p/metadata.rs b/core/src/p2p/metadata.rs index a54694158..5e03e9c7d 100644 --- a/core/src/p2p/metadata.rs +++ b/core/src/p2p/metadata.rs @@ -14,6 +14,13 @@ pub struct PeerMetadata { } impl PeerMetadata { + pub fn remove(map: &mut HashMap) { + map.remove("name"); + map.remove("os"); + map.remove("device_model"); + map.remove("version"); + } + pub fn update(self, map: &mut HashMap) { map.insert("name".to_owned(), self.name.clone()); if let Some(os) = self.operating_system { diff --git a/crates/p2p-block/Cargo.toml b/crates/p2p/crates/block/Cargo.toml similarity index 82% rename from crates/p2p-block/Cargo.toml rename to crates/p2p/crates/block/Cargo.toml index 7e23b35d3..1aaa816c7 100644 --- a/crates/p2p-block/Cargo.toml +++ b/crates/p2p/crates/block/Cargo.toml @@ -8,8 +8,8 @@ repository.workspace = true [dependencies] # Spacedrive Sub-crates -sd-p2p = { path = "../p2p" } -sd-p2p-proto = { path = "../p2p-proto" } +sd-p2p = { path = "../../" } +sd-p2p-proto = { path = "../proto" } thiserror = { workspace = true } tokio = { workspace = true } diff --git a/crates/p2p-block/src/block.rs b/crates/p2p/crates/block/src/block.rs similarity index 100% rename from crates/p2p-block/src/block.rs rename to crates/p2p/crates/block/src/block.rs diff --git a/crates/p2p-block/src/block_size.rs b/crates/p2p/crates/block/src/block_size.rs similarity index 100% rename from crates/p2p-block/src/block_size.rs rename to crates/p2p/crates/block/src/block_size.rs diff --git a/crates/p2p-block/src/lib.rs b/crates/p2p/crates/block/src/lib.rs similarity index 100% rename from crates/p2p-block/src/lib.rs rename to crates/p2p/crates/block/src/lib.rs diff --git a/crates/p2p-block/src/sb_request.rs b/crates/p2p/crates/block/src/sb_request.rs similarity index 100% rename from crates/p2p-block/src/sb_request.rs rename to crates/p2p/crates/block/src/sb_request.rs diff --git a/crates/p2p-proto/Cargo.toml b/crates/p2p/crates/proto/Cargo.toml similarity index 100% rename from crates/p2p-proto/Cargo.toml rename to crates/p2p/crates/proto/Cargo.toml diff --git a/crates/p2p-proto/src/lib.rs b/crates/p2p/crates/proto/src/lib.rs similarity index 100% rename from crates/p2p-proto/src/lib.rs rename to crates/p2p/crates/proto/src/lib.rs diff --git a/crates/p2p-tunnel/Cargo.toml b/crates/p2p/crates/tunnel/Cargo.toml similarity index 91% rename from crates/p2p-tunnel/Cargo.toml rename to crates/p2p/crates/tunnel/Cargo.toml index e4e0df7bc..ff37c4c8b 100644 --- a/crates/p2p-tunnel/Cargo.toml +++ b/crates/p2p/crates/tunnel/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true [dependencies] # Spacedrive Sub-crates -sd-p2p = { path = "../p2p" } +sd-p2p = { path = "../../" } tokio = { workspace = true, features = ["io-util"] } thiserror = { workspace = true } diff --git a/crates/p2p-tunnel/src/lib.rs b/crates/p2p/crates/tunnel/src/lib.rs similarity index 100% rename from crates/p2p-tunnel/src/lib.rs rename to crates/p2p/crates/tunnel/src/lib.rs diff --git a/crates/p2p-tunnel/src/tunnel.rs b/crates/p2p/crates/tunnel/src/tunnel.rs similarity index 100% rename from crates/p2p-tunnel/src/tunnel.rs rename to crates/p2p/crates/tunnel/src/tunnel.rs diff --git a/crates/p2p/src/quic/transport.rs b/crates/p2p/src/quic/transport.rs index 0d493f66b..bbdaf2891 100644 --- a/crates/p2p/src/quic/transport.rs +++ b/crates/p2p/src/quic/transport.rs @@ -16,6 +16,7 @@ use libp2p::{ yamux, Multiaddr, PeerId, StreamProtocol, Swarm, SwarmBuilder, }; use serde::{Deserialize, Serialize}; +use thiserror::Error; use tokio::{ net::TcpListener, sync::{mpsc, oneshot}, @@ -78,6 +79,20 @@ struct MyBehaviour { dcutr: dcutr::Behaviour, } +#[derive(Debug, Error)] +pub enum QuicTransportError { + #[error("Failed to modify the SwarmBuilder: {0}")] + SwarmBuilderCreation(String), + #[error("Internal response channel closed: {0}")] + SendChannelClosed(String), + #[error("Internal response channel closed: {0}")] + ReceiveChannelClosed(#[from] oneshot::error::RecvError), + #[error("Failed internal event: {0}")] + InternalEvent(String), + #[error("Failed to create the Listener: {0}")] + ListenerSetup(std::io::Error), +} + /// Transport using Quic to establish a connection between peers. /// This uses `libp2p` internally. #[derive(Debug)] @@ -91,8 +106,7 @@ pub struct QuicTransport { impl QuicTransport { /// Spawn the `QuicTransport` and register it with the P2P system. /// Be aware spawning this does nothing unless you call `Self::set_ipv4_enabled`/`Self::set_ipv6_enabled` to enable the listeners. - // TODO: Error type here - pub fn spawn(p2p: Arc) -> Result<(Self, Libp2pPeerId), String> { + pub fn spawn(p2p: Arc) -> Result<(Self, Libp2pPeerId), QuicTransportError> { let keypair = identity_to_libp2p_keypair(p2p.identity()); let libp2p_peer_id = Libp2pPeerId(keypair.public().to_peer_id()); @@ -108,14 +122,14 @@ impl QuicTransport { .with_tokio() .with_quic() .with_relay_client(noise::Config::new, yamux::Config::default) - .map_err(|err| err.to_string())? + .map_err(|err| QuicTransportError::SwarmBuilderCreation(err.to_string()))? .with_behaviour(|keypair, relay_behaviour| MyBehaviour { stream: libp2p_stream::Behaviour::new(), relay: relay_behaviour, autonat: autonat::Behaviour::new(keypair.public().to_peer_id(), Default::default()), dcutr: dcutr::Behaviour::new(keypair.public().to_peer_id()), }) - .map_err(|err| err.to_string())? + .map_err(|err| QuicTransportError::SwarmBuilderCreation(err.to_string()))? .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); @@ -163,7 +177,7 @@ impl QuicTransport { } // `None` on the port means disabled. Use `0` for random port. - pub async fn set_ipv4_enabled(&self, port: Option) -> Result<(), String> { + pub async fn set_ipv4_enabled(&self, port: Option) -> Result<(), QuicTransportError> { self.setup_listener( port.map(|p| SocketAddr::from((Ipv4Addr::UNSPECIFIED, p))), true, @@ -171,7 +185,7 @@ impl QuicTransport { .await } - pub async fn set_ipv6_enabled(&self, port: Option) -> Result<(), String> { + pub async fn set_ipv6_enabled(&self, port: Option) -> Result<(), QuicTransportError> { self.setup_listener( port.map(|p| SocketAddr::from((Ipv6Addr::UNSPECIFIED, p))), false, @@ -179,18 +193,20 @@ impl QuicTransport { .await } - // TODO: Proper error type - async fn setup_listener(&self, addr: Option, ipv4: bool) -> Result<(), String> { + async fn setup_listener( + &self, + addr: Option, + ipv4: bool, + ) -> Result<(), QuicTransportError> { let (tx, rx) = oneshot::channel(); let event = if let Some(mut addr) = addr { if addr.port() == 0 { - #[allow(clippy::unwrap_used)] // TODO: Error handling addr.set_port( TcpListener::bind(addr) .await - .unwrap() + .map_err(|e| QuicTransportError::ListenerSetup(e))? .local_addr() - .unwrap() + .map_err(|e| QuicTransportError::ListenerSetup(e))? .port(), ); } @@ -209,12 +225,13 @@ impl QuicTransport { } }; - let Ok(_) = self.internal_tx.send(event) else { - return Err("internal channel closed".to_string()); - }; + self.internal_tx + .send(event) + .map_err(|e| QuicTransportError::SendChannelClosed(e.to_string()))?; + rx.await - .map_err(|_| "internal response channel closed".to_string()) - .and_then(|r| r) + .map_err(|e| QuicTransportError::ReceiveChannelClosed(e)) + .and_then(|r| r.map_err(|e| QuicTransportError::InternalEvent(e))) } pub async fn shutdown(self) { diff --git a/docs/developers/technology/p2p.mdx b/docs/developers/technology/p2p.mdx new file mode 100644 index 000000000..73abf5e27 --- /dev/null +++ b/docs/developers/technology/p2p.mdx @@ -0,0 +1,208 @@ +--- +title: ​p2p +index: 14 +--- + +# Peer-to-peer + +Our peer-to-peer technology works at the heart of Spacedrive allowing all of your devices to seamlessly communicate and share data. This documentation outlines + +## Implementing features with P2P + +TODO: + - From frontend, to backend + - Including authentication + - Versioning/making breaking change + - Show using `sd_p2p_tunnel` + +## Underlying technology + +### Terminology + + - **Node**: An application running Spacedrive's network stack. + - This could be the Spacedrive app or the P2P relay. + - If you have multiple Spacedrive installations open on your computer, each one is an independant node. + - **Library**: A logical collection of your data within Spacedrive. + - From a theorical perspective, a library is just the conflict resolved state of one or more **instances** although a lot of the time we don't stricly treat it that way. + - **Instance**: An instance of a library running on a particular node. + - An instance correlates directly to each SQLite file. + - You could *technically* have more than one instance for a library on a single node, although our core would fall apart as we identify traffic by library. + - [`Identity`](https://github.com/spacedriveapp/spacedrive/blob/518d5836f6585a5f597c3ae5a0d27d084adc0a63/crates/p2p/src/identity.rs#L29) - A public/private keypair which represents the library or node. + - [`RemoteIdentity`](https://github.com/spacedriveapp/spacedrive/blob/518d5836f6585a5f597c3ae5a0d27d084adc0a63/crates/p2p/src/identity.rs#L70) - A public key which represents the library or node. + - [`PeerId`](https://docs.rs/libp2p/latest/libp2p/struct.PeerId.html) - The identifier libp2p uses. Can be derived from a `RemoteIdentity`. + +### `sd_p2p` crate + +The P2P crate was designed from the group up to be modular. + +The `P2P` struct is the core of the system, and suprisingly doesn't actually do any P2P functionality. It's a state manager and event bus along with providing a hook system for other components of the P2P system to register themselves. + +This modular design helps with separting the concern which helps with comprehending the entire system and makes it easier for testing. + +The `sd_p2p` crate provides a hook for: + - `Mdns` - Local network discovery + - `Quic` - Quic transport layer built on top of `libp2p` + +#### What are hooks? + +A hook is very similar to an actor. It's a component which can be registered with the P2P system. + +A hook allows for processing events from the P2P system and also ensures when the P2P system shuts down, the hook is also shutdown. + +Their are special hooks called listeners. These are implemented as a superset of a regular hook and are able to create and accept connections. + +Subcrates: + - [`sd_p2p_block`](https://github.com/spacedriveapp/spacedrive/tree/main/crates/p2p/crates/block) - Block protocol based on [SyncThing Block Exchange Protocol v1](https://docs.syncthing.net/specs/bep-v1.html) + - [`sd_p2p_proto`](https://github.com/spacedriveapp/spacedrive/tree/main/crates/p2p/crates/proto) - Utilities for zero fluff encoding and decoding. + - [`sd_p2p_tunnel`](https://github.com/spacedriveapp/spacedrive/tree/main/crates/p2p/crates/tunnel) - Encrypt a stream of data between two nodes + +#### `sd_p2p_proto` + +This crate provides utilities for implementing asynchronous deserializers and matching synchronous serializers. The goal of these implementations is to really quickly send and receive Rust structs over the network. + +This crate allows for creating implementations faster than other common options, at the cost of some developer experience. + +Before building this I originally compared the performance of both [msgpack](https://docs.rs/rmp-serde) and [bincode](https://docs.rs/bincode) against manual implementations using `AsyncRead` and I found that over the network using asynchronous deserialization was faster. + +This makes logically makes sense as if you want to use a synchronous serializer you will do the following: + + - Send the total length of the message + - Allocate a buffer for the message + - Wait asynchronously for the buffer to be filled + - Synchronously copy from the buffer into each of the struct fields + +When using an asynchronous serializer you can skip sending the messages length and allocating the intermediate buffer as we can rely on the known length of each field while decoding and this is a win for performance and memory usage. + +This crate provides utilities to make the implementations less error prone, however long term it would be great to replace this with a derive macro similar to how crates like [serde](https://serde.rs) work. + +From my research no crate exists that meets these requirements. It is also a difficult problem because your juggling lifetimes and async which is rough. I attempted an implementation called [binario](https://github.com/oscartbeaumont/binario), however it is still incomplete so we never adopted it. I suspect Rust's recent stablisation of [RPITIT](https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html) would make this much easier. + +### Local Network Discovery + +Our local network discovery uses [DNS-Based Service Discovery](https://www.rfc-editor.org/rfc/rfc6763.html) which itself is built around [Multicast DNS (mDNS)](https://datatracker.ietf.org/doc/html/rfc6762). This is a really well established technology and is used in [Spotify Connect](https://support.spotify.com/au/article/spotify-connect/), [Apple Airplay](https://www.apple.com/au/airplay/) and many other services you use every day. + +#### Service Structure + +The following is an example of what would be broadcast from a single Spacedrive node: +```toml +# {remote_identity_of_self}._sd._udp.local. + +name=Oscars Laptop # Shown to the user to select a device +operating_system=macos # Used for UI purposes +device_model=MacBook Pro # Used for UI purposes +version=0.0.1 # Spacedrive version + +# For each library that's active on the Spacedrive node: +# {library_uuid}={remote_identity_of_self} +d66ed0c3-03ac-4f9b-a374-a927830dfd5b=0l9vTOWu+5aJs0cyWxdfJEGtloEepGRAXcEuDeTDRPk +``` + +Within `sd-core` this is defined in two parts. The [`PeerMetadata` struct](https://github.com/spacedriveapp/spacedrive/blob/44478207e72495b3777e294660d78939711b544f/core/src/p2p/metadata.rs#L9) takes care of the node metadata and libraries are inserted by the [`libraries_hook`](https://github.com/spacedriveapp/spacedrive/blob/44478207e72495b3777e294660d78939711b544f/core/src/p2p/libraries.rs#L13). + +#### Modes + + + +Within Spacedrive's settings the user is able to choose between three modes for local network discovery: + - **Contacts only**: Only devices that are in your contacts list will be able to see your device. + - **Enabled**: All devices on the local network will be able to see your device. + - **Disabled**: No devices on the local network will be able to see your device. + +**Enabled** and **Disabled** are implemented by spawning and shutting down the [`sd_p2p::Mdns`](https://github.com/spacedriveapp/spacedrive/blob/44478207e72495b3777e294660d78939711b544f/crates/p2p/src/mdns.rs#L17) service as required within `sd-core`. + +**Contacts only** the mDNS service will not contain the [`PeerMetadata`](https://github.com/spacedriveapp/spacedrive/blob/44478207e72495b3777e294660d78939711b544f/core/src/p2p/metadata.rs#L9) fields and instead will contain a hash of the users Spacedrive identifier. If a Spacedrive node detects another node in the local network with a hash in it's contacts, it can make a request to the node and if the remote node also has the current node in it's contacts, it will respond with the full metadata. + +#### Implementation + +We make use of the [mdns-sd](https://docs.rs/mdns-sd) crate. + +### Manual connection + +The user can manually provide a set of [`SocketAddr`](https://doc.rust-lang.org/std/net/enum.SocketAddr.html)'s and the P2P system to attempt to connect to. + +This feature primarily exists for usage in combination with Docker but it could be useful for working around difficult network setups. + +#### Implementation + +TODO - TODO + +#### Problems with Docker + +TODO - MDNS daemon +TODO - Docker and why it's a pain mDNS. Explain the current stuff i've done with it. + +### Transport layer + +TODO - Quic + +TODO - Explain authentication + +### Relay + +TODO + +### Direction Connect via Relay + +TODO + +#### Authentication + +TODO - How we gonna restrict this??? + +#### Billing + +TODO - How we gonna bill for this??? + +### Design Decisions + +TODO + +### Things I would do differently? + +TODO + +### Crates + +TODO + +#### Security + +##### Threat model + +TODO - Risks of sharing IP's using discovery, risks of compromised relay, risks of compromised local network during pairing + +##### Authentication + +TODO + +##### Authorization + +TODO + +##### Tracking + +TODO - Link to Apple stuff + +#### Version compatibility and breaking changes + +TODO - Compatibility across versions of Spacedrive + +#### libp2p + +TODO - Why libp2p fork?, Why libp2p can be problematic for what we do +TODO - How we transpose our certificates to libp2p certificates + +#### Major issues + +TODO - mDNS issues on Linux +TODO - The double up of service discovery when using local and relay + +TODO - Question? Why does remote_identity_of_self show up in metadata and the mDNS record itself. + +{/* TODO */} + +TODO - Request flow. Eg. incoming goes from Quic to mpsc to the users code, to the handlers. +TODO - Resumable uploads/transfers diff --git a/docs/developers/technology/rspc.mdx b/docs/developers/technology/rspc.mdx index 23056d19f..e42f6a93b 100644 --- a/docs/developers/technology/rspc.mdx +++ b/docs/developers/technology/rspc.mdx @@ -3,6 +3,8 @@ title: ​rspc index: 13 --- +# rspc + We use a fork based on [rspc 0.1.4](https://docs.rs/rspc) which contains heavy modifications from the upstream. ## What's different? @@ -66,17 +68,19 @@ This is due to a bug in the future that resolves requests. This was fixed [upstr A panic will also take out the entire request which could contain a batch of multiple procedures. +This will likely cause the frontend request to hang indefinitely. + ### Blocking -### Batching +#### Batching This applies to both Tauri and Axum when using the batch link (**which Spacedrive uses**). Each request within a batch is effectively run in serial. This may or may not make a major difference as the response can't be send until all items are ready but having to run them in parallel would be faster regardless. -### Tauri +#### Tauri Minus batching everything is run in parallel. -### Axum +#### Axum All queries and mutations run within a single websocket connection (**which Spacedrive uses**) are run in serial. diff --git a/interface/app/$libraryId/Explorer/Inspector/index.tsx b/interface/app/$libraryId/Explorer/Inspector/index.tsx index 5e3cf41dd..5f9116c37 100644 --- a/interface/app/$libraryId/Explorer/Inspector/index.tsx +++ b/interface/app/$libraryId/Explorer/Inspector/index.tsx @@ -483,7 +483,7 @@ const MultiItemMetadata = ({ items }: { items: ExplorerItem[] }) => { getExplorerItemData(item); if (item.type !== 'NonIndexedPath' || !item.item.is_dir) { - metadata.size = (metadata.size ?? BigInt(0)) + BigInt(size.original); + metadata.size = (metadata.size ?? 0n) + size.bytes; } if (dateCreated) diff --git a/interface/app/$libraryId/overview/StatCard.tsx b/interface/app/$libraryId/overview/StatCard.tsx index 0e2aa73b8..2554ec755 100644 --- a/interface/app/$libraryId/overview/StatCard.tsx +++ b/interface/app/$libraryId/overview/StatCard.tsx @@ -47,7 +47,6 @@ const StatCard = ({ icon, name, devices, connectionType, ...stats }: StatCardPro if (!mounted || totalSpace.original === 0) return 0; return Math.floor((usedSpace.value / totalSpace.value) * 100); }, [mounted, totalSpace, usedSpace]); - const { t } = useLocale(); return ( diff --git a/interface/app/$libraryId/settings/Sidebar.tsx b/interface/app/$libraryId/settings/Sidebar.tsx index 53d70d134..a4ce570c6 100644 --- a/interface/app/$libraryId/settings/Sidebar.tsx +++ b/interface/app/$libraryId/settings/Sidebar.tsx @@ -117,12 +117,10 @@ export default () => { Saved Searches */} - {useFeatureFlag('cloudSync') && ( - - - {t('sync')} - - )} + + + {t('sync')} + {t('clouds')} diff --git a/interface/app/$libraryId/settings/client/general.tsx b/interface/app/$libraryId/settings/client/general.tsx index 68442ae5c..da16765d9 100644 --- a/interface/app/$libraryId/settings/client/general.tsx +++ b/interface/app/$libraryId/settings/client/general.tsx @@ -347,35 +347,37 @@ export const Component = () => { /> + + {t('p2p_visibility_description')} +

+ } + > + +
+ {isP2PWipFeatureEnabled && ( <> - - {t('spacedrop_description')} -

- } - > - -
- { useLibrarySubscription(['library.actors'], { onData: setData }); + const cloudSync = useFeatureFlag('cloudSync'); + return ( <> @@ -78,7 +81,8 @@ export const Component = () => { )} - + + {cloudSync && } )} diff --git a/interface/locales/en/common.json b/interface/locales/en/common.json index 33197bbe5..d39723038 100644 --- a/interface/locales/en/common.json +++ b/interface/locales/en/common.json @@ -571,6 +571,11 @@ "spacedrop_disabled": "Disabled", "spacedrop_everyone": "Everyone", "spacedrop_rejected": "Spacedrop rejected", + "p2p_visibility": "P2P Visibility", + "p2p_visibility_description": "Configure who can see your Spacedrive installation.", + "p2p_visibility_everyone": "Everyone", + "p2p_visibility_contacts_only": "Contacts Only", + "p2p_visibility_disabled": "Disabled", "square_thumbnails": "Square Thumbnails", "star_on_github": "Star on GitHub", "start_time": "Start Time", diff --git a/package.json b/package.json index f8ad78c65..421958aac 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,5 @@ "eslintConfig": { "root": true }, - "packageManager": "pnpm@9.1.0", - "engineStrict": false + "packageManager": "pnpm@9.1.1" } diff --git a/packages/client/src/lib/humanizeSize.ts b/packages/client/src/lib/humanizeSize.ts index 2945265b7..0da066abc 100644 --- a/packages/client/src/lib/humanizeSize.ts +++ b/packages/client/src/lib/humanizeSize.ts @@ -89,7 +89,7 @@ export interface ByteSizeOpts { } /** - * Returns an object with the spec `{ value: string, unit: string, long: string }`. The returned object defines a `toString` method meaning it can be used in any string context. + * Returns an object with the spec `{ unit: string, long: string, bytes: bigint, value: number }`. The returned object defines a `toString` method meaning it can be used in any string context. * * @param value - The bytes value to convert. * @param options - Optional config. @@ -111,9 +111,16 @@ export const humanizeSize = ( ) => { if (value == null) value = 0n; if (Array.isArray(value)) value = bytesToNumber(value); - else if (typeof value === 'number') value = BigInt(value | 0); else if (typeof value !== 'bigint') value = BigInt(value); - const [isNegative, bytes] = value < 0n ? [true, -value] : [false, value]; + const [isNegative, bytes] = + typeof value === 'number' + ? value < 0 + ? // Note: These magic shift operations internally convert value from f64 to u32 + [true, BigInt(-value >>> 0)] + : [false, BigInt(value >>> 0)] + : value < 0n + ? [true, -value] + : [false, value]; const unit = getBaseUnit(bytes, base_unit === 'decimal' ? DECIMAL_UNITS : BINARY_UNITS); const defaultFormat = new Intl.NumberFormat(locales, { @@ -126,14 +133,15 @@ export const humanizeSize = ( unit.from === 0n ? Number(bytes) : Number((bytes * BigInt(precisionFactor)) / unit.from) / precisionFactor; + const plural = use_plural && value !== 1 ? 's' : ''; return { unit: is_bit ? BYTE_TO_BIT[unit.short as keyof typeof BYTE_TO_BIT] : unit.short, long: is_bit ? BYTE_TO_BIT[unit.long as keyof typeof BYTE_TO_BIT] : unit.long, + bytes, value: (isNegative ? -1 : 1) * value, - original: value, toString() { - return `${defaultFormat.format(this.value)} ${this.unit}`; + return `${defaultFormat.format(this.value)} ${this.unit}${plural}`; } }; }; diff --git a/packages/client/src/utils/index.ts b/packages/client/src/utils/index.ts index fb650c5d0..7ad20f405 100644 --- a/packages/client/src/utils/index.ts +++ b/packages/client/src/utils/index.ts @@ -138,7 +138,7 @@ export function insertLibrary(queryClient: QueryClient, library: LibraryConfigWr } export function int32ArrayToBigInt([high, low]: [number, number]) { - // Note: These magic shift operations internally convert the high into i32 and the low into u32 + // Note: These magic shift operations internally convert high into i32 and low into u32 return (BigInt(high | 0) << 32n) | BigInt(low >>> 0); }