Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix spacedrive:// custom protocol on Windows #550

Merged
merged 41 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2399a8b
fix `spacedrive://` custom protocol on Windows
oscartbeaumont Jan 25, 2023
2d2f4c3
custom protocol using `http::Response` + fix broken web
oscartbeaumont Jan 25, 2023
48a5862
import patches before App on web
oscartbeaumont Jan 25, 2023
975d9ef
use `http::Request` for input to `handle_custom_uri`
oscartbeaumont Jan 25, 2023
2b293bb
break into dedicated file + error handling
oscartbeaumont Jan 25, 2023
16f36b2
serving files via custom protocol
oscartbeaumont Jan 25, 2023
0943ddd
cargo fmt because vscode did cringe
oscartbeaumont Jan 25, 2023
1a7ee02
lru cache to reduce video chunk request time
oscartbeaumont Jan 25, 2023
a887380
add helper to JS
oscartbeaumont Jan 25, 2023
ad0cec8
clippy be like
oscartbeaumont Jan 25, 2023
465af8b
remove duplicate Open buttons in context menu
oscartbeaumont Jan 25, 2023
9090d92
fix Linux :pray:
oscartbeaumont Jan 25, 2023
7acbea5
no shot
oscartbeaumont Jan 25, 2023
06c4c64
fix Windows custom URI passing (hopefully)
oscartbeaumont Jan 25, 2023
aba14ac
better fix for custom uri on Linux
oscartbeaumont Jan 25, 2023
bf6e892
upgrade Tauri for feature
oscartbeaumont Jan 25, 2023
25ac2e3
switch url replacement order
oscartbeaumont Jan 25, 2023
c44a593
prevent React dev tools script being added in prod to desktop
oscartbeaumont Jan 25, 2023
a1e7719
remove React devtools from html
oscartbeaumont Jan 25, 2023
7380bc2
upgrade Tauri; required upgrading rspc, Axum, PCR
oscartbeaumont Feb 2, 2023
26d5f20
Merge branch 'main' into load-files
oscartbeaumont Feb 2, 2023
d0d23e4
pass typecheck + less cringe bigint
oscartbeaumont Feb 2, 2023
8ed9d8d
clippy is love, clippy is life
oscartbeaumont Feb 2, 2023
8a1424b
Typecheck plz
oscartbeaumont Feb 2, 2023
1e3b645
fix bigint to number conversion
oscartbeaumont Feb 2, 2023
0a090c9
use httpz + localhost server for Linux
oscartbeaumont Feb 7, 2023
4adabf8
clippy be right
oscartbeaumont Feb 7, 2023
2afc690
Remove console.log
oscartbeaumont Feb 7, 2023
a91f2c3
[wip] proper auth
oscartbeaumont Feb 9, 2023
3c6b361
fix Linux sidebar padding
oscartbeaumont Feb 9, 2023
bd6ef41
Secure Axum server with random
oscartbeaumont Feb 9, 2023
5d5549d
Extracting app setup specific to linux to a different file
fogodev Feb 12, 2023
28c6cf5
remove outdated comment
oscartbeaumont Feb 12, 2023
6de57da
Some tweaks on cursom_uri.rs
fogodev Feb 12, 2023
95d5246
Merge branch 'load-files' of github.com:spacedriveapp/spacedrive into…
fogodev Feb 12, 2023
a64f955
Merge branch 'main' of github.com:spacedriveapp/spacedrive into load-…
fogodev Feb 12, 2023
fb3bbfe
file_path_with_location doesn't need to be a named include
oscartbeaumont Feb 13, 2023
9025cb0
fix typo
oscartbeaumont Feb 13, 2023
e1b0039
factually wrong comment
oscartbeaumont Feb 13, 2023
3622783
Change `unwrap` to `expect`
oscartbeaumont Feb 13, 2023
eef2423
bruh
oscartbeaumont Feb 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
543 changes: 363 additions & 180 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@ members = [
]

[workspace.dependencies]
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.6.4", features = [
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "c965b89f1a07a6931d90f4b5556421f7ffcda03b", features = [
"rspc",
"sqlite-create-many",
"migrations",
"sqlite",
], default-features = false }
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.6.4", features = [
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "c965b89f1a07a6931d90f4b5556421f7ffcda03b", features = [
"rspc",
"sqlite-create-many",
"migrations",
"sqlite",
], default-features = false }
prisma-client-rust-sdk = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.6.4", features = [
prisma-client-rust-sdk = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "c965b89f1a07a6931d90f4b5556421f7ffcda03b", features = [
"sqlite",
], default-features = false }

rspc = { version = "0.1.2" }
normi = { version = "0.0.1" }
specta = { version = "0.0.4" }
specta = { version = "0.0.6" }
httpz = { version = "0.0.3" }

