client_core/store/
migration.rs1use super::{Store, StoreError};
2use std::path::{Path, PathBuf};
3
4pub fn legacy_desktop_db_path() -> Option<PathBuf> {
7 let home = dirs::home_dir()?;
8 #[cfg(target_os = "macos")]
9 return Some(home.join("Library/Application Support/com.cinchcli.desktop/cinch.db"));
10 #[cfg(target_os = "linux")]
11 return Some(home.join(".local/share/com.cinchcli.desktop/cinch.db"));
12 #[cfg(target_os = "windows")]
13 {
14 if let Some(appdata) = std::env::var_os("APPDATA") {
15 return Some(
16 PathBuf::from(appdata)
17 .join("com.cinchcli.desktop")
18 .join("cinch.db"),
19 );
20 }
21 return None;
22 }
23 #[allow(unreachable_code)]
24 None
25}
26
27pub fn import_legacy_if_present(
33 store: &Store,
34 new_media_root: &Path,
35 legacy_path: Option<&Path>,
36) -> Result<Option<PathBuf>, StoreError> {
37 let already: Option<String> = store.with_conn(|c| {
38 c.query_row(
39 "SELECT value FROM meta WHERE key='migrated_from'",
40 [],
41 |r| r.get(0),
42 )
43 .map(Some)
44 .or_else(|e| match e {
45 rusqlite::Error::QueryReturnedNoRows => Ok(None),
46 other => Err(other),
47 })
48 })?;
49 if already.is_some() {
50 return Ok(None);
51 }
52
53 let path = match legacy_path {
54 Some(p) => p.to_path_buf(),
55 None => match legacy_desktop_db_path() {
56 Some(p) => p,
57 None => return Ok(None),
58 },
59 };
60 if !path.exists() {
61 return Ok(None);
62 }
63
64 store.with_conn(|conn| {
66 conn.execute_batch(&format!(
67 "ATTACH DATABASE {p} AS old;
68 BEGIN;
69 INSERT OR REPLACE INTO clips
70 (id, source, source_key, content_type, content, media_path, byte_size, created_at, pinned, pinned_at)
71 SELECT id, source, source_key, content_type, content, media_path, byte_size, created_at,
72 COALESCE(pinned, 0), pinned_at
73 FROM old.clips;
74 INSERT OR REPLACE INTO devices
75 SELECT id, hostname, nickname, source_key, machine_id, public_key,
76 paired_at, last_push_at, online, refreshed_at FROM old.devices;
77 INSERT OR REPLACE INTO retention_prefs SELECT device_id, days FROM old.retention_prefs;
78 INSERT OR REPLACE INTO alert_prefs SELECT source, enabled FROM old.alert_prefs;
79 INSERT OR REPLACE INTO meta(key, value)
80 VALUES('migrated_from', {p});
81 COMMIT;
82 DETACH DATABASE old;",
83 p = sql_literal(&path.to_string_lossy())
84 ))?;
85 Ok(())
86 })?;
87
88 if let Some(parent) = path.parent() {
90 let old_media = parent.join("media");
91 if old_media.exists() {
92 std::fs::create_dir_all(new_media_root)?;
93 for entry in std::fs::read_dir(&old_media)? {
94 let e = entry?;
95 let dest = new_media_root.join(e.file_name());
96 if std::fs::rename(e.path(), &dest).is_err() {
97 std::fs::copy(e.path(), &dest)?;
98 }
99 }
100 }
101 }
102
103 let bak = path.parent().unwrap_or_else(|| Path::new("")).join(format!(
106 "{}.bak",
107 path.file_name().unwrap_or_default().to_string_lossy()
108 ));
109 let _ = std::fs::rename(&path, bak);
110 Ok(Some(path))
111}
112
113fn sql_literal(s: &str) -> String {
114 format!("'{}'", s.replace('\'', "''"))
115}