Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/admin/src/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ impl From<AccountV3> for AccountModel {
updated_at: value.updated_at,
created_by: value.created_by,
use_dangerous: value.use_dangerous,
uidonly_enabled: false,
pgp_key: value.pgp_key,
imap_quota_window: None,
imap_quota_bytes: None,
Expand Down Expand Up @@ -666,6 +667,7 @@ impl From<MailBox> for bichon_core::cache::imap::mailbox::MailBox {
uid_next: value.uid_next,
uid_validity: value.uid_validity,
highest_uid: None,
uidonly_source_scope: None,
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions crates/blob/src/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,27 @@ impl IndexStore {
self.get(key).map(|r| r.is_some())
}

/// Check several keys in one read transaction.
pub fn exists_batch(&self, keys: &[[u8; 32]]) -> Result<Vec<bool>> {
let txn = self
.db
.begin_read()
.map_err(|e| crate::error::Error::IndexDb(format!("read txn: {}", e)))?;
let table = txn
.open_table(INDEX_TABLE)
.map_err(|e| crate::error::Error::IndexDb(format!("open table: {}", e)))?;
keys.iter()
.map(|key| {
let record = table
.get(key)
.map_err(|e| crate::error::Error::IndexDb(format!("get: {}", e)))?
.map(|guard| IndexRecord::decode(&guard.value().0))
.transpose()?;
Ok(record.is_some_and(|record| !record.is_tombstone()))
})
.collect()
}

/// Insert or update a record for a key. Committed in a single write txn.
pub fn insert(&self, record: &IndexRecord) -> Result<()> {
let txn = self
Expand Down
5 changes: 4 additions & 1 deletion crates/blob/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ impl Engine {
self.shared.index_store.exists(key)
}

pub fn exists_batch(&self, keys: &[[u8; 32]]) -> Result<Vec<bool>> {
self.shared.index_store.exists_batch(keys)
}

// ── Batch delete ─────────────────────────────────────────────────────

pub fn delete_batch(&self, keys: &[[u8; 32]]) -> Result<()> {
Expand Down Expand Up @@ -375,7 +379,6 @@ impl Engine {

let mut records: Vec<IndexRecord> = Vec::with_capacity(entries.len());
let mut ends: Vec<(u32, u64)> = Vec::with_capacity(entries.len());

for (key, value, codec) in entries {
if value.len() > crate::types::MAX_VALUE_SIZE {
return Err(Error::ValueTooLarge { size: value.len() });
Expand Down
39 changes: 35 additions & 4 deletions crates/core/src/account/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use crate::{
id,
oauth2::token::OAuth2AccessToken,
raise_error,
store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER},
store::tantivy::envelope::ENVELOPE_MANAGER,
users::{payload::UserUpdateRequest, role::DEFAULT_ACCOUNT_MANAGER_ROLE_ID, UserModel},
utc_now,
};
Expand Down Expand Up @@ -301,6 +301,9 @@ pub struct Account {
pub updated_at: i64,
pub created_by: u64, //user id
pub use_dangerous: bool,
/// UIDONLY acquisition is opt-in because it changes remote message identity.
#[serde(default)]
pub uidonly_enabled: bool,
pub pgp_key: Option<String>,
pub imap_quota_bytes: Option<u64>,
pub imap_quota_window: Option<QuotaWindow>,
Expand Down Expand Up @@ -345,6 +348,7 @@ impl Account {
created_at: utc_now!(),
updated_at: utc_now!(),
use_dangerous: request.use_dangerous,
uidonly_enabled: request.uidonly_enabled.unwrap_or_default(),
pgp_key: request.pgp_key,
created_by: user_id,
download_batch_size: request.download_batch_size,
Expand Down Expand Up @@ -502,9 +506,6 @@ impl Account {
ENVELOPE_MANAGER
.delete_account_envelopes(account.id)
.await?;
ATTACHMENT_MANAGER
.delete_account_attachments(account.id)
.await?;
Self::delete_account(account)?;
info!("Sequential cleanup completed for account: {}", account.id);
Ok(())
Expand Down Expand Up @@ -671,6 +672,10 @@ impl Account {
new.use_dangerous = use_dangerous;
}

if let Some(uidonly_enabled) = request.uidonly_enabled {
new.uidonly_enabled = uidonly_enabled;
}

if let Some(pgp_key) = request.pgp_key {
new.pgp_key = Some(pgp_key);
}
Expand Down Expand Up @@ -707,6 +712,32 @@ impl Account {
mod tests {
use super::*;

#[test]
fn uidonly_setting_defaults_off_and_only_changes_explicitly() {
let account = Account::new(1, AccountCreateRequest::default()).unwrap();
assert!(!account.uidonly_enabled);
let update = |old: &Account, value| {
Account::apply_update_fields(
old,
AccountUpdateRequest {
uidonly_enabled: value,
..Default::default()
},
)
.unwrap()
};
let enabled = update(&account, Some(true));
assert!(enabled.uidonly_enabled);
assert!(update(&enabled, None).uidonly_enabled);
assert!(!update(&enabled, Some(false)).uidonly_enabled);

let mut legacy = serde_json::to_value(enabled).unwrap();
legacy.as_object_mut().unwrap().remove("uidonly_enabled");
assert!(!serde_json::from_value::<Account>(legacy)
.unwrap()
.uidonly_enabled);
}

// ── FilterRule ───────────────────────────────────────────────────

#[test]
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/account/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub struct AccountCreateRequest {
pub download_batch_size: Option<u32>,
pub max_email_size_bytes: Option<u64>,
pub use_dangerous: bool,
/// Explicitly enables UIDONLY acquisition when required by the server.
/// Omitted values remain disabled for backward compatibility.
pub uidonly_enabled: Option<bool>,
pub pgp_key: Option<String>,
pub imap_quota_bytes: Option<u64>,
pub imap_quota_window: Option<QuotaWindow>,
Expand Down Expand Up @@ -188,6 +191,8 @@ pub struct AccountUpdateRequest {
pub download_batch_size: Option<u32>,
pub max_email_size_bytes: Option<u64>,
pub use_dangerous: Option<bool>,
/// Enables or disables UIDONLY acquisition for this account.
pub uidonly_enabled: Option<bool>,

pub pgp_key: Option<String>,
pub imap_quota_bytes: Option<u64>,
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/account/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub struct AccountResp {
pub created_user_name: String,
pub created_user_email: String,
pub use_dangerous: bool,
pub uidonly_enabled: bool,
pub pgp_key: Option<String>,
pub imap_quota_bytes: Option<u64>,
pub imap_quota_window: Option<QuotaWindow>,
Expand Down Expand Up @@ -90,6 +91,7 @@ impl AccountResp {
.map(|u| u.email.clone())
.unwrap_or_else(|| "N/A".to_string()),
use_dangerous: account.use_dangerous,
uidonly_enabled: account.uidonly_enabled,
pgp_key: account.pgp_key,
imap_quota_bytes: account.imap_quota_bytes,
imap_quota_window: account.imap_quota_window,
Expand Down
Loading