mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
Merge branch 'main' of https://github.com/rustmailer/bichon
This commit is contained in:
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use bichon_core::migrate::{
|
use bichon_core::migrate::{
|
||||||
count_eml_segments, do_migrate_segment, is_tantivy_index_dir,
|
count_eml_segments, do_migrate_segment, is_tantivy_index_dir,
|
||||||
store::{LegacyDirs, NewDirs},
|
store::{LegacyDirs, NewDirs, NewIndexWriter},
|
||||||
};
|
};
|
||||||
use console::style;
|
use console::style;
|
||||||
use dialoguer::{theme::ColorfulTheme, Confirm, Input};
|
use dialoguer::{theme::ColorfulTheme, Confirm, Input};
|
||||||
@@ -326,6 +326,18 @@ pub fn handle_migration(theme: &ColorfulTheme) {
|
|||||||
.progress_chars("#>-"),
|
.progress_chars("#>-"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut writer = match NewIndexWriter::open(NewDirs::new(
|
||||||
|
new_index_path.clone(),
|
||||||
|
new_data_path.clone(),
|
||||||
|
)) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => {
|
||||||
|
pb.finish_with_message(format!("{}", style("Migration failed.").red()));
|
||||||
|
eprintln!("\n{} {:?}", style("✘").red().bold(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut grand_total_migrated: usize = 0;
|
let mut grand_total_migrated: usize = 0;
|
||||||
let mut grand_total_skipped: usize = 0;
|
let mut grand_total_skipped: usize = 0;
|
||||||
|
|
||||||
@@ -337,7 +349,7 @@ pub fn handle_migration(theme: &ColorfulTheme) {
|
|||||||
match do_migrate_segment(
|
match do_migrate_segment(
|
||||||
batch_size,
|
batch_size,
|
||||||
legacy,
|
legacy,
|
||||||
NewDirs::new(new_index_path.clone(), new_data_path.clone()),
|
&mut writer,
|
||||||
seg_idx,
|
seg_idx,
|
||||||
|msg| {
|
|msg| {
|
||||||
if let Some(data) = msg.strip_prefix("TOTAL:") {
|
if let Some(data) = msg.strip_prefix("TOTAL:") {
|
||||||
@@ -407,6 +419,13 @@ pub fn handle_migration(theme: &ColorfulTheme) {
|
|||||||
pb.set_position((seg_idx + 1) as u64);
|
pb.set_position((seg_idx + 1) as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pb.set_message(style("Finalizing indexes...").dim().to_string());
|
||||||
|
if let Err(e) = writer.finish_writers() {
|
||||||
|
pb.finish_with_message(format!("{}", style("Migration failed.").red()));
|
||||||
|
eprintln!("\n{} {:?}", style("✘").red().bold(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pb.finish_with_message(format!(
|
pb.finish_with_message(format!(
|
||||||
"Migration finished. Total: {}, Skipped: {}",
|
"Migration finished. Total: {}, Skipped: {}",
|
||||||
grand_total_migrated, grand_total_skipped
|
grand_total_migrated, grand_total_skipped
|
||||||
|
|||||||
+34
-19
@@ -339,6 +339,17 @@ pub async fn fetch_and_save_full_mailbox(
|
|||||||
Ok(max_uid)
|
Ok(max_uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a synthetic UIDVALIDITY for IMAP servers that don't provide it.
|
||||||
|
/// Uses a stable hash of the mailbox name to ensure consistent IDs across sessions.
|
||||||
|
fn generate_synthetic_uidvalidity(mailbox_name: &str) -> u32 {
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
mailbox_name.hash(&mut hasher);
|
||||||
|
(hasher.finish() as u32).wrapping_add(1) // Avoid 0, which might be reserved
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn reconcile_mailboxes(
|
pub async fn reconcile_mailboxes(
|
||||||
account: &AccountModel,
|
account: &AccountModel,
|
||||||
remote_mailboxes: &[MailBox],
|
remote_mailboxes: &[MailBox],
|
||||||
@@ -366,30 +377,30 @@ pub async fn reconcile_mailboxes(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_highest_uid = if local_mailbox.uid_validity != remote_mailbox.uid_validity {
|
// Handle missing UIDVALIDITY from non-compliant IMAP servers
|
||||||
if remote_mailbox.uid_validity.is_none() {
|
// (e.g., Tencent Enterprise Mail, etc.)
|
||||||
let err_msg = format!(
|
let remote_uid_validity = match remote_mailbox.uid_validity {
|
||||||
"Mailbox '{}' logic error: Server did not provide UIDVALIDITY.",
|
Some(uid) => uid,
|
||||||
local_mailbox.name
|
None => {
|
||||||
|
// Generate a synthetic UIDVALIDITY based on mailbox name
|
||||||
|
let synthetic_uid = generate_synthetic_uidvalidity(&remote_mailbox.name);
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"Account {}: Mailbox '{}' - Server did not provide UIDVALIDITY. \
|
||||||
|
Using synthetic UIDVALIDITY {} based on mailbox name. \
|
||||||
|
This mailbox will be synced but may require periodic rebuilds if the server's mailbox structure changes.",
|
||||||
|
account_id, remote_mailbox.name, synthetic_uid
|
||||||
);
|
);
|
||||||
|
|
||||||
warn!("Account {}: {}", account_id, err_msg);
|
synthetic_uid
|
||||||
|
|
||||||
DownloadState::update_folder_progress(
|
|
||||||
account_id,
|
|
||||||
remote_mailbox.name.clone(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
FolderStatus::Failed,
|
|
||||||
Some(err_msg.clone()),
|
|
||||||
)?;
|
|
||||||
DownloadState::append_session_error(account_id, err_msg)?;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_highest_uid = if local_mailbox.uid_validity != Some(remote_uid_validity) {
|
||||||
info!(
|
info!(
|
||||||
"Account {}: Mailbox '{}' detected with changed uid_validity (local: {:#?}, remote: {:#?}). \
|
"Account {}: Mailbox '{}' detected with changed uid_validity (local: {:#?}, remote: {:#?}). \
|
||||||
The mailbox data may be invalid, resetting its envelopes and rebuilding the cache.",
|
The mailbox data may be invalid, resetting its envelopes and rebuilding the cache.",
|
||||||
account_id, local_mailbox.name, &local_mailbox.uid_validity, &remote_mailbox.uid_validity
|
account_id, local_mailbox.name, &local_mailbox.uid_validity, &remote_uid_validity
|
||||||
);
|
);
|
||||||
|
|
||||||
DownloadState::update_folder_progress(
|
DownloadState::update_folder_progress(
|
||||||
@@ -443,6 +454,10 @@ pub async fn reconcile_mailboxes(
|
|||||||
|
|
||||||
let mut updated = remote_mailbox.clone();
|
let mut updated = remote_mailbox.clone();
|
||||||
updated.highest_uid = new_highest_uid;
|
updated.highest_uid = new_highest_uid;
|
||||||
|
// Update uid_validity with the resolved value (either from server or synthetic)
|
||||||
|
if updated.uid_validity.is_none() {
|
||||||
|
updated.uid_validity = Some(remote_uid_validity);
|
||||||
|
}
|
||||||
mailboxes_to_update.push(updated);
|
mailboxes_to_update.push(updated);
|
||||||
}
|
}
|
||||||
//The metadata of this mailbox must only be updated after a successful synchronization;
|
//The metadata of this mailbox must only be updated after a successful synchronization;
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ fn is_dir_not_empty(path: &PathBuf) -> std::io::Result<bool> {
|
|||||||
pub fn do_migrate_segment<F>(
|
pub fn do_migrate_segment<F>(
|
||||||
batch_size: u32,
|
batch_size: u32,
|
||||||
legacy: LegacyDirs,
|
legacy: LegacyDirs,
|
||||||
new_dirs: NewDirs,
|
writer: &mut NewIndexWriter,
|
||||||
segment_index: usize,
|
segment_index: usize,
|
||||||
mut on_progress: F,
|
mut on_progress: F,
|
||||||
) -> BichonResult<()>
|
) -> BichonResult<()>
|
||||||
@@ -226,8 +226,6 @@ where
|
|||||||
drop(envelope_index);
|
drop(envelope_index);
|
||||||
|
|
||||||
// ── Phase 2: process EML docs, streaming one at a time ─────────────
|
// ── Phase 2: process EML docs, streaming one at a time ─────────────
|
||||||
let mut writer = NewIndexWriter::open(new_dirs)?;
|
|
||||||
|
|
||||||
let mut total_migrated = 0usize;
|
let mut total_migrated = 0usize;
|
||||||
let mut total_skipped = 0usize;
|
let mut total_skipped = 0usize;
|
||||||
|
|
||||||
@@ -308,7 +306,6 @@ where
|
|||||||
chunk_start = chunk_end;
|
chunk_start = chunk_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.finish_writers()?;
|
|
||||||
on_progress(&format!("DONE:{}:{}", total_migrated, total_skipped));
|
on_progress(&format!("DONE:{}:{}", total_migrated, total_skipped));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user