Skip to content

Commit 9a332b0

Browse files
JSKittyclaude
andcommitted
feat: configurable auto-download limit, fix Android MY_KEYS race
Add auto-download size limit dropdown in Storage settings (Off, 1-100 MB) persisted via sql_setting. Fix Android background sync not setting MY_KEYS in globals — caused login_from_stored_key to early-return without keys, breaking all message sends after resuming from background. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b488fa2 commit 9a332b0

5 files changed

Lines changed: 64 additions & 7 deletions

File tree

src-tauri/src/android/background_sync.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::collections::HashSet;
1616
use std::sync::atomic::{AtomicBool, Ordering};
1717
use std::sync::{Arc, Mutex, OnceLock};
1818

19-
use crate::{NOSTR_CLIENT, MY_PUBLIC_KEY};
19+
use crate::{NOSTR_CLIENT, MY_KEYS, MY_PUBLIC_KEY};
2020
use crate::commands::relays::DEFAULT_RELAYS;
2121
use crate::services::event_handler::handle_event_with_context;
2222

@@ -248,22 +248,25 @@ fn run_standalone_sync_loop(data_dir: &str) {
248248

249249
rt.block_on(async {
250250
// Bootstrap the standalone client — get connected ASAP
251-
let (client, my_public_key, can_decrypt) = match bootstrap_client(data_dir).await {
251+
let (client, my_public_key, can_decrypt, keys) = match bootstrap_client(data_dir).await {
252252
Ok(result) => result,
253253
Err(e) => {
254254
logcat(&format!("Failed to bootstrap client: {}", e));
255255
return;
256256
}
257257
};
258258

259-
// Store the client and public key in globals so headless operations
259+
// Store the client, keys, and public key in globals so headless operations
260260
// (notification reply, mark-as-read) can use them in service-only mode.
261261
// ONLY set for unencrypted accounts — encrypted accounts use a signerless
262262
// client that would poison the OnceCell and prevent the full app from
263263
// creating a proper client with signer after PIN unlock.
264264
if can_decrypt {
265265
let _ = NOSTR_CLIENT.set(client.clone());
266266
let _ = MY_PUBLIC_KEY.set(my_public_key);
267+
if let Some(keys) = keys {
268+
let _ = MY_KEYS.set(keys);
269+
}
267270
}
268271

269272
// Subscribe to GiftWraps addressed to us (DMs, files, MLS welcomes)
@@ -402,7 +405,7 @@ fn run_standalone_sync_loop(data_dir: &str) {
402405
/// Returns (client, public_key, can_decrypt).
403406
/// When local encryption is enabled, returns a read-only client (no signer) that can
404407
/// subscribe to events but not decrypt them — used for generic "New message" notifications.
405-
async fn bootstrap_client(data_dir: &str) -> Result<(Client, PublicKey, bool), String> {
408+
async fn bootstrap_client(data_dir: &str) -> Result<(Client, PublicKey, bool, Option<Keys>), String> {
406409
let data_path = std::path::Path::new(data_dir);
407410

408411
// Scan for npub directories
@@ -486,7 +489,7 @@ async fn bootstrap_client(data_dir: &str) -> Result<(Client, PublicKey, bool), S
486489
logcat(&format!("Connecting to {} relays...", DEFAULT_RELAYS.len()));
487490
client.connect().await;
488491

489-
Ok((client, my_public_key, false))
492+
Ok((client, my_public_key, false, None))
490493
} else {
491494
// Normal account — full signer client
492495
let pkey: String = conn
@@ -508,7 +511,7 @@ async fn bootstrap_client(data_dir: &str) -> Result<(Client, PublicKey, bool), S
508511
&my_public_key.to_bech32().unwrap_or_default()[..20.min(my_public_key.to_bech32().unwrap_or_default().len())]));
509512

510513
let client = Client::builder()
511-
.signer(keys)
514+
.signer(keys.clone())
512515
.build();
513516

514517
for relay_url in DEFAULT_RELAYS {
@@ -520,7 +523,7 @@ async fn bootstrap_client(data_dir: &str) -> Result<(Client, PublicKey, bool), S
520523
logcat(&format!("Connecting to {} relays...", DEFAULT_RELAYS.len()));
521524
client.connect().await;
522525

523-
Ok((client, my_public_key, true))
526+
Ok((client, my_public_key, true, Some(keys)))
524527
}
525528
}
526529

src/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,20 @@ <h2>Storage</h2>
10071007
<div id="storage-bar" style="height: 100%; display: flex;"></div>
10081008
</div>
10091009
</div>
1010+
<div class="form-group" style="display: flex; align-items: center; margin-top: 15px;">
1011+
<span style="flex: 1;">Auto-Download Limit</span>
1012+
<div class="select-container" style="margin: 0;">
1013+
<select id="auto-download-limit" style="margin-bottom: 0 !important;">
1014+
<option value="0">Off</option>
1015+
<option value="1048576">1 MB</option>
1016+
<option value="5242880">5 MB</option>
1017+
<option value="10485760" selected>10 MB</option>
1018+
<option value="26214400">25 MB</option>
1019+
<option value="52428800">50 MB</option>
1020+
<option value="104857600">100 MB</option>
1021+
</select>
1022+
</div>
1023+
</div>
10101024
<div class="form-group" style="margin-top: 15px;">
10111025
<button id="clear-storage-btn" class="btn cancel-btn">Clear Storage</button>
10121026
</div>

src/js/db.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
async function loadMaxAutoDownloadBytes() {
2+
const value = await invoke('get_sql_setting', { key: 'max_auto_download_bytes' });
3+
return value !== null && value !== undefined ? parseInt(value, 10) : 10485760;
4+
}
5+
6+
async function saveMaxAutoDownloadBytes(bytes) {
7+
await invoke('set_sql_setting', { key: 'max_auto_download_bytes', value: String(bytes) });
8+
}
9+
110
/**
211
* Save the user-selected Whisper Model ID
312
* @param {string} name - The model ID

src/js/settings.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,19 @@ async function initStorageSection() {
11351135
// Render file type distribution bar
11361136
renderFileTypeDistribution(storageInfo.type_distribution, storageInfo.file_count);
11371137
}
1138+
1139+
// Auto-download limit
1140+
const savedLimit = await loadMaxAutoDownloadBytes();
1141+
MAX_AUTO_DOWNLOAD_BYTES = savedLimit;
1142+
const limitSelect = document.getElementById('auto-download-limit');
1143+
if (limitSelect) {
1144+
limitSelect.value = String(savedLimit);
1145+
limitSelect.addEventListener('change', async () => {
1146+
const bytes = parseInt(limitSelect.value, 10);
1147+
MAX_AUTO_DOWNLOAD_BYTES = bytes;
1148+
await saveMaxAutoDownloadBytes(bytes);
1149+
});
1150+
}
11381151
}
11391152

11401153
function renderFileTypeDistribution(typeDistribution, totalBytes) {

src/styles.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4083,6 +4083,24 @@ select:focus::-ms-value {
40834083
padding-right: 36px;
40844084
}
40854085

4086+
#auto-download-limit {
4087+
text-align: center;
4088+
text-align-last: center;
4089+
appearance: none;
4090+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E");
4091+
background-repeat: no-repeat;
4092+
background-position: right 12px center;
4093+
background-size: 10px;
4094+
padding-right: 36px;
4095+
width: auto;
4096+
}
4097+
4098+
#auto-download-limit option {
4099+
background: #171717;
4100+
text-align: center;
4101+
padding: 10px;
4102+
}
4103+
40864104
/* Delete model button with trash bin icon */
40874105
.btn-delete-model {
40884106
position: absolute;

0 commit comments

Comments
 (0)