fix: relocate jan data folder (#5179)
* fix: relocate jan data folder failed * fix: avoid infinite recursion * chore: kill background processes to unblock factory reset * chore: stop models before reset factory * chore: clean up * chore: clean up * fix: show error * chore: get active models should not have retry
This commit is contained in:
parent
135e75b812
commit
7dc51c5e0f
@ -270,7 +270,13 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
|
||||
|
||||
async activeModels(): Promise<(object & { id: string })[]> {
|
||||
return await this.apiInstance()
|
||||
.then((e) => e.get('inferences/server/models'))
|
||||
.then((e) =>
|
||||
e.get('inferences/server/models', {
|
||||
retry: {
|
||||
limit: 0, // Do not retry
|
||||
},
|
||||
})
|
||||
)
|
||||
.then((e) => e.json())
|
||||
.then((e) => (e as LoadedModelResponse).data ?? [])
|
||||
.catch(() => [])
|
||||
|
||||
@ -316,6 +316,12 @@ pub fn change_app_data_folder(
|
||||
new_data_folder_path
|
||||
);
|
||||
|
||||
// Check if this is a parent directory to avoid infinite recursion
|
||||
if new_data_folder_path.starts_with(¤t_data_folder) {
|
||||
return Err(
|
||||
"New data folder cannot be a subdirectory of the current data folder".to_string(),
|
||||
);
|
||||
}
|
||||
copy_dir_recursive(¤t_data_folder, &new_data_folder_path)
|
||||
.map_err(|e| format!("Failed to copy data to new folder: {}", e))?;
|
||||
} else {
|
||||
|
||||
@ -212,16 +212,17 @@ pub fn setup_mcp(app: &App) {
|
||||
|
||||
pub fn setup_sidecar(app: &App) -> Result<(), String> {
|
||||
let app_handle = app.handle().clone();
|
||||
let app_handle_for_spawn = app_handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
const MAX_RESTARTS: u32 = 5;
|
||||
const RESTART_DELAY_MS: u64 = 5000;
|
||||
|
||||
let app_state = app_handle.state::<AppState>();
|
||||
let app_state = app_handle_for_spawn.state::<AppState>();
|
||||
let cortex_restart_count_state = app_state.cortex_restart_count.clone();
|
||||
let app_data_dir = get_jan_data_folder_path(app_handle.clone());
|
||||
let app_data_dir = get_jan_data_folder_path(app_handle_for_spawn.clone());
|
||||
|
||||
let sidecar_command_builder = || {
|
||||
let mut cmd = app_handle
|
||||
let mut cmd = app_handle_for_spawn
|
||||
.shell()
|
||||
.sidecar("cortex-server")
|
||||
.expect("Failed to get sidecar command")
|
||||
@ -243,14 +244,17 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> {
|
||||
]);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
cmd = cmd.current_dir(app_handle.path().resource_dir().unwrap());
|
||||
cmd = cmd.current_dir(app_handle_for_spawn.path().resource_dir().unwrap());
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
cmd = cmd.env("LD_LIBRARY_PATH", {
|
||||
let current_app_data_dir =
|
||||
app_handle.path().resource_dir().unwrap().join("binaries");
|
||||
let current_app_data_dir = app_handle_for_spawn
|
||||
.path()
|
||||
.resource_dir()
|
||||
.unwrap()
|
||||
.join("binaries");
|
||||
let dest = current_app_data_dir.to_str().unwrap();
|
||||
let ld_path_env = std::env::var("LD_LIBRARY_PATH").unwrap_or_default();
|
||||
format!("{}{}{}", ld_path_env, ":", dest)
|
||||
@ -262,9 +266,15 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> {
|
||||
let child_process: Arc<Mutex<Option<CommandChild>>> = Arc::new(Mutex::new(None));
|
||||
|
||||
let child_process_clone_for_kill = child_process.clone();
|
||||
let app_handle_for_kill = app_handle.clone();
|
||||
app_handle.listen("kill-sidecar", move |_event| {
|
||||
let app_handle = app_handle_for_kill.clone();
|
||||
let child_to_kill_arc = child_process_clone_for_kill.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let app_state = app_handle.state::<AppState>();
|
||||
let mut count = app_state.cortex_restart_count.lock().await;
|
||||
*count = 5;
|
||||
drop(count);
|
||||
log::info!("Received kill-sidecar event (processing async).");
|
||||
if let Some(child) = child_to_kill_arc.lock().await.take() {
|
||||
log::info!("Attempting to kill sidecar process...");
|
||||
@ -286,7 +296,7 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> {
|
||||
"Cortex server reached maximum restart attempts ({}). Giving up.",
|
||||
current_restart_count
|
||||
);
|
||||
if let Err(e) = app_handle.emit("cortex_max_restarts_reached", ()) {
|
||||
if let Err(e) = app_handle_for_spawn.emit("cortex_max_restarts_reached", ()) {
|
||||
log::error!("Failed to emit cortex_max_restarts_reached event: {}", e);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -42,6 +42,8 @@ import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { windowKey } from '@/constants/windows'
|
||||
import { toast } from 'sonner'
|
||||
import { isDev } from '@/lib/utils'
|
||||
import { emit } from '@tauri-apps/api/event'
|
||||
import { stopAllModels } from '@/services/models'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const Route = createFileRoute(route.settings.general as any)({
|
||||
@ -146,12 +148,24 @@ function General() {
|
||||
const confirmDataFolderChange = async () => {
|
||||
if (selectedNewPath) {
|
||||
try {
|
||||
setJanDataFolder(selectedNewPath)
|
||||
await stopAllModels()
|
||||
emit('kill-sidecar')
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await relocateJanDataFolder(selectedNewPath)
|
||||
setJanDataFolder(selectedNewPath)
|
||||
// Only relaunch if relocation was successful
|
||||
window.core?.api?.relaunch()
|
||||
setSelectedNewPath(null)
|
||||
setIsDialogOpen(false)
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to relocate Jan data folder'
|
||||
)
|
||||
}
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.error('Failed to relocate data folder:', error)
|
||||
// Revert the data folder path on error
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { AppConfiguration, fs } from '@janhq/core'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { emit } from '@tauri-apps/api/event'
|
||||
import { stopAllModels } from './models'
|
||||
|
||||
/**
|
||||
* @description This function is used to reset the app to its factory settings.
|
||||
@ -7,11 +9,16 @@ import { invoke } from '@tauri-apps/api/core'
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const factoryReset = async () => {
|
||||
// Kill background processes and remove data folder
|
||||
await stopAllModels()
|
||||
emit('kill-sidecar')
|
||||
setTimeout(async () => {
|
||||
const janDataFolderPath = await getJanDataFolder()
|
||||
if (janDataFolderPath) await fs.rm(janDataFolderPath)
|
||||
window.localStorage.clear()
|
||||
await window.core?.api?.installExtensions()
|
||||
await window.core?.api?.relaunch()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,5 +78,5 @@ export const getJanDataFolder = async (): Promise<string | undefined> => {
|
||||
* @param path The new path for the Jan data folder
|
||||
*/
|
||||
export const relocateJanDataFolder = async (path: string) => {
|
||||
window.core?.api?.changeAppDataFolder({ newDataFolder: path })
|
||||
await window.core?.api?.changeAppDataFolder({ newDataFolder: path })
|
||||
}
|
||||
|
||||
@ -260,6 +260,18 @@ export const stopModel = async (model: string, provider?: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all active models.
|
||||
* @returns
|
||||
*/
|
||||
export const stopAllModels = async () => {
|
||||
const models = await getActiveModels()
|
||||
if (models)
|
||||
await Promise.all(
|
||||
models.map((model: { id: string }) => stopModel(model.id))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @fileoverview Helper function to start a model.
|
||||
* This function loads the model from the provider.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user