- #70: implémentation suppression modèles locaux téléchargés - #100: correction scroll OpenCode - #102: correction fit TUI après switch/layout - memory note scoping UX
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
@ -10,6 +10,7 @@ use futures_util::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
|
||||
use domain::model_server::{
|
||||
ExecutablePath, HfModelRef, LlamaCppOptions, LocalModelRef, LocalModelServerConfig,
|
||||
@ -17,9 +18,9 @@ use domain::model_server::{
|
||||
};
|
||||
use domain::ports::{
|
||||
FileSystem, ManagedProcess, ManagedProcessHandle, ModelArtifactCancel, ModelArtifactDownloader,
|
||||
ModelArtifactProgress, ModelArtifactResolution, ModelServerArgv, ModelServerError,
|
||||
ModelServerProbe, ModelServerRegistry, ModelServerRuntime, ProcessStatus, RemotePath,
|
||||
SpawnSpec,
|
||||
ModelArtifactProgress, ModelArtifactResolution, ModelArtifactState, ModelServerArgv,
|
||||
ModelServerError, ModelServerProbe, ModelServerRegistry, ModelServerRuntime, ProcessStatus,
|
||||
RemotePath, SpawnSpec,
|
||||
};
|
||||
use domain::{LocalModelServerId, ProjectPath, StopPolicy};
|
||||
|
||||
@ -76,6 +77,7 @@ fn is_ready(result: Result<reqwest::Response, reqwest::Error>) -> bool {
|
||||
pub struct HfModelArtifactDownloader {
|
||||
cache_dir: PathBuf,
|
||||
client: reqwest::Client,
|
||||
repo_locks: Arc<Mutex<HashMap<String, Arc<AsyncMutex<()>>>>>,
|
||||
}
|
||||
|
||||
impl HfModelArtifactDownloader {
|
||||
@ -85,6 +87,7 @@ impl HfModelArtifactDownloader {
|
||||
Self {
|
||||
cache_dir: cache_dir.into(),
|
||||
client: reqwest::Client::new(),
|
||||
repo_locks: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,6 +152,62 @@ impl HfModelArtifactDownloader {
|
||||
std::fs::write(manifest_path, json)
|
||||
}
|
||||
|
||||
fn lock_for(&self, repo: &HfModelRef) -> Arc<AsyncMutex<()>> {
|
||||
let mut locks = self.repo_locks.lock().expect("repo locks mutex poisoned");
|
||||
Arc::clone(
|
||||
locks
|
||||
.entry(repo.as_str().to_owned())
|
||||
.or_insert_with(|| Arc::new(AsyncMutex::new(()))),
|
||||
)
|
||||
}
|
||||
|
||||
fn cached_state(&self, repo: &HfModelRef) -> Result<ModelArtifactState, ModelServerError> {
|
||||
let merged_path = self.cache_path_for(repo);
|
||||
if merged_path.is_file() {
|
||||
return Ok(ModelArtifactState::Downloaded {
|
||||
size_bytes: Some(file_size(&merged_path)?),
|
||||
path: model_path_from_pathbuf(merged_path)?,
|
||||
});
|
||||
}
|
||||
if let Some(paths) = self.cached_shard_set(repo) {
|
||||
if let Some(first) = paths.first() {
|
||||
return Ok(ModelArtifactState::Downloaded {
|
||||
size_bytes: Some(paths_size(&paths)?),
|
||||
path: model_path_from_pathbuf(first.clone())?,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(ModelArtifactState::Missing)
|
||||
}
|
||||
|
||||
fn delete_cached(&self, repo: &HfModelRef) -> Result<(), ModelServerError> {
|
||||
let merged_path = self.cache_path_for(repo);
|
||||
if merged_path.is_file() {
|
||||
remove_file_if_exists(&merged_path)?;
|
||||
}
|
||||
|
||||
let manifest_path = self.manifest_path_for(repo);
|
||||
if let Ok(raw) = std::fs::read(&manifest_path) {
|
||||
let manifest: ShardManifest =
|
||||
serde_json::from_slice(&raw).map_err(|e| ModelServerError::Store(e.to_string()))?;
|
||||
let dir = self.cache_dir_for(repo);
|
||||
for filename in manifest.files {
|
||||
remove_file_if_exists(&dir.join(filename))?;
|
||||
}
|
||||
remove_file_if_exists(&manifest_path)?;
|
||||
}
|
||||
|
||||
let repo_dir = self.cache_dir_for(repo);
|
||||
if repo_dir.is_dir()
|
||||
&& std::fs::read_dir(&repo_dir)
|
||||
.map(is_empty_dir)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
std::fs::remove_dir(&repo_dir).map_err(|e| ModelServerError::Store(e.to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve_remote_filenames(
|
||||
&self,
|
||||
repo: &HfModelRef,
|
||||
@ -189,12 +248,23 @@ struct ShardManifest {
|
||||
|
||||
#[async_trait]
|
||||
impl ModelArtifactDownloader for HfModelArtifactDownloader {
|
||||
async fn hf_model_state(
|
||||
&self,
|
||||
repo: &HfModelRef,
|
||||
) -> Result<ModelArtifactState, ModelServerError> {
|
||||
let lock = self.lock_for(repo);
|
||||
let _guard = lock.lock().await;
|
||||
self.cached_state(repo)
|
||||
}
|
||||
|
||||
async fn resolve_hf_model(
|
||||
&self,
|
||||
repo: &HfModelRef,
|
||||
progress: std::sync::Arc<dyn Fn(ModelArtifactProgress) + Send + Sync>,
|
||||
cancel: ModelArtifactCancel,
|
||||
) -> Result<ModelArtifactResolution, ModelServerError> {
|
||||
let lock = self.lock_for(repo);
|
||||
let _guard = lock.lock().await;
|
||||
if cancel.is_cancelled() {
|
||||
return Err(ModelServerError::Cancelled);
|
||||
}
|
||||
@ -300,6 +370,12 @@ impl ModelArtifactDownloader for HfModelArtifactDownloader {
|
||||
cache_hit: false,
|
||||
})
|
||||
}
|
||||
|
||||
async fn delete_hf_model(&self, repo: &HfModelRef) -> Result<(), ModelServerError> {
|
||||
let lock = self.lock_for(repo);
|
||||
let _guard = lock.lock().await;
|
||||
self.delete_cached(repo)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -409,6 +485,31 @@ fn model_path_from_pathbuf(path: PathBuf) -> Result<ModelPath, ModelServerError>
|
||||
.map_err(|e| ModelServerError::Invalid(e.to_string()))
|
||||
}
|
||||
|
||||
fn file_size(path: &Path) -> Result<u64, ModelServerError> {
|
||||
std::fs::metadata(path)
|
||||
.map(|metadata| metadata.len())
|
||||
.map_err(|e| ModelServerError::Store(e.to_string()))
|
||||
}
|
||||
|
||||
fn paths_size(paths: &[PathBuf]) -> Result<u64, ModelServerError> {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| file_size(path))
|
||||
.try_fold(0_u64, |acc, size| size.map(|size| acc.saturating_add(size)))
|
||||
}
|
||||
|
||||
fn remove_file_if_exists(path: &Path) -> Result<(), ModelServerError> {
|
||||
match std::fs::remove_file(path) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
|
||||
Err(err) => Err(ModelServerError::Store(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty_dir(entries: std::fs::ReadDir) -> bool {
|
||||
entries.into_iter().next().is_none()
|
||||
}
|
||||
|
||||
/// Builds `llama-server` argv without shell interpolation.
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct LlamaCppRuntime;
|
||||
|
||||
Reference in New Issue
Block a user