feat: refine download UX and native host flow
This commit is contained in:
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -77,7 +77,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"log",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
mod engine;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tauri::Manager;
|
||||
|
||||
@@ -46,6 +48,145 @@ struct ExternalAddRequest {
|
||||
split: Option<u32>,
|
||||
}
|
||||
|
||||
fn default_extension_ids() -> Vec<String> {
|
||||
vec![
|
||||
"alaohbbicffclloghmknhlmfdbobcigc".to_string(),
|
||||
"makoclohjdpempbndoaljeadpngefhcf".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn collect_extension_ids() -> Vec<String> {
|
||||
let mut ids = default_extension_ids();
|
||||
for key in ["GDOWN_EXTENSION_ID", "GDOWN_EXTENSION_IDS"] {
|
||||
if let Ok(raw) = env::var(key) {
|
||||
for id in raw.split([',', ' ', '\n', '\t']) {
|
||||
let trimmed = id.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if !ids.iter().any(|v| v == trimmed) {
|
||||
ids.push(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ids
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn merge_existing_allowed_origins(manifest_path: &Path, ids: &mut Vec<String>) {
|
||||
let content = match fs::read_to_string(manifest_path) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return,
|
||||
};
|
||||
let parsed: serde_json::Value = match serde_json::from_str(&content) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return,
|
||||
};
|
||||
let Some(origins) = parsed.get("allowed_origins").and_then(|v| v.as_array()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for origin in origins {
|
||||
let Some(value) = origin.as_str() else {
|
||||
continue;
|
||||
};
|
||||
let prefix = "chrome-extension://";
|
||||
if !value.starts_with(prefix) {
|
||||
continue;
|
||||
}
|
||||
let id = value.trim_start_matches(prefix).trim_end_matches('/');
|
||||
if id.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if !ids.iter().any(|v| v == id) {
|
||||
ids.push(id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_node_path() -> Option<PathBuf> {
|
||||
if let Ok(path_var) = env::var("PATH") {
|
||||
for dir in env::split_paths(&path_var) {
|
||||
let node = dir.join("node");
|
||||
if node.is_file() {
|
||||
return Some(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for candidate in ["/usr/local/bin/node", "/opt/homebrew/bin/node", "/usr/bin/node"] {
|
||||
let path = PathBuf::from(candidate);
|
||||
if path.is_file() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn ensure_native_host_installed() -> Result<PathBuf, String> {
|
||||
let host_name = "org.gdown.nativehost";
|
||||
let home = env::var("HOME").map_err(|err| format!("HOME 경로 확인 실패: {err}"))?;
|
||||
let tools_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join("tools")
|
||||
.join("native-host");
|
||||
let host_script = tools_dir.join("host.mjs");
|
||||
if !host_script.is_file() {
|
||||
return Err(format!("native host script not found: {}", host_script.display()));
|
||||
}
|
||||
|
||||
let node_path = resolve_node_path().ok_or_else(|| "node 경로를 찾을 수 없습니다.".to_string())?;
|
||||
let runtime_dir = tools_dir.join(".runtime");
|
||||
fs::create_dir_all(&runtime_dir).map_err(|err| format!("runtime 디렉터리 생성 실패: {err}"))?;
|
||||
|
||||
let runner_path = runtime_dir.join("run-host-macos.sh");
|
||||
let runner_content = format!(
|
||||
"#!/usr/bin/env bash\nset -euo pipefail\nexec \"{}\" \"{}\"\n",
|
||||
node_path.display(),
|
||||
host_script.display()
|
||||
);
|
||||
fs::write(&runner_path, runner_content).map_err(|err| format!("runner 생성 실패: {err}"))?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let perm = fs::Permissions::from_mode(0o755);
|
||||
fs::set_permissions(&runner_path, perm).map_err(|err| format!("runner 권한 설정 실패: {err}"))?;
|
||||
}
|
||||
|
||||
let chrome_hosts_dir = Path::new(&home)
|
||||
.join("Library")
|
||||
.join("Application Support")
|
||||
.join("Google")
|
||||
.join("Chrome")
|
||||
.join("NativeMessagingHosts");
|
||||
fs::create_dir_all(&chrome_hosts_dir).map_err(|err| format!("native host 디렉터리 생성 실패: {err}"))?;
|
||||
let manifest_path = chrome_hosts_dir.join(format!("{host_name}.json"));
|
||||
|
||||
let mut extension_ids = collect_extension_ids();
|
||||
merge_existing_allowed_origins(&manifest_path, &mut extension_ids);
|
||||
let allowed_origins = extension_ids
|
||||
.into_iter()
|
||||
.map(|id| format!("chrome-extension://{id}/"))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let manifest = serde_json::json!({
|
||||
"name": host_name,
|
||||
"description": "gdown Native Messaging Host",
|
||||
"path": runner_path,
|
||||
"type": "stdio",
|
||||
"allowed_origins": allowed_origins,
|
||||
});
|
||||
|
||||
let manifest_text = serde_json::to_string_pretty(&manifest)
|
||||
.map_err(|err| format!("manifest 직렬화 실패: {err}"))?;
|
||||
fs::write(&manifest_path, manifest_text).map_err(|err| format!("manifest 쓰기 실패: {err}"))?;
|
||||
|
||||
Ok(manifest_path)
|
||||
}
|
||||
|
||||
fn external_add_queue_path() -> Result<PathBuf, String> {
|
||||
let home = std::env::var("HOME").map_err(|err| format!("HOME 경로 확인 실패: {err}"))?;
|
||||
Ok(PathBuf::from(home).join(".gdown").join("external_add_queue.jsonl"))
|
||||
@@ -102,6 +243,13 @@ pub fn run() {
|
||||
}
|
||||
})
|
||||
.setup(|app| {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Err(err) = ensure_native_host_installed() {
|
||||
eprintln!("[gdown] native host auto-install skipped: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "gdown",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"identifier": "com.tauri.dev",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
@@ -15,9 +15,9 @@
|
||||
"label": "main",
|
||||
"title": "gdown",
|
||||
"width": 1280,
|
||||
"height": 860,
|
||||
"height": 660,
|
||||
"minWidth": 1080,
|
||||
"minHeight": 720,
|
||||
"minHeight": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user