Skip to content

Commit 62f129e

Browse files
committed
Stick to fewer hardcoded locations for the rpc server
* Avoid scanning subdirectories or fuzzy findings * We may want to let the user configure this path from the UI
1 parent d2517c8 commit 62f129e

3 files changed

Lines changed: 69 additions & 247 deletions

File tree

src/account_finder.vala

Lines changed: 65 additions & 236 deletions
Original file line numberDiff line numberDiff line change
@@ -1,275 +1,104 @@
11
namespace Dc {
22

33
/**
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.
67
*/
78
public class AccountFinder {
89

9-
/* ---- RPC server binary resolution ---- */
10+
private const string RPC_BIN = "deltachat-rpc-server";
1011

1112
/**
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.
1414
*/
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 = {
12939
"stdio-rpc-server-linux-x64",
13040
"stdio-rpc-server-linux-arm64",
13141
};
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) {
14544
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);
14946
if (FileUtils.test (candidate, FileTest.IS_EXECUTABLE)) {
15047
return candidate;
15148
}
15249
}
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-
}
16150
}
162-
16351
return null;
16452
}
16553

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-
18854
/**
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.
19357
*/
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;
22970
}
230-
return null;
71+
string fallback = Path.build_filename (home, ".config", "deltachat-gnome");
72+
DirUtils.create_with_parents (fallback, 0700);
73+
return fallback;
23174
}
23275

23376
/**
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`.
23779
*/
23880
public static async int ensure_configured (RpcClient rpc,
23981
out string? description, out string? toast_msg) {
24082
description = null;
24183
toast_msg = null;
24284
try {
24385
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+
}
26697
}
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.";
27298
}
99+
description =
100+
"No Delta Chat accounts configured.\n" +
101+
"Add an account from Settings to connect.";
273102
} catch (Error e) {
274103
toast_msg = "Account setup error: " + e.message;
275104
}

src/models.vala

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,6 @@ namespace Dc {
159159
public bool is_verified { get; set; default = false; }
160160
}
161161

162-
public class FoundInstallation : Object {
163-
public string label { get; set; default = ""; }
164-
public string data_path { get; set; default = ""; }
165-
public string? email { get; set; default = null; }
166-
public string source { get; set; default = ""; }
167-
}
168-
169162
public ChatEntry? find_chat_entry (GLib.ListStore store, int chat_id) {
170163
for (uint i = 0; i < store.get_n_items (); i++) {
171164
var entry = (ChatEntry) store.get_item (i);

src/window.vala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,20 +212,20 @@ namespace Dc {
212212
rpc = ((Dc.Application) this.application).rpc;
213213

214214
/* Find the RPC server binary */
215-
string[]? rpc_cmd = AccountFinder.find_rpc_server ();
216-
if (rpc_cmd == null) {
215+
string? rpc_path = AccountFinder.find_rpc_server ();
216+
if (rpc_path == null) {
217217
show_toast ("Delta Chat RPC server not found. Install deltachat-rpc-server.");
218218
empty_status.description = "deltachat-rpc-server not found.\nInstall it with: pip install deltachat-rpc-server";
219219
return;
220220
}
221221

222222
/* Determine data directory and accounts path */
223-
string data_dir = AccountFinder.get_default_data_dir ();
223+
string data_dir = AccountFinder.get_data_dir ();
224224
string accounts_path = Path.build_filename (data_dir, "accounts");
225225

226226
/* Try to connect */
227227
try {
228-
yield rpc.start (rpc_cmd, data_dir, accounts_path);
228+
yield rpc.start ({ rpc_path }, data_dir, accounts_path);
229229
} catch (Error e) {
230230
string msg = e.message;
231231
if ("already running" in msg.down () || "accounts.lock" in msg.down ()) {

0 commit comments

Comments
 (0)