Skip to content

Commit 4b8b807

Browse files
authored
Merge pull request #63 from xLieferant/VTC-BIG-Change
Embed DB migrations & datasets; migrate legacy DB
2 parents 1b74d86 + c208a42 commit 4b8b807

9 files changed

Lines changed: 213 additions & 75 deletions

File tree

ets2-tool/data/app.sqlite

0 Bytes
Binary file not shown.

ets2-tool/data/app.sqlite-shm

0 Bytes
Binary file not shown.

ets2-tool/data/app.sqlite-wal

-48.3 KB
Binary file not shown.

ets2-tool/src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ets2-tool/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ets2-tool"
3-
version = "0.7.0"
3+
version = "0.7.1"
44
description = "Save-Edit Tool for ETS2"
55
authors = ["xLieferant"]
66
edition = "2024"

ets2-tool/src-tauri/src/db/sqlite.rs

Lines changed: 123 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,61 @@ use rusqlite::{Connection as RusqliteConnection, OptionalExtension};
66
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
77
use sqlx::{Row, SqlitePool};
88

9-
const MIGRATION_FILES: [&str; 10] = [
10-
"2026-04-06_create_ets_profiles.sql",
11-
"2026-04-06_create_ets_saves.sql",
12-
"2026-04-06_create_ets_job_links.sql",
13-
"2026-04-06_create_ets_job_link_audit.sql",
14-
"2026-04-06_create_vtc_job_ledger.sql",
15-
"2026-04-06_create_ets2_datasets.sql",
16-
"2026-04-06_create_ets_save_snapshot.sql",
17-
"2026-04-06_add_resolved_tokens_to_ets_job_links.sql",
18-
"2026-04-06_add_cargo_resolution_to_ets_job_links.sql",
19-
"2026-04-07_add_vtc_local_persistence.sql",
9+
const APP_RUNTIME_DIR_NAME: &str = "SimNexusHub";
10+
const RUNTIME_MIGRATIONS: [(&str, &str); 10] = [
11+
(
12+
"2026-04-06_create_ets_profiles.sql",
13+
include_str!("migrations/2026-04-06_create_ets_profiles.sql"),
14+
),
15+
(
16+
"2026-04-06_create_ets_saves.sql",
17+
include_str!("migrations/2026-04-06_create_ets_saves.sql"),
18+
),
19+
(
20+
"2026-04-06_create_ets_job_links.sql",
21+
include_str!("migrations/2026-04-06_create_ets_job_links.sql"),
22+
),
23+
(
24+
"2026-04-06_create_ets_job_link_audit.sql",
25+
include_str!("migrations/2026-04-06_create_ets_job_link_audit.sql"),
26+
),
27+
(
28+
"2026-04-06_create_vtc_job_ledger.sql",
29+
include_str!("migrations/2026-04-06_create_vtc_job_ledger.sql"),
30+
),
31+
(
32+
"2026-04-06_create_ets2_datasets.sql",
33+
include_str!("migrations/2026-04-06_create_ets2_datasets.sql"),
34+
),
35+
(
36+
"2026-04-06_create_ets_save_snapshot.sql",
37+
include_str!("migrations/2026-04-06_create_ets_save_snapshot.sql"),
38+
),
39+
(
40+
"2026-04-06_add_resolved_tokens_to_ets_job_links.sql",
41+
include_str!("migrations/2026-04-06_add_resolved_tokens_to_ets_job_links.sql"),
42+
),
43+
(
44+
"2026-04-06_add_cargo_resolution_to_ets_job_links.sql",
45+
include_str!("migrations/2026-04-06_add_cargo_resolution_to_ets_job_links.sql"),
46+
),
47+
(
48+
"2026-04-07_add_vtc_local_persistence.sql",
49+
include_str!("migrations/2026-04-07_add_vtc_local_persistence.sql"),
50+
),
2051
];
2152

2253
pub fn app_db_path() -> PathBuf {
54+
app_runtime_dir().join("app.sqlite")
55+
}
56+
57+
fn app_runtime_dir() -> PathBuf {
58+
dirs::data_local_dir()
59+
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
60+
.join(APP_RUNTIME_DIR_NAME)
61+
}
62+
63+
fn legacy_repo_db_path() -> PathBuf {
2364
Path::new(env!("CARGO_MANIFEST_DIR"))
2465
.parent()
2566
.unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")))
@@ -29,10 +70,20 @@ pub fn app_db_path() -> PathBuf {
2970

3071
pub async fn init_sqlite() -> Result<SqlitePool, String> {
3172
let db_path = app_db_path();
73+
let legacy_db_path = legacy_repo_db_path();
74+
75+
crate::dev_log!("[db] Resolved runtime DB path: {}", db_path.display());
76+
crate::dev_log!(
77+
"[db] Legacy repo DB candidate: {} (exists={})",
78+
legacy_db_path.display(),
79+
legacy_db_path.exists()
80+
);
81+
3282
validate_sqlite_extension(&db_path)?;
3383
if let Some(parent) = db_path.parent() {
3484
std::fs::create_dir_all(parent).map_err(|error| error.to_string())?;
3585
}
86+
migrate_legacy_db_if_needed(&db_path, &legacy_db_path)?;
3687
run_runtime_migrations(&db_path)?;
3788

3889
let options = SqliteConnectOptions::new()
@@ -81,6 +132,63 @@ pub fn validate_sqlite_extension(path: &Path) -> Result<(), String> {
81132
Ok(())
82133
}
83134

135+
fn migrate_legacy_db_if_needed(db_path: &Path, legacy_db_path: &Path) -> Result<(), String> {
136+
if db_path.exists() {
137+
crate::dev_log!("[db] Runtime DB already exists: {}", db_path.display());
138+
return Ok(());
139+
}
140+
141+
if !legacy_db_path.exists() {
142+
crate::dev_log!(
143+
"[db] No legacy DB to migrate from: {}",
144+
legacy_db_path.display()
145+
);
146+
return Ok(());
147+
}
148+
149+
if let Some(parent) = db_path.parent() {
150+
std::fs::create_dir_all(parent).map_err(|error| error.to_string())?;
151+
}
152+
153+
std::fs::copy(legacy_db_path, db_path).map_err(|error| {
154+
format!(
155+
"copy legacy db {} -> {} failed: {}",
156+
legacy_db_path.display(),
157+
db_path.display(),
158+
error
159+
)
160+
})?;
161+
crate::dev_log!(
162+
"[db] Migrated legacy DB: {} -> {}",
163+
legacy_db_path.display(),
164+
db_path.display()
165+
);
166+
167+
for suffix in ["-wal", "-shm"] {
168+
let legacy_sidecar = PathBuf::from(format!("{}{}", legacy_db_path.display(), suffix));
169+
if !legacy_sidecar.exists() {
170+
continue;
171+
}
172+
173+
let target_sidecar = PathBuf::from(format!("{}{}", db_path.display(), suffix));
174+
std::fs::copy(&legacy_sidecar, &target_sidecar).map_err(|error| {
175+
format!(
176+
"copy legacy sqlite sidecar {} -> {} failed: {}",
177+
legacy_sidecar.display(),
178+
target_sidecar.display(),
179+
error
180+
)
181+
})?;
182+
crate::dev_log!(
183+
"[db] Migrated SQLite sidecar: {} -> {}",
184+
legacy_sidecar.display(),
185+
target_sidecar.display()
186+
);
187+
}
188+
189+
Ok(())
190+
}
191+
84192
#[derive(Debug, Clone, serde::Serialize)]
85193
#[serde(rename_all = "camelCase")]
86194
pub struct SqliteInfoDto {
@@ -191,14 +299,8 @@ async fn count_rows(pool: &SqlitePool, table: &str) -> Result<i64, String> {
191299
.map_err(|error| error.to_string())
192300
}
193301

194-
fn migration_directory() -> PathBuf {
195-
Path::new(env!("CARGO_MANIFEST_DIR"))
196-
.join("src")
197-
.join("db")
198-
.join("migrations")
199-
}
200-
201302
fn run_runtime_migrations(db_path: &Path) -> Result<(), String> {
303+
crate::dev_log!("[db] Running runtime migrations for {}", db_path.display());
202304
let mut connection = RusqliteConnection::open(db_path).map_err(|error| error.to_string())?;
203305
connection
204306
.busy_timeout(std::time::Duration::from_secs(5))
@@ -218,9 +320,8 @@ fn run_runtime_migrations(db_path: &Path) -> Result<(), String> {
218320
let tx = connection
219321
.transaction()
220322
.map_err(|error| error.to_string())?;
221-
let migration_dir = migration_directory();
222323

223-
for filename in MIGRATION_FILES {
324+
for (filename, sql) in RUNTIME_MIGRATIONS {
224325
let already_applied: Option<String> = tx
225326
.query_row(
226327
"SELECT filename FROM ets_feature_migrations WHERE filename = ?1",
@@ -233,25 +334,19 @@ fn run_runtime_migrations(db_path: &Path) -> Result<(), String> {
233334
continue;
234335
}
235336

236-
let migration_path = migration_dir.join(filename);
237-
let sql = std::fs::read_to_string(&migration_path).map_err(|error| {
238-
format!(
239-
"read migration {} failed: {}",
240-
migration_path.display(),
241-
error
242-
)
243-
})?;
244337
tx.execute_batch(&sql)
245338
.map_err(|error| format!("apply migration {} failed: {}", filename, error))?;
246339
tx.execute(
247340
"INSERT INTO ets_feature_migrations (filename, applied_at_utc) VALUES (?1, ?2)",
248341
rusqlite::params![filename, Utc::now().to_rfc3339()],
249342
)
250343
.map_err(|error| format!("record migration {} failed: {}", filename, error))?;
344+
crate::dev_log!("[db] Applied runtime migration: {}", filename);
251345
}
252346

253347
tx.commit().map_err(|error| error.to_string())?;
254348
ensure_runtime_columns(&connection)?;
349+
crate::dev_log!("[db] Runtime migrations complete");
255350
Ok(())
256351
}
257352

ets2-tool/src-tauri/src/features/ets2save/link_service.rs

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::BTreeSet;
22
use std::fs;
3-
use std::path::{Path, PathBuf};
3+
use std::path::Path;
44

55
use chrono::Utc;
66
use rusqlite::{Connection as RusqliteConnection, OptionalExtension};
@@ -32,17 +32,47 @@ use crate::shared::models::save_context::build_save_session_id;
3232
use crate::shared::sqlite_schema::ensure_columns;
3333
use crate::state::AppProfileState;
3434

35-
const MIGRATION_FILES: [&str; 10] = [
36-
"2026-04-06_create_ets_profiles.sql",
37-
"2026-04-06_create_ets_saves.sql",
38-
"2026-04-06_create_ets_job_links.sql",
39-
"2026-04-06_create_ets_job_link_audit.sql",
40-
"2026-04-06_create_vtc_job_ledger.sql",
41-
"2026-04-06_create_ets2_datasets.sql",
42-
"2026-04-06_create_ets_save_snapshot.sql",
43-
"2026-04-06_add_resolved_tokens_to_ets_job_links.sql",
44-
"2026-04-06_add_cargo_resolution_to_ets_job_links.sql",
45-
"2026-04-07_add_vtc_local_persistence.sql",
35+
const RUNTIME_MIGRATIONS: [(&str, &str); 10] = [
36+
(
37+
"2026-04-06_create_ets_profiles.sql",
38+
include_str!("../../db/migrations/2026-04-06_create_ets_profiles.sql"),
39+
),
40+
(
41+
"2026-04-06_create_ets_saves.sql",
42+
include_str!("../../db/migrations/2026-04-06_create_ets_saves.sql"),
43+
),
44+
(
45+
"2026-04-06_create_ets_job_links.sql",
46+
include_str!("../../db/migrations/2026-04-06_create_ets_job_links.sql"),
47+
),
48+
(
49+
"2026-04-06_create_ets_job_link_audit.sql",
50+
include_str!("../../db/migrations/2026-04-06_create_ets_job_link_audit.sql"),
51+
),
52+
(
53+
"2026-04-06_create_vtc_job_ledger.sql",
54+
include_str!("../../db/migrations/2026-04-06_create_vtc_job_ledger.sql"),
55+
),
56+
(
57+
"2026-04-06_create_ets2_datasets.sql",
58+
include_str!("../../db/migrations/2026-04-06_create_ets2_datasets.sql"),
59+
),
60+
(
61+
"2026-04-06_create_ets_save_snapshot.sql",
62+
include_str!("../../db/migrations/2026-04-06_create_ets_save_snapshot.sql"),
63+
),
64+
(
65+
"2026-04-06_add_resolved_tokens_to_ets_job_links.sql",
66+
include_str!("../../db/migrations/2026-04-06_add_resolved_tokens_to_ets_job_links.sql"),
67+
),
68+
(
69+
"2026-04-06_add_cargo_resolution_to_ets_job_links.sql",
70+
include_str!("../../db/migrations/2026-04-06_add_cargo_resolution_to_ets_job_links.sql"),
71+
),
72+
(
73+
"2026-04-07_add_vtc_local_persistence.sql",
74+
include_str!("../../db/migrations/2026-04-07_add_vtc_local_persistence.sql"),
75+
),
4676
];
4777

4878
pub async fn create_pool(db_path: &std::path::Path) -> Result<SqlitePool, AppError> {
@@ -59,13 +89,6 @@ pub async fn create_pool(db_path: &std::path::Path) -> Result<SqlitePool, AppErr
5989
Ok(pool)
6090
}
6191

62-
fn migration_directory() -> PathBuf {
63-
Path::new(env!("CARGO_MANIFEST_DIR"))
64-
.join("src")
65-
.join("db")
66-
.join("migrations")
67-
}
68-
6992
fn run_runtime_migrations(db_path: &Path) -> Result<(), AppError> {
7093
let mut connection = RusqliteConnection::open(db_path).map_err(|error| {
7194
AppError::new(
@@ -104,9 +127,8 @@ fn run_runtime_migrations(db_path: &Path) -> Result<(), AppError> {
104127
format!("begin migration transaction failed: {}", error),
105128
)
106129
})?;
107-
let migration_dir = migration_directory();
108130

109-
for filename in MIGRATION_FILES {
131+
for (filename, sql) in RUNTIME_MIGRATIONS {
110132
let already_applied: Option<String> = tx
111133
.query_row(
112134
"SELECT filename FROM ets_feature_migrations WHERE filename = ?1",
@@ -124,17 +146,6 @@ fn run_runtime_migrations(db_path: &Path) -> Result<(), AppError> {
124146
continue;
125147
}
126148

127-
let migration_path = migration_dir.join(filename);
128-
let sql = fs::read_to_string(&migration_path).map_err(|error| {
129-
AppError::new(
130-
AppErrorCode::WriteFailed,
131-
format!(
132-
"read migration {} failed: {}",
133-
migration_path.display(),
134-
error
135-
),
136-
)
137-
})?;
138149
tx.execute_batch(&sql).map_err(|error| {
139150
AppError::new(
140151
AppErrorCode::WriteFailed,

0 commit comments

Comments
 (0)