|
1 | 1 | namespace Dc { |
2 | 2 |
|
3 | 3 | /** |
4 | | - * Locates existing DeltaChat installations and the RPC server binary. |
5 | | - * Supports Flatpak, native, and Snap installations. |
| 4 | + * Locates Delta Chat Desktop installations and the deltachat-rpc-server |
| 5 | + * binary via a small fixed list of well-known paths. No filesystem |
| 6 | + * scanning — startup stays fast and predictable. |
6 | 7 | */ |
7 | 8 | public class AccountFinder { |
8 | 9 |
|
9 | | - /* ---- RPC server binary resolution ---- */ |
| 10 | + private const string RPC_BIN = "deltachat-rpc-server"; |
10 | 11 |
|
11 | 12 | /** |
12 | | - * Find the deltachat-rpc-server binary. Returns the argv array to spawn it, |
13 | | - * or null if not found. |
| 13 | + * Return an absolute path to deltachat-rpc-server, or null if none found. |
14 | 14 | */ |
15 | | - public static string[]? find_rpc_server () { |
16 | | - /* 1. PATH lookup */ |
17 | | - string? in_path = Environment.find_program_in_path ("deltachat-rpc-server"); |
18 | | - if (in_path != null) { |
19 | | - return new string[] { in_path }; |
20 | | - } |
21 | | - |
22 | | - /* 2. Common venv location */ |
23 | | - string venv_bin = Path.build_filename ( |
24 | | - Environment.get_home_dir (), ".venv", "deltachat", "bin", "deltachat-rpc-server" |
25 | | - ); |
26 | | - if (FileUtils.test (venv_bin, FileTest.IS_EXECUTABLE)) { |
27 | | - return new string[] { venv_bin }; |
28 | | - } |
29 | | - |
30 | | - /* 3. venv via python */ |
31 | | - string venv_py = Path.build_filename ( |
32 | | - Environment.get_home_dir (), ".venv", "deltachat", "bin", "python" |
33 | | - ); |
34 | | - if (FileUtils.test (venv_py, FileTest.IS_EXECUTABLE)) { |
35 | | - if (FileUtils.test (venv_bin, FileTest.EXISTS)) { |
36 | | - return new string[] { venv_py, venv_bin }; |
37 | | - } |
38 | | - return new string[] { venv_py, "-m", "deltachat_rpc_server" }; |
39 | | - } |
40 | | - |
41 | | - /* 4. pip --user install */ |
42 | | - string local_bin = Path.build_filename ( |
43 | | - Environment.get_home_dir (), ".local", "bin", "deltachat-rpc-server" |
44 | | - ); |
45 | | - if (FileUtils.test (local_bin, FileTest.IS_EXECUTABLE)) { |
46 | | - return new string[] { local_bin }; |
47 | | - } |
48 | | - |
49 | | - /* 5. Inside Flatpak DeltaChat Desktop installation */ |
50 | | - string? flatpak_rpc = find_flatpak_rpc_server (); |
51 | | - if (flatpak_rpc != null) { |
52 | | - return new string[] { flatpak_rpc }; |
53 | | - } |
54 | | - |
55 | | - return null; |
56 | | - } |
57 | | - |
58 | | - /* ---- Installation discovery ---- */ |
59 | | - |
60 | | - /** |
61 | | - * Scan the system for existing DeltaChat data directories and config files. |
62 | | - * Returns a list of found installations with whatever metadata we can extract. |
63 | | - */ |
64 | | - public static GenericArray<FoundInstallation> find_installations () { |
65 | | - var results = new GenericArray<FoundInstallation> (); |
66 | | - |
67 | | - /* Flatpak desktop app */ |
68 | | - string flatpak_dir = Path.build_filename ( |
69 | | - Environment.get_home_dir (), |
70 | | - ".var", "app", "chat.delta.desktop", "config", "DeltaChat" |
71 | | - ); |
72 | | - check_deltachat_dir (results, flatpak_dir, "Delta Chat Desktop (Flatpak)"); |
73 | | - |
74 | | - /* Native / distro-packaged desktop app */ |
75 | | - string native_dir = Path.build_filename ( |
76 | | - Environment.get_home_dir (), ".config", "DeltaChat" |
77 | | - ); |
78 | | - check_deltachat_dir (results, native_dir, "Delta Chat Desktop (native)"); |
79 | | - |
80 | | - /* XDG_CONFIG_HOME override */ |
81 | | - string? xdg = Environment.get_variable ("XDG_CONFIG_HOME"); |
82 | | - if (xdg != null && xdg.length > 0) { |
83 | | - string xdg_dir = Path.build_filename (xdg, "DeltaChat"); |
84 | | - if (xdg_dir != native_dir) { |
85 | | - check_deltachat_dir (results, xdg_dir, "Delta Chat Desktop (XDG)"); |
86 | | - } |
87 | | - } |
88 | | - |
89 | | - /* Snap */ |
90 | | - string snap_dir = Path.build_filename ( |
91 | | - Environment.get_home_dir (), |
92 | | - "snap", "deltachat-desktop", "current", ".config", "DeltaChat" |
93 | | - ); |
94 | | - check_deltachat_dir (results, snap_dir, "Delta Chat Desktop (Snap)"); |
95 | | - |
96 | | - return results; |
97 | | - } |
98 | | - |
99 | | - /** |
100 | | - * Find the best data directory to start the RPC server in. |
101 | | - * Prefers Flatpak > native > fallback. |
102 | | - */ |
103 | | - public static string get_default_data_dir () { |
104 | | - var installations = find_installations (); |
105 | | - for (int i = 0; i < installations.length; i++) { |
106 | | - var inst = installations[i]; |
107 | | - if (inst.data_path.length > 0 && |
108 | | - FileUtils.test (inst.data_path, FileTest.IS_DIR)) { |
109 | | - return inst.data_path; |
110 | | - } |
111 | | - } |
112 | | - /* Fallback: create in user config */ |
113 | | - string fallback = Path.build_filename ( |
114 | | - Environment.get_home_dir (), ".config", "deltachat-gnome" |
115 | | - ); |
116 | | - DirUtils.create_with_parents (fallback, 0700); |
117 | | - return fallback; |
118 | | - } |
119 | | - |
120 | | - /* ---- Private helpers ---- */ |
121 | | - |
122 | | - /** |
123 | | - * Search for deltachat-rpc-server binary inside a Flatpak DeltaChat |
124 | | - * Desktop installation (user or system). |
125 | | - */ |
126 | | - private static string? find_flatpak_rpc_server () { |
127 | | - /* Relative path inside the Flatpak app files */ |
128 | | - string[] arch_variants = { |
| 15 | + public static string? find_rpc_server () { |
| 16 | + /* PATH first — covers distro packages, /usr/local, and ~/.local/bin |
| 17 | + * when the user has it exported. */ |
| 18 | + string? in_path = Environment.find_program_in_path (RPC_BIN); |
| 19 | + if (in_path != null) return in_path; |
| 20 | + |
| 21 | + string home = Environment.get_home_dir (); |
| 22 | + |
| 23 | + /* pip --user or manual install. */ |
| 24 | + string user_bin = Path.build_filename (home, ".local", "bin", RPC_BIN); |
| 25 | + if (FileUtils.test (user_bin, FileTest.IS_EXECUTABLE)) return user_bin; |
| 26 | + |
| 27 | + /* Electron-bundled binary inside a Delta Chat Desktop install. |
| 28 | + * All installs share the same node_modules layout; only the root |
| 29 | + * and the arch suffix differ. */ |
| 30 | + string[] app_roots = { |
| 31 | + "/opt/DeltaChat/resources/app.asar.unpacked", |
| 32 | + Path.build_filename (home, ".local", "share", "flatpak", "app", |
| 33 | + "chat.delta.desktop", "current", "active", |
| 34 | + "files", "delta", "resources", "app"), |
| 35 | + "/var/lib/flatpak/app/chat.delta.desktop/current/active/files/delta/resources/app", |
| 36 | + "/snap/deltachat-desktop/current/resources/app.asar.unpacked", |
| 37 | + }; |
| 38 | + string[] arch_dirs = { |
129 | 39 | "stdio-rpc-server-linux-x64", |
130 | 40 | "stdio-rpc-server-linux-arm64", |
131 | 41 | }; |
132 | | - |
133 | | - string[] flatpak_roots = { |
134 | | - Path.build_filename ( |
135 | | - Environment.get_home_dir (), |
136 | | - ".local", "share", "flatpak", "app", |
137 | | - "chat.delta.desktop", "x86_64", "stable", "active", "files"), |
138 | | - Path.build_filename ( |
139 | | - "/var", "lib", "flatpak", "app", |
140 | | - "chat.delta.desktop", "x86_64", "stable", "active", "files"), |
141 | | - }; |
142 | | - |
143 | | - foreach (string root in flatpak_roots) { |
144 | | - foreach (string arch in arch_variants) { |
| 42 | + foreach (string root in app_roots) { |
| 43 | + foreach (string arch in arch_dirs) { |
145 | 44 | string candidate = Path.build_filename ( |
146 | | - root, "delta", "resources", "app", |
147 | | - "node_modules", "@deltachat", arch, |
148 | | - "deltachat-rpc-server"); |
| 45 | + root, "node_modules", "@deltachat", arch, RPC_BIN); |
149 | 46 | if (FileUtils.test (candidate, FileTest.IS_EXECUTABLE)) { |
150 | 47 | return candidate; |
151 | 48 | } |
152 | 49 | } |
153 | | - /* Also check dc-core path */ |
154 | | - string dc_core = Path.build_filename ( |
155 | | - root, "dc-core", "deltachat-rpc-server", |
156 | | - "npm-package", "platform_package", |
157 | | - "x86_64-unknown-linux-gnu", "deltachat-rpc-server"); |
158 | | - if (FileUtils.test (dc_core, FileTest.IS_EXECUTABLE)) { |
159 | | - return dc_core; |
160 | | - } |
161 | 50 | } |
162 | | - |
163 | 51 | return null; |
164 | 52 | } |
165 | 53 |
|
166 | | - private static void check_deltachat_dir (GenericArray<FoundInstallation> results, |
167 | | - string dir_path, string label) { |
168 | | - if (!FileUtils.test (dir_path, FileTest.IS_DIR)) return; |
169 | | - |
170 | | - string accounts_dir = Path.build_filename (dir_path, "accounts"); |
171 | | - if (!FileUtils.test (accounts_dir, FileTest.IS_DIR)) return; |
172 | | - |
173 | | - var inst = new FoundInstallation (); |
174 | | - inst.label = label; |
175 | | - inst.data_path = dir_path; |
176 | | - inst.source = "desktop"; |
177 | | - |
178 | | - /* Try to read accounts.toml for email addresses */ |
179 | | - string toml_path = Path.build_filename (dir_path, "accounts.toml"); |
180 | | - if (FileUtils.test (toml_path, FileTest.EXISTS)) { |
181 | | - string? email = parse_accounts_toml_email (toml_path, accounts_dir); |
182 | | - if (email != null) inst.email = email; |
183 | | - } |
184 | | - |
185 | | - results.add (inst); |
186 | | - } |
187 | | - |
188 | 54 | /** |
189 | | - * Parse accounts.toml to extract the configured email address. |
190 | | - * accounts.toml is a simple TOML file listing account UUIDs. |
191 | | - * The actual email is in each account's dc.db, but we can list |
192 | | - * what accounts exist. |
| 55 | + * Return a Delta Chat Desktop data directory to reuse, or create |
| 56 | + * and return a private fallback under ~/.config/deltachat-gnome. |
193 | 57 | */ |
194 | | - private static string? parse_accounts_toml_email (string toml_path, |
195 | | - string accounts_dir) { |
196 | | - try { |
197 | | - string contents; |
198 | | - FileUtils.get_contents (toml_path, out contents); |
199 | | - |
200 | | - /* Look for selected_account or first account UUID dir */ |
201 | | - /* accounts.toml format: |
202 | | - * selected_account = "uuid-here" |
203 | | - * [accounts."uuid-here"] |
204 | | - * ... |
205 | | - */ |
206 | | - string? selected = null; |
207 | | - foreach (string line in contents.split ("\n")) { |
208 | | - string trimmed = line.strip (); |
209 | | - if (trimmed.has_prefix ("selected_account")) { |
210 | | - int eq = trimmed.index_of ("="); |
211 | | - if (eq >= 0) { |
212 | | - selected = trimmed.substring (eq + 1).strip () |
213 | | - .replace ("\"", "").replace ("'", ""); |
214 | | - } |
215 | | - break; |
216 | | - } |
217 | | - } |
218 | | - |
219 | | - if (selected != null && selected.length > 0) { |
220 | | - /* Check if this account directory exists and has dc.db */ |
221 | | - string acct_dir = Path.build_filename (accounts_dir, selected); |
222 | | - string db_path = Path.build_filename (acct_dir, "dc.db"); |
223 | | - if (FileUtils.test (db_path, FileTest.EXISTS)) { |
224 | | - return "(account: %s)".printf (selected.substring (0, 8)); |
225 | | - } |
226 | | - } |
227 | | - } catch (Error e) { |
228 | | - /* Ignore parse errors */ |
| 58 | + public static string get_data_dir () { |
| 59 | + string home = Environment.get_home_dir (); |
| 60 | + string[] candidates = { |
| 61 | + Path.build_filename (home, ".var", "app", |
| 62 | + "chat.delta.desktop", "config", "DeltaChat"), |
| 63 | + Path.build_filename (home, ".config", "DeltaChat"), |
| 64 | + Path.build_filename (home, "snap", "deltachat-desktop", |
| 65 | + "current", ".config", "DeltaChat"), |
| 66 | + }; |
| 67 | + foreach (string dir in candidates) { |
| 68 | + string accounts = Path.build_filename (dir, "accounts"); |
| 69 | + if (FileUtils.test (accounts, FileTest.IS_DIR)) return dir; |
229 | 70 | } |
230 | | - return null; |
| 71 | + string fallback = Path.build_filename (home, ".config", "deltachat-gnome"); |
| 72 | + DirUtils.create_with_parents (fallback, 0700); |
| 73 | + return fallback; |
231 | 74 | } |
232 | 75 |
|
233 | 76 | /** |
234 | | - * Find and activate a configured account, or create one from |
235 | | - * credentials. Returns the account id (>0) on success, or 0 with |
236 | | - * a human-readable status in `out description`. |
| 77 | + * Activate the first configured account. Returns its id (>0) on |
| 78 | + * success, or 0 with a human-readable status in `description`. |
237 | 79 | */ |
238 | 80 | public static async int ensure_configured (RpcClient rpc, |
239 | 81 | out string? description, out string? toast_msg) { |
240 | 82 | description = null; |
241 | 83 | toast_msg = null; |
242 | 84 | try { |
243 | 85 | var accounts_node = yield rpc.get_all_accounts (); |
244 | | - if (accounts_node == null) return 0; |
245 | | - var accounts = accounts_node.get_array (); |
246 | | - |
247 | | - for (uint i = 0; i < accounts.get_length (); i++) { |
248 | | - var acct = accounts.get_object_element (i); |
249 | | - int id = (int) acct.get_int_member ("id"); |
250 | | - if (yield rpc.is_configured (id)) { |
251 | | - rpc.account_id = id; |
252 | | - yield rpc.select_account (id); |
253 | | - yield rpc.start_io (id); |
254 | | - return id; |
255 | | - } |
256 | | - } |
257 | | - |
258 | | - var installations = find_installations (); |
259 | | - if (installations.length > 0) { |
260 | | - var sb = new StringBuilder ("Found installations:\n"); |
261 | | - for (int j = 0; j < installations.length; j++) { |
262 | | - var inst = installations[j]; |
263 | | - sb.append ("\xe2\x80\xa2 %s".printf (inst.label)); |
264 | | - if (inst.email != null) sb.append (" (%s)".printf (inst.email)); |
265 | | - sb.append ("\n"); |
| 86 | + if (accounts_node != null) { |
| 87 | + var accounts = accounts_node.get_array (); |
| 88 | + for (uint i = 0; i < accounts.get_length (); i++) { |
| 89 | + var acct = accounts.get_object_element (i); |
| 90 | + int id = (int) acct.get_int_member ("id"); |
| 91 | + if (yield rpc.is_configured (id)) { |
| 92 | + rpc.account_id = id; |
| 93 | + yield rpc.select_account (id); |
| 94 | + yield rpc.start_io (id); |
| 95 | + return id; |
| 96 | + } |
266 | 97 | } |
267 | | - description = sb.str + "\nAdd an account from Settings to connect."; |
268 | | - } else { |
269 | | - description = |
270 | | - "No Delta Chat accounts found.\n" + |
271 | | - "Add an account from Settings to connect."; |
272 | 98 | } |
| 99 | + description = |
| 100 | + "No Delta Chat accounts configured.\n" + |
| 101 | + "Add an account from Settings to connect."; |
273 | 102 | } catch (Error e) { |
274 | 103 | toast_msg = "Account setup error: " + e.message; |
275 | 104 | } |
|
0 commit comments