swift-rs = { git = "https://github.com/Brendonovich/swift-rs.git", rev = "833e29ba333f1dfe303eaa21de78c4f8c5a3f2ff" }

Expand All @@ -40,6 +40,6 @@ tokio = { version = "1.25.0" }
# We use this patch so we can compile for the IOS simulator on M1
openssl-sys = { git = "https://github.com/spacedriveapp/rust-openssl", rev = "92c3dec225a9e984884d5b30a517e5d44a24d03b" }

rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "6243b5b6a1376940a40318340e5eaef22e4a2c22" } # TODO: Move back to crates.io when new jsonrpc executor + `tokio::spawn` in the Tauri IPC plugin is released
normi = { git = "https://github.com/oscartbeaumont/rspc", rev = "6243b5b6a1376940a40318340e5eaef22e4a2c22" } # TODO: When normi is released on crates.io
specta = { git = "https://github.com/oscartbeaumont/rspc", rev = "6243b5b6a1376940a40318340e5eaef22e4a2c22" } # TODO: When normi is released on crates.io
rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "c03872c0ba29d2429e9c059dfb235cdd03e15e8c" } # TODO: Move back to crates.io when new jsonrpc executor + `tokio::spawn` in the Tauri IPC plugin + upgraded Tauri version is released
specta = { git = "https://github.com/oscartbeaumont/rspc", rev = "c03872c0ba29d2429e9c059dfb235cdd03e15e8c" }
httpz = { git = "https://github.com/oscartbeaumont/httpz", rev = "a5185f2ed2fdefeb2f582dce38a692a1bf76d1d6" }
4 changes: 2 additions & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
"@sd/client": "workspace:*",
"@sd/interface": "workspace:*",
"@sd/ui": "workspace:*",
"@tauri-apps/api": "1.1.0",
"@tauri-apps/api": "1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@sd/config": "workspace:*",
"@tauri-apps/cli": "1.1.1",
"@tauri-apps/cli": "1.2.3",
"@types/babel-core": "^6.25.7",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
Expand Down
11 changes: 10 additions & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,22 @@ edition = "2021"
build = "build.rs"

[dependencies]
tauri = { version = "1.1.1", features = ["api-all", "macos-private-api"] }
tauri = { version = "1.2.4", features = ["api-all", "linux-protocol-headers", "macos-private-api"] }
rspc = { workspace = true, features = ["tauri"] }
httpz = { workspace = true, features = ["axum", "tauri"] } # TODO: The `axum` feature should be only enabled on Linux but this currently can't be done: https://github.com/rust-lang/cargo/issues/1197
sd-core = { path = "../../../core", features = ["ffmpeg", "location-watcher"] }
tokio = { workspace = true, features = ["sync"] }
window-shadows = "0.2.0"
tracing = "0.1.36"
serde = "1.0.145"
percent-encoding = "2.2.0"
http = "0.2.8"

[target.'cfg(target_os = "linux")'.dependencies]
server = { path = "../../server" }
axum = "0.6.4"
rand = "0.8.5"
url = "2.1.1"

[target.'cfg(target_os = "macos")'.dependencies]
swift-rs.workspace = true
Expand Down
95 changes: 95 additions & 0 deletions apps/desktop/src-tauri/src/app_linux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::{
net::{SocketAddr, TcpListener},
sync::Arc,
};

use sd_core::Node;

use axum::{
extract::State,
http::{Request, StatusCode},
middleware::{self, Next},
response::{IntoResponse, Response},
routing::get,
};
use httpz::{Endpoint, HttpEndpoint};
use rand::{distributions::Alphanumeric, Rng};
use tauri::{plugin::TauriPlugin, Builder, Runtime};
use tracing::debug;
use url::Url;

pub(super) async fn setup<R: Runtime>(
app: Builder<R>,
node: Arc<Node>,
endpoint: Endpoint<impl HttpEndpoint>,
) -> Builder<R> {
let signal = server::utils::axum_shutdown_signal(node);

let auth_token: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(10)
.map(char::from)
.collect();

let axum_app = axum::Router::new()
.route("/", get(|| async { "Spacedrive Server!" }))
.nest("/spacedrive", endpoint.axum())
.route_layer(middleware::from_fn_with_state(
auth_token.clone(),
auth_middleware,
))
.fallback(|| async { "404 Not Found: We're past the event horizon..." });

// Only allow current device to access it and randomise port
let listener = TcpListener::bind("127.0.0.1:0").expect("Error creating localhost server!");
let listen_addr = listener
.local_addr()
.expect("Error getting localhost server listen addr!");

debug!("Localhost server listening on: http://{:?}", listen_addr);

tokio::spawn(async move {
axum::Server::from_tcp(listener)
.expect("error creating HTTP server!")
.serve(axum_app.into_make_service())
.with_graceful_shutdown(signal)
.await
.expect("Error with HTTP server!");
});

app.plugin(spacedrive_plugin_init(&auth_token, listen_addr))
}

