feat: migrate legacy local storage data to new app (#5156)
* feat: migrate legacy local storage data to new app * chore: refactor localstorage db read * chore: clean up * chore: migrate api key setting * chore: apply proxy configs * chore: fix key
This commit is contained in:
parent
646ba86de8
commit
573e667c34
@ -33,6 +33,7 @@ tauri-plugin-store = "2"
|
|||||||
hyper = { version = "0.14", features = ["server"] }
|
hyper = { version = "0.14", features = ["server"] }
|
||||||
reqwest = { version = "0.11", features = ["json", "blocking", "stream"] }
|
reqwest = { version = "0.11", features = ["json", "blocking", "stream"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
rocksdb = "0.21"
|
||||||
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", rev = "c1c4c9a0c9afbfbbf9eb42d6f8b00d8546fbdc2c", features = [
|
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", rev = "c1c4c9a0c9afbfbbf9eb42d6f8b00d8546fbdc2c", features = [
|
||||||
"client",
|
"client",
|
||||||
"transport-sse-client",
|
"transport-sse-client",
|
||||||
|
|||||||
@ -395,39 +395,3 @@ pub async fn read_logs(app: AppHandle) -> Result<String, String> {
|
|||||||
Err(format!("Log file not found"))
|
Err(format!("Log file not found"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn handle_app_update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> {
|
|
||||||
if let Some(update) = app.updater()?.check().await? {
|
|
||||||
let mut downloaded = 0;
|
|
||||||
|
|
||||||
// alternatively we could also call update.download() and update.install() separately
|
|
||||||
log::info!(
|
|
||||||
"Has update {} {} {}",
|
|
||||||
update.version,
|
|
||||||
update.current_version,
|
|
||||||
update.download_url
|
|
||||||
);
|
|
||||||
update
|
|
||||||
.download_and_install(
|
|
||||||
|chunk_length, content_length| {
|
|
||||||
downloaded += chunk_length;
|
|
||||||
log::info!("downloaded {downloaded} from {content_length:?}");
|
|
||||||
},
|
|
||||||
|| {
|
|
||||||
log::info!("download finished");
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
log::info!("update installed");
|
|
||||||
let client = Client::new();
|
|
||||||
let url = "http://127.0.0.1:39291/processManager/destroy";
|
|
||||||
let _ = client.delete(url).send();
|
|
||||||
app.restart();
|
|
||||||
} else {
|
|
||||||
log::info!("Cannot parse response or update is not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
122
src-tauri/src/core/migration.rs
Normal file
122
src-tauri/src/core/migration.rs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
use rocksdb::{IteratorMode, DB};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_legacy_browser_data(app: tauri::AppHandle) -> Result<HashMap<String, String>, String> {
|
||||||
|
let mut path = app.path().data_dir().unwrap();
|
||||||
|
|
||||||
|
let app_name =
|
||||||
|
std::env::var("APP_NAME").unwrap_or_else(|_| app.config().product_name.clone().unwrap());
|
||||||
|
path.push(app_name);
|
||||||
|
path.push("Local Storage");
|
||||||
|
path.push("leveldb");
|
||||||
|
// Check if the path exists
|
||||||
|
if !path.exists() {
|
||||||
|
log::info!("Path {:?} does not exist, skipping migration.", path);
|
||||||
|
return Ok(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = DB::open_default(path);
|
||||||
|
match db {
|
||||||
|
Ok(db) => {
|
||||||
|
let iter = db.iterator(IteratorMode::Start);
|
||||||
|
|
||||||
|
let migration_kvs: HashMap<String, String> = HashMap::from([
|
||||||
|
// Api Server
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}apiServerHost".to_string(),
|
||||||
|
"serverHost".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}apiServerPort".to_string(),
|
||||||
|
"serverPort".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}apiServerCorsEnabled".to_string(),
|
||||||
|
"corsEnabled".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}apiServerPrefix".to_string(),
|
||||||
|
"apiPrefix".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}apiServerVerboseLogEnabled".to_string(),
|
||||||
|
"verboseLogs".to_string(),
|
||||||
|
),
|
||||||
|
// Proxy
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}proxyFeatureEnabled".to_string(),
|
||||||
|
"proxyEnabled".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}httpsProxyFeature".to_string(),
|
||||||
|
"proxyUrl".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}proxyPassword".to_string(),
|
||||||
|
"proxyPassword".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}proxyUsername".to_string(),
|
||||||
|
"proxyUsername".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}ignoreSSLFeature".to_string(),
|
||||||
|
"proxyIgnoreSSL".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}verifyProxySSL".to_string(),
|
||||||
|
"verifyProxySSL".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}verifyProxyHostSSL".to_string(),
|
||||||
|
"verifyProxyHostSSL".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}verifyPeerSSL".to_string(),
|
||||||
|
"verifyPeerSSL".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}verifyHostSSL".to_string(),
|
||||||
|
"verifyHostSSL".to_string(),
|
||||||
|
),
|
||||||
|
("_file://\0\u{1}noProxy".to_string(), "noProxy".to_string()),
|
||||||
|
// Analytics
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}productAnalytic".to_string(),
|
||||||
|
"productAnalytic".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"_file://\0\u{1}productAnalyticPrompt".to_string(),
|
||||||
|
"productAnalyticPrompt".to_string(),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mut results = HashMap::new();
|
||||||
|
|
||||||
|
for item in iter {
|
||||||
|
match item {
|
||||||
|
Ok((key, value)) => {
|
||||||
|
let key_str = String::from_utf8_lossy(&key).to_string();
|
||||||
|
let value_str = String::from_utf8_lossy(&value).to_string();
|
||||||
|
// log::info!("Key: {:?} | Value: {:?}", key_str, value_str);
|
||||||
|
if let Some(new_key) = migration_kvs.get(&key_str) {
|
||||||
|
log::info!("Migrating key {:?} to new key {:?}", key_str, new_key);
|
||||||
|
|
||||||
|
results.insert(new_key.to_string(), value_str.replace("\u{1}", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error reading from DB: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::info!("Migration results: {:?}", results);
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Ok(HashMap::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
pub mod hardware;
|
||||||
pub mod mcp;
|
pub mod mcp;
|
||||||
|
pub mod migration;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod threads;
|
pub mod threads;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod hardware;
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ use tauri_plugin_shell::ShellExt;
|
|||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_store::StoreExt;
|
||||||
use tokio::sync::Mutex; // Using tokio::sync::Mutex
|
use tokio::sync::Mutex; // Using tokio::sync::Mutex
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
// MCP
|
// MCP
|
||||||
use super::{
|
use super::{
|
||||||
cmd::{get_jan_data_folder_path, get_jan_extensions_path},
|
cmd::{get_jan_data_folder_path, get_jan_extensions_path},
|
||||||
|
|||||||
@ -48,9 +48,9 @@ pub fn run() {
|
|||||||
core::cmd::start_server,
|
core::cmd::start_server,
|
||||||
core::cmd::stop_server,
|
core::cmd::stop_server,
|
||||||
core::cmd::read_logs,
|
core::cmd::read_logs,
|
||||||
core::cmd::handle_app_update,
|
|
||||||
core::cmd::change_app_data_folder,
|
core::cmd::change_app_data_folder,
|
||||||
core::cmd::reset_cortex_restart_count,
|
core::cmd::reset_cortex_restart_count,
|
||||||
|
core::migration::get_legacy_browser_data,
|
||||||
// MCP commands
|
// MCP commands
|
||||||
core::mcp::get_tools,
|
core::mcp::get_tools,
|
||||||
core::mcp::call_tool,
|
core::mcp::call_tool,
|
||||||
@ -106,11 +106,6 @@ pub fn run() {
|
|||||||
setup_mcp(app);
|
setup_mcp(app);
|
||||||
setup_sidecar(app).expect("Failed to setup sidecar");
|
setup_sidecar(app).expect("Failed to setup sidecar");
|
||||||
setup_engine_binaries(app).expect("Failed to setup engine binaries");
|
setup_engine_binaries(app).expect("Failed to setup engine binaries");
|
||||||
// TODO(any) need to wire up with frontend
|
|
||||||
// let handle = app.handle().clone();
|
|
||||||
// tauri::async_runtime::spawn(async move {
|
|
||||||
// handle_app_update(handle).await.unwrap();
|
|
||||||
// });
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.on_window_event(|window, event| match event {
|
.on_window_event(|window, event| match event {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const localStorageKey = {
|
|||||||
settingCodeBlock: 'setting-code-block',
|
settingCodeBlock: 'setting-code-block',
|
||||||
settingMCPSevers: 'setting-mcp-servers',
|
settingMCPSevers: 'setting-mcp-servers',
|
||||||
settingLocalApiServer: 'setting-local-api-server',
|
settingLocalApiServer: 'setting-local-api-server',
|
||||||
|
settingProxyConfig: 'setting-proxy-config',
|
||||||
settingHardware: 'setting-hardware',
|
settingHardware: 'setting-hardware',
|
||||||
productAnalyticPrompt: 'productAnalyticPrompt',
|
productAnalyticPrompt: 'productAnalyticPrompt',
|
||||||
productAnalytic: 'productAnalytic',
|
productAnalytic: 'productAnalytic',
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const useProxyConfig = create<ProxyConfigState>()(
|
|||||||
setNoProxy: (noProxy) => set({ noProxy }),
|
setNoProxy: (noProxy) => set({ noProxy }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: localStorageKey.settingLocalApiServer,
|
name: localStorageKey.settingProxyConfig,
|
||||||
storage: createJSONStorage(() => localStorage),
|
storage: createJSONStorage(() => localStorage),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { useMCPServers } from '@/hooks/useMCPServers'
|
|||||||
import { getMCPConfig } from '@/services/mcp'
|
import { getMCPConfig } from '@/services/mcp'
|
||||||
import { useAssistant } from '@/hooks/useAssistant'
|
import { useAssistant } from '@/hooks/useAssistant'
|
||||||
import { getAssistants } from '@/services/assistants'
|
import { getAssistants } from '@/services/assistants'
|
||||||
|
import { migrateData } from '@/utils/migration'
|
||||||
|
|
||||||
export function DataProvider() {
|
export function DataProvider() {
|
||||||
const { setProviders } = useModelProvider()
|
const { setProviders } = useModelProvider()
|
||||||
@ -30,6 +31,7 @@ export function DataProvider() {
|
|||||||
getAssistants().then((data) =>
|
getAssistants().then((data) =>
|
||||||
setAssistants((data as unknown as Assistant[]) ?? [])
|
setAssistants((data as unknown as Assistant[]) ?? [])
|
||||||
)
|
)
|
||||||
|
migrateData()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
78
web-app/src/utils/migration.ts
Normal file
78
web-app/src/utils/migration.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { useProductAnalytic } from '@/hooks/useAnalytic'
|
||||||
|
import { useLocalApiServer } from '@/hooks/useLocalApiServer'
|
||||||
|
import { useModelProvider } from '@/hooks/useModelProvider'
|
||||||
|
import { useProxyConfig } from '@/hooks/useProxyConfig'
|
||||||
|
import { ExtensionManager } from '@/lib/extension'
|
||||||
|
import { configurePullOptions } from '@/services/models'
|
||||||
|
import { EngineManagementExtension, ExtensionTypeEnum } from '@janhq/core'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates legacy browser data to new browser session.
|
||||||
|
*/
|
||||||
|
export const migrateData = async () => {
|
||||||
|
if (!localStorage.getItem('migration_completed')) {
|
||||||
|
try {
|
||||||
|
// Migrate local storage data
|
||||||
|
const oldData = await invoke('get_legacy_browser_data')
|
||||||
|
for (const [key, value] of Object.entries(
|
||||||
|
oldData as unknown as Record<string, string>
|
||||||
|
)) {
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
if (Object.keys(useLocalApiServer.getState()).includes(key)) {
|
||||||
|
useLocalApiServer.setState({
|
||||||
|
...useLocalApiServer.getState(),
|
||||||
|
[key]: value.replace(/"/g, ''),
|
||||||
|
})
|
||||||
|
} else if (Object.keys(useProxyConfig.getState()).includes(key)) {
|
||||||
|
useProxyConfig.setState({
|
||||||
|
...useProxyConfig.getState(),
|
||||||
|
[key]: value.replace(/"/g, ''),
|
||||||
|
})
|
||||||
|
} else if (Object.keys(useProductAnalytic.getState()).includes(key)) {
|
||||||
|
useProductAnalytic.setState({
|
||||||
|
...useProductAnalytic.getState(),
|
||||||
|
[key]: value.replace(/"/g, ''),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Migrate provider configurations
|
||||||
|
const engines = await ExtensionManager.getInstance()
|
||||||
|
.get<EngineManagementExtension>(ExtensionTypeEnum.Engine)
|
||||||
|
?.getEngines()
|
||||||
|
if (engines) {
|
||||||
|
for (const [key, value] of Object.entries(engines)) {
|
||||||
|
const providerName = key.replace('google_gemini', 'gemini')
|
||||||
|
const engine = value[0] as
|
||||||
|
| {
|
||||||
|
api_key?: string
|
||||||
|
url?: string
|
||||||
|
engine?: string
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
if (engine && 'api_key' in engine) {
|
||||||
|
const provider = useModelProvider
|
||||||
|
.getState()
|
||||||
|
.getProviderByName(providerName)
|
||||||
|
const settings = provider?.settings.map((e) => {
|
||||||
|
if (e.key === 'api-key')
|
||||||
|
e.controller_props.value = (engine.api_key as string) ?? ''
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
if (provider) {
|
||||||
|
useModelProvider.getState().updateProvider(providerName, {
|
||||||
|
...provider,
|
||||||
|
settings: settings ?? [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStorage.setItem('migration_completed', 'true')
|
||||||
|
configurePullOptions(useProxyConfig.getState())
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Migration failed:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user