Mayo commited on
chore: add more context info
Browse files- Cargo.lock +0 -3
- README.md +3 -3
- koharu-ml/Cargo.toml +0 -2
- koharu-runtime/Cargo.toml +0 -1
- koharu-runtime/src/dylib.rs +21 -9
- koharu-runtime/src/lib.rs +0 -1
- koharu-runtime/src/pypi.rs +0 -44
- koharu/src/app.rs +15 -9
Cargo.lock
CHANGED
|
@@ -4131,8 +4131,6 @@ dependencies = [
|
|
| 4131 |
"objc2-metal-performance-shaders",
|
| 4132 |
"objc2-metal-performance-shaders-graph",
|
| 4133 |
"once_cell",
|
| 4134 |
-
"rayon",
|
| 4135 |
-
"reqwest",
|
| 4136 |
"rustfft",
|
| 4137 |
"serde",
|
| 4138 |
"serde_json",
|
|
@@ -4181,7 +4179,6 @@ dependencies = [
|
|
| 4181 |
"rayon",
|
| 4182 |
"reqwest",
|
| 4183 |
"serde_json",
|
| 4184 |
-
"strum",
|
| 4185 |
"tempfile",
|
| 4186 |
"tokio",
|
| 4187 |
"tracing",
|
|
|
|
| 4131 |
"objc2-metal-performance-shaders",
|
| 4132 |
"objc2-metal-performance-shaders-graph",
|
| 4133 |
"once_cell",
|
|
|
|
|
|
|
| 4134 |
"rustfft",
|
| 4135 |
"serde",
|
| 4136 |
"serde_json",
|
|
|
|
| 4179 |
"rayon",
|
| 4180 |
"reqwest",
|
| 4181 |
"serde_json",
|
|
|
|
| 4182 |
"tempfile",
|
| 4183 |
"tokio",
|
| 4184 |
"tracing",
|
README.md
CHANGED
|
@@ -39,12 +39,12 @@ Koharu can be run in headless mode via command line.
|
|
| 39 |
|
| 40 |
```bash
|
| 41 |
# macOS / Linux
|
| 42 |
-
koharu -
|
| 43 |
# Windows
|
| 44 |
-
koharu.exe -
|
| 45 |
```
|
| 46 |
|
| 47 |
-
You can now access Koharu Web UI at `http://<your-server-ip>:
|
| 48 |
|
| 49 |
### File association
|
| 50 |
|
|
|
|
| 39 |
|
| 40 |
```bash
|
| 41 |
# macOS / Linux
|
| 42 |
+
koharu --port 4000 --headless
|
| 43 |
# Windows
|
| 44 |
+
koharu.exe --port 4000 --headless
|
| 45 |
```
|
| 46 |
|
| 47 |
+
You can now access Koharu Web UI at `http://<your-server-ip>:4000`.
|
| 48 |
|
| 49 |
### File association
|
| 50 |
|
koharu-ml/Cargo.toml
CHANGED
|
@@ -29,9 +29,7 @@ tracing = { workspace = true }
|
|
| 29 |
tracing-subscriber = { workspace = true }
|
| 30 |
tokio = { workspace = true }
|
| 31 |
rustfft = { workspace = true }
|
| 32 |
-
reqwest = { workspace = true }
|
| 33 |
once_cell = { workspace = true }
|
| 34 |
-
rayon = { workspace = true }
|
| 35 |
dirs = { workspace = true }
|
| 36 |
futures = { workspace = true }
|
| 37 |
num_cpus = { workspace = true }
|
|
|
|
| 29 |
tracing-subscriber = { workspace = true }
|
| 30 |
tokio = { workspace = true }
|
| 31 |
rustfft = { workspace = true }
|
|
|
|
| 32 |
once_cell = { workspace = true }
|
|
|
|
| 33 |
dirs = { workspace = true }
|
| 34 |
futures = { workspace = true }
|
| 35 |
num_cpus = { workspace = true }
|
koharu-runtime/Cargo.toml
CHANGED
|
@@ -24,7 +24,6 @@ futures = { workspace = true }
|
|
| 24 |
once_cell = { workspace = true }
|
| 25 |
libloading = { workspace = true }
|
| 26 |
num_cpus = { workspace = true }
|
| 27 |
-
strum = { workspace = true }
|
| 28 |
csv = { workspace = true }
|
| 29 |
flate2 = { workspace = true }
|
| 30 |
|
|
|
|
| 24 |
once_cell = { workspace = true }
|
| 25 |
libloading = { workspace = true }
|
| 26 |
num_cpus = { workspace = true }
|
|
|
|
| 27 |
csv = { workspace = true }
|
| 28 |
flate2 = { workspace = true }
|
| 29 |
|
koharu-runtime/src/dylib.rs
CHANGED
|
@@ -5,7 +5,7 @@ use std::{
|
|
| 5 |
sync::Arc,
|
| 6 |
};
|
| 7 |
|
| 8 |
-
use anyhow::Result;
|
| 9 |
use futures::stream::{self, StreamExt, TryStreamExt};
|
| 10 |
use koharu_core::http::{http_client, http_download};
|
| 11 |
use once_cell::sync::OnceCell;
|
|
@@ -13,7 +13,7 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
|
| 13 |
use tokio::task;
|
| 14 |
use tracing::debug;
|
| 15 |
|
| 16 |
-
use crate::
|
| 17 |
|
| 18 |
/// Keep handles to loaded dynamic libraries alive for process lifetime
|
| 19 |
static DYLIB_HANDLES: OnceCell<Vec<libloading::Library>> = OnceCell::new();
|
|
@@ -226,9 +226,16 @@ fn current_platform_tags() -> Result<&'static [&'static str]> {
|
|
| 226 |
|
| 227 |
async fn fetch_and_extract(pkg: &str, platform_tags: &[&str], out_dir: Arc<PathBuf>) -> Result<()> {
|
| 228 |
// 1) Query PyPI JSON
|
| 229 |
-
let meta_url = format!("
|
| 230 |
-
let resp = http_client()
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
// 2) Choose a wheel
|
| 234 |
let files = json
|
|
@@ -239,7 +246,6 @@ async fn fetch_and_extract(pkg: &str, platform_tags: &[&str], out_dir: Arc<PathB
|
|
| 239 |
for f in files {
|
| 240 |
let filename = f.get("filename").and_then(|v| v.as_str()).unwrap_or("");
|
| 241 |
let file_url = f.get("url").and_then(|v| v.as_str()).unwrap_or("");
|
| 242 |
-
let file_url = PYPI_ENDPOINT.refine_url(file_url);
|
| 243 |
|
| 244 |
if !filename.ends_with(".whl") {
|
| 245 |
continue;
|
|
@@ -253,7 +259,9 @@ async fn fetch_and_extract(pkg: &str, platform_tags: &[&str], out_dir: Arc<PathB
|
|
| 253 |
debug!("{pkg}: selected wheel {wheel_name}");
|
| 254 |
|
| 255 |
// 3) Use RECORD to check local dylibs; download only if needed
|
| 256 |
-
let entries = fetch_record(&wheel_url)
|
|
|
|
|
|
|
| 257 |
|
| 258 |
// Fast path: existence + size-only check; no hashing.
|
| 259 |
// If size is None and file exists, treat as OK (no further verification).
|
|
@@ -273,10 +281,14 @@ async fn fetch_and_extract(pkg: &str, platform_tags: &[&str], out_dir: Arc<PathB
|
|
| 273 |
|
| 274 |
if needs_download {
|
| 275 |
debug!("{pkg}: downloading {wheel_name}...");
|
| 276 |
-
let bytes = http_download(&wheel_url)
|
|
|
|
|
|
|
| 277 |
let out = Arc::clone(&out_dir);
|
| 278 |
|
| 279 |
-
task::spawn_blocking(move || extract_from_wheel(&bytes, out.as_ref()))
|
|
|
|
|
|
|
| 280 |
debug!("{pkg}: download and extract complete");
|
| 281 |
Ok(())
|
| 282 |
} else {
|
|
|
|
| 5 |
sync::Arc,
|
| 6 |
};
|
| 7 |
|
| 8 |
+
use anyhow::{Context, Result};
|
| 9 |
use futures::stream::{self, StreamExt, TryStreamExt};
|
| 10 |
use koharu_core::http::{http_client, http_download};
|
| 11 |
use once_cell::sync::OnceCell;
|
|
|
|
| 13 |
use tokio::task;
|
| 14 |
use tracing::debug;
|
| 15 |
|
| 16 |
+
use crate::zip::fetch_record;
|
| 17 |
|
| 18 |
/// Keep handles to loaded dynamic libraries alive for process lifetime
|
| 19 |
static DYLIB_HANDLES: OnceCell<Vec<libloading::Library>> = OnceCell::new();
|
|
|
|
| 226 |
|
| 227 |
async fn fetch_and_extract(pkg: &str, platform_tags: &[&str], out_dir: Arc<PathBuf>) -> Result<()> {
|
| 228 |
// 1) Query PyPI JSON
|
| 229 |
+
let meta_url = format!("https://pypi.org/pypi/{pkg}/json");
|
| 230 |
+
let resp = http_client()
|
| 231 |
+
.get(&meta_url)
|
| 232 |
+
.send()
|
| 233 |
+
.await
|
| 234 |
+
.context("failed to fetch package metadata")?;
|
| 235 |
+
let json: serde_json::Value = resp
|
| 236 |
+
.json()
|
| 237 |
+
.await
|
| 238 |
+
.context("failed to parse package metadata")?;
|
| 239 |
|
| 240 |
// 2) Choose a wheel
|
| 241 |
let files = json
|
|
|
|
| 246 |
for f in files {
|
| 247 |
let filename = f.get("filename").and_then(|v| v.as_str()).unwrap_or("");
|
| 248 |
let file_url = f.get("url").and_then(|v| v.as_str()).unwrap_or("");
|
|
|
|
| 249 |
|
| 250 |
if !filename.ends_with(".whl") {
|
| 251 |
continue;
|
|
|
|
| 259 |
debug!("{pkg}: selected wheel {wheel_name}");
|
| 260 |
|
| 261 |
// 3) Use RECORD to check local dylibs; download only if needed
|
| 262 |
+
let entries = fetch_record(&wheel_url)
|
| 263 |
+
.await
|
| 264 |
+
.context("failed to fetch RECORD")?;
|
| 265 |
|
| 266 |
// Fast path: existence + size-only check; no hashing.
|
| 267 |
// If size is None and file exists, treat as OK (no further verification).
|
|
|
|
| 281 |
|
| 282 |
if needs_download {
|
| 283 |
debug!("{pkg}: downloading {wheel_name}...");
|
| 284 |
+
let bytes = http_download(&wheel_url)
|
| 285 |
+
.await
|
| 286 |
+
.context("failed to download wheel")?;
|
| 287 |
let out = Arc::clone(&out_dir);
|
| 288 |
|
| 289 |
+
task::spawn_blocking(move || extract_from_wheel(&bytes, out.as_ref()))
|
| 290 |
+
.await?
|
| 291 |
+
.context("failed to extract dylibs")?;
|
| 292 |
debug!("{pkg}: download and extract complete");
|
| 293 |
Ok(())
|
| 294 |
} else {
|
koharu-runtime/src/lib.rs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
mod dylib;
|
| 2 |
mod fonts;
|
| 3 |
-
mod pypi;
|
| 4 |
mod zip;
|
| 5 |
|
| 6 |
pub use dylib::{ensure_dylibs, preload_dylibs};
|
|
|
|
| 1 |
mod dylib;
|
| 2 |
mod fonts;
|
|
|
|
| 3 |
mod zip;
|
| 4 |
|
| 5 |
pub use dylib::{ensure_dylibs, preload_dylibs};
|
koharu-runtime/src/pypi.rs
DELETED
|
@@ -1,44 +0,0 @@
|
|
| 1 |
-
use std::time::{Duration, Instant};
|
| 2 |
-
|
| 3 |
-
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
| 4 |
-
use strum::Display;
|
| 5 |
-
|
| 6 |
-
pub static PYPI_ENDPOINT: once_cell::sync::Lazy<&PypiEndpoint> = once_cell::sync::Lazy::new(|| {
|
| 7 |
-
[PypiEndpoint::Official, PypiEndpoint::Tsinghua]
|
| 8 |
-
.par_iter()
|
| 9 |
-
.map(|endpoint| {
|
| 10 |
-
let start = Instant::now();
|
| 11 |
-
let url = format!("{endpoint}/pypi/sampleproject/json");
|
| 12 |
-
let resp = reqwest::blocking::get(&url);
|
| 13 |
-
match resp {
|
| 14 |
-
Ok(resp) if resp.status().is_success() => {
|
| 15 |
-
let duration = start.elapsed();
|
| 16 |
-
(duration, endpoint)
|
| 17 |
-
}
|
| 18 |
-
_ => (Duration::MAX, endpoint),
|
| 19 |
-
}
|
| 20 |
-
})
|
| 21 |
-
.min_by_key(|(duration, _)| *duration)
|
| 22 |
-
.map(|(_, endpoint)| endpoint)
|
| 23 |
-
.unwrap_or_else(|| &PypiEndpoint::Official)
|
| 24 |
-
});
|
| 25 |
-
|
| 26 |
-
#[derive(Debug, Clone, Display)]
|
| 27 |
-
pub enum PypiEndpoint {
|
| 28 |
-
#[strum(serialize = "https://pypi.org")]
|
| 29 |
-
Official,
|
| 30 |
-
#[strum(serialize = "https://mirrors.tuna.tsinghua.edu.cn")]
|
| 31 |
-
Tsinghua,
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
impl PypiEndpoint {
|
| 35 |
-
pub fn refine_url(&self, path: &str) -> String {
|
| 36 |
-
match self {
|
| 37 |
-
PypiEndpoint::Tsinghua => path.replace(
|
| 38 |
-
"https://files.pythonhosted.org",
|
| 39 |
-
format!("{self}/pypi/web").as_str(),
|
| 40 |
-
),
|
| 41 |
-
_ => path.to_string(),
|
| 42 |
-
}
|
| 43 |
-
}
|
| 44 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
koharu/src/app.rs
CHANGED
|
@@ -6,7 +6,7 @@ use std::{
|
|
| 6 |
},
|
| 7 |
};
|
| 8 |
|
| 9 |
-
use anyhow::Result;
|
| 10 |
use clap::Parser;
|
| 11 |
use koharu_ml::{DeviceName, cuda_is_available, device_name};
|
| 12 |
use koharu_runtime::{ensure_dylibs, preload_dylibs};
|
|
@@ -170,10 +170,12 @@ async fn prefetch() -> Result<()> {
|
|
| 170 |
Ok(())
|
| 171 |
}
|
| 172 |
|
| 173 |
-
async fn build_resources(
|
| 174 |
if cuda_is_available() {
|
| 175 |
-
ensure_dylibs(LIB_ROOT.to_path_buf())
|
| 176 |
-
|
|
|
|
|
|
|
| 177 |
|
| 178 |
#[cfg(target_os = "windows")]
|
| 179 |
{
|
|
@@ -181,7 +183,7 @@ async fn build_resources(use_cpu: bool, _register_file_assoc: bool) -> Result<Ap
|
|
| 181 |
tracing::warn!(?err, "Failed to register .khr file association");
|
| 182 |
}
|
| 183 |
|
| 184 |
-
crate::windows::add_dll_directory(&LIB_ROOT)?;
|
| 185 |
}
|
| 186 |
|
| 187 |
tracing::info!(
|
|
@@ -190,10 +192,14 @@ async fn build_resources(use_cpu: bool, _register_file_assoc: bool) -> Result<Ap
|
|
| 190 |
);
|
| 191 |
}
|
| 192 |
|
| 193 |
-
let ml_device = device_name(
|
| 194 |
-
let ml = Arc::new(
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
let state = Arc::new(RwLock::new(State::default()));
|
| 198 |
|
| 199 |
Ok(AppResources {
|
|
|
|
| 6 |
},
|
| 7 |
};
|
| 8 |
|
| 9 |
+
use anyhow::{Context, Result};
|
| 10 |
use clap::Parser;
|
| 11 |
use koharu_ml::{DeviceName, cuda_is_available, device_name};
|
| 12 |
use koharu_runtime::{ensure_dylibs, preload_dylibs};
|
|
|
|
| 170 |
Ok(())
|
| 171 |
}
|
| 172 |
|
| 173 |
+
async fn build_resources(cpu: bool, _register_file_assoc: bool) -> Result<AppResources> {
|
| 174 |
if cuda_is_available() {
|
| 175 |
+
ensure_dylibs(LIB_ROOT.to_path_buf())
|
| 176 |
+
.await
|
| 177 |
+
.context("Failed to ensure dynamic libraries")?;
|
| 178 |
+
preload_dylibs(LIB_ROOT.to_path_buf()).context("Failed to preload dynamic libraries")?;
|
| 179 |
|
| 180 |
#[cfg(target_os = "windows")]
|
| 181 |
{
|
|
|
|
| 183 |
tracing::warn!(?err, "Failed to register .khr file association");
|
| 184 |
}
|
| 185 |
|
| 186 |
+
crate::windows::add_dll_directory(&LIB_ROOT).context("Failed to add DLL directory")?;
|
| 187 |
}
|
| 188 |
|
| 189 |
tracing::info!(
|
|
|
|
| 192 |
);
|
| 193 |
}
|
| 194 |
|
| 195 |
+
let ml_device = device_name(cpu);
|
| 196 |
+
let ml = Arc::new(
|
| 197 |
+
ml::Model::new(cpu)
|
| 198 |
+
.await
|
| 199 |
+
.context("Failed to initialize ML model")?,
|
| 200 |
+
);
|
| 201 |
+
let llm = Arc::new(llm::Model::new(cpu));
|
| 202 |
+
let renderer = Arc::new(Renderer::new().context("Failed to initialize renderer")?);
|
| 203 |
let state = Arc::new(RwLock::new(State::default()));
|
| 204 |
|
| 205 |
Ok(AppResources {
|