async fn auth_middleware<B>(
State(auth_token): State<String>,
request: Request<B>,
next: Next<B>,
) -> Response {
let url = Url::parse(&request.uri().to_string()).unwrap();
if let Some((_, v)) = url.query_pairs().find(|(k, _)| k == "token") {
if v == auth_token {
return next.run(request).await;
}
} else if let Some(v) = request
.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok())
{
if v == auth_token {
return next.run(request).await;
}
}

(StatusCode::UNAUTHORIZED, "Unauthorized!").into_response()
}

pub fn spacedrive_plugin_init<R: Runtime>(
auth_token: &str,
listen_addr: SocketAddr,
) -> TauriPlugin<R> {
tauri::plugin::Builder::new("spacedrive")
.js_init_script(format!(
r#"window.__SD_CUSTOM_SERVER_AUTH_TOKEN__ = "{auth_token}"; window.__SD_CUSTOM_URI_SERVER__ = "http://{listen_addr}";"#
))
.build()
}
61 changes: 27 additions & 34 deletions apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,20 @@
windows_subsystem = "windows"
)]

use std::error::Error;
use std::path::PathBuf;
use std::time::Duration;

use sd_core::Node;
use tauri::async_runtime::block_on;
use tauri::{
api::path,
http::{ResponseBuilder, Uri},
Manager, RunEvent,
};
use tokio::task::block_in_place;
use tokio::time::sleep;
use std::{error::Error, path::PathBuf, sync::Arc, time::Duration};

use sd_core::{custom_uri::create_custom_uri_endpoint, Node};

use tauri::{api::path, async_runtime::block_on, Manager, RunEvent};
use tokio::{task::block_in_place, time::sleep};
use tracing::{debug, error};

#[cfg(target_os = "macos")]
mod macos;

#[cfg(target_os = "linux")]
mod app_linux;

mod menu;

#[tauri::command(async)]
Expand All @@ -41,26 +37,21 @@ async fn main() -> Result<(), Box<dyn Error>> {

let (node, router) = Node::new(data_dir).await?;

let app = tauri::Builder::default()
.plugin(rspc::integrations::tauri::plugin(router, {
let node = node.clone();
move || node.get_request_context()
}))
.register_uri_scheme_protocol("spacedrive", {
let node = node.clone();
move |_, req| {
let url = req.uri().parse::<Uri>().unwrap();
let mut path = url.path().split('/').collect::<Vec<_>>();
path[0] = url.host().unwrap(); // The first forward slash causes an empty item and we replace it with the URL's host which you expect to be at the start

let (status_code, content_type, body) =
block_in_place(|| block_on(node.handle_custom_uri(path)));
ResponseBuilder::new()
.status(status_code)
.mimetype(content_type)
.body(body)
}
})
let app = tauri::Builder::default().plugin(rspc::integrations::tauri::plugin(router, {
let node = Arc::clone(&node);
move || node.get_request_context()
}));

// This is a super cringe workaround for: https://github.com/tauri-apps/tauri/issues/3725 & https://bugs.webkit.org/show_bug.cgi?id=146351#c5
let endpoint = create_custom_uri_endpoint(Arc::clone(&node));

#[cfg(target_os = "linux")]
let app = app_linux::setup(app, Arc::clone(&node), endpoint).await;

#[cfg(not(target_os = "linux"))]
let app = app.register_uri_scheme_protocol("spacedrive", endpoint.tauri_uri_scheme("spacedrive"));

let app = app
.setup(|app| {
let app = app.handle();
app.windows().iter().for_each(|(_, window)| {
Expand All @@ -71,7 +62,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
async move {
sleep(Duration::from_secs(3)).await;
if !window.is_visible().unwrap_or(true) {
println!("Window did not emit `app_ready` event fast enough. Showing window...");
println!(
"Window did not emit `app_ready` event fast enough. Showing window..."
);
let _ = window.show();
}
}
Expand Down
23 changes: 22 additions & 1 deletion apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { loggerLink } from '@rspc/client';
import { tauriLink } from '@rspc/tauri';
import { dialog, invoke, os, shell } from '@tauri-apps/api';
import { listen } from '@tauri-apps/api/event';
import { convertFileSrc } from '@tauri-apps/api/tauri';
import { useEffect } from 'react';
import { getDebugState, hooks, queryClient } from '@sd/client';
import SpacedriveInterface, { OperatingSystem, Platform, PlatformProvider } from '@sd/interface';
Expand Down Expand Up @@ -30,9 +31,29 @@ async function getOs(): Promise<OperatingSystem> {
}
}

let customUriServerUrl = (window as any).__SD_CUSTOM_URI_SERVER__ as string | undefined;
const customUriAuthToken = (window as any).__SD_CUSTOM_URI_TOKEN__ as string | undefined;

if (customUriServerUrl && !customUriServerUrl?.endsWith('/')) {
customUriServerUrl += '/';
}

function getCustomUriURL(path: string): string {
if (customUriServerUrl) {
const queryParams = customUriAuthToken
? `?token=${encodeURIComponent(customUriAuthToken)}`
: '';
return `${customUriServerUrl}spacedrive/${path}${queryParams}`;
} else {
return convertFileSrc(path, 'spacedrive');
}
}

const platform: Platform = {
platform: 'tauri',
getThumbnailUrlById: (casId) => `spacedrive://thumbnail/${encodeURIComponent(casId)}`,
getThumbnailUrlById: (casId) => getCustomUriURL(`thumbnail/${casId}`),
getFileUrl: (libraryId, locationLocalId, filePathId) =>
getCustomUriURL(`file/${libraryId}/${locationLocalId}/${filePathId}`),
openLink: shell.open,
getOs,
openDirectoryPickerDialog: () => dialog.open({ directory: true }),
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
</head>
<body style="overflow: hidden">
<div id="root"></div>
<!-- Script for React devtools. TODO: Make sure this isn't included in production builds. -->
<script src="http://localhost:8097"></script>
<script type="module" src="./index.tsx"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions apps/desktop/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import '@sd/ui/style';
import '~/patches';
import App from './App';

// React dev tools extension
if (import.meta.env.DEV) {
var script = document.createElement('script');
script.src = 'http://localhost:8097';
document.head.appendChild(script);
}

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
Expand Down
9 changes: 2 additions & 7 deletions apps/mobile/rust/android/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ pub extern "system" fn Java_com_spacedrive_app_SDCore_registerCoreEventListener(
if let Err(err) = result {
// TODO: Send rspc error or something here so we can show this in the UI.
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
println!(
"Error in Java_com_spacedrive_app_SDCore_registerCoreEventListener: {:?}",
err
);
println!("Error in Java_com_spacedrive_app_SDCore_registerCoreEventListener: {err:?}");
}
}

Expand Down Expand Up @@ -86,9 +83,7 @@ pub extern "system" fn Java_com_spacedrive_app_SDCore_handleCoreMsg(
)
.unwrap();
}
Err(_) => {
// TODO: handle error
}
Err(err) => error!(err),
});
});

Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/rust/mobile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub fn spawn_core_event_listener(callback: impl Fn(String) + Send + 'static) {
let data = match to_string(&event) {
Ok(json) => json,
Err(err) => {
println!("Failed to serialize event: {}", err);
println!("Failed to serialize event: {err}");
continue;
}
};
Expand Down
11 changes: 6 additions & 5 deletions apps/mobile/src/containers/OverviewStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const StatItemNames: Partial<Record<keyof Statistics, string>> = {
total_bytes_free: 'Free space'
};

const StatItem: FC<{ title: string; bytes: number }> = ({ title, bytes }) => {
const { value, unit } = byteSize(+bytes);
const StatItem: FC<{ title: string; bytes: bigint }> = ({ title, bytes }) => {
const { value, unit } = byteSize(Number(bytes)); // TODO: This BigInt to Number conversion will truncate the number if the number is too large. `byteSize` doesn't support BigInt so we are gonna need to come up with a longer term solution at some point.

const count = useCounter({ name: title, end: Number(value) });

Expand Down Expand Up @@ -51,12 +51,13 @@ const OverviewStats = () => {

return libraryStatistics ? (
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{Object.entries(libraryStatistics).map(([key, bytes]) => {
{Object.entries(libraryStatistics).map(([key, bytesRaw]) => {
if (!displayableStatItems.includes(key)) return null;
let bytes = BigInt(bytesRaw);
if (key === 'total_bytes_free') {
bytes = sizeInfo.freeSpace;
bytes = BigInt(sizeInfo.freeSpace);
} else if (key === 'total_bytes_capacity') {
bytes = sizeInfo.totalSpace;
bytes = BigInt(sizeInfo.totalSpace);
}
return <StatItem key={key} title={StatItemNames[key as keyof Statistics]!} bytes={bytes} />;
})}
Expand Down
6 changes: 1 addition & 5 deletions apps/mobile/src/stores/explorerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { resetStore } from '@sd/client';
// TODO: Add "media"
export type ExplorerLayoutMode = 'list' | 'grid';

export enum ExplorerKind {
Location,
Tag,
Space
}
export type ExplorerKind = 'Location' | 'Tag' | 'Space';

const state = {
locationId: null as number | null,
Expand Down
Loading