mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
initial commit
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
|
||||
//
|
||||
// This file is part of the Bichon Email Archiving Project
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::{cache::imap::task::SYNC_TASKS, error::BichonResult};
|
||||
use std::{sync::LazyLock, time::Duration};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub static SYNC_CONTROLLER: LazyLock<SyncController> = LazyLock::new(SyncController::new);
|
||||
|
||||
pub struct SyncController {
|
||||
channel: mpsc::Sender<(u64, String)>, // Channel to trigger account sync by account ID
|
||||
}
|
||||
|
||||
impl SyncController {
|
||||
pub fn new() -> Self {
|
||||
let (tx, mut rx) = mpsc::channel::<(u64, String)>(100);
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some((account_id, email)) = rx.recv().await {
|
||||
match Self::start_syncer(account_id, email.clone()).await {
|
||||
Ok(Some(_)) => {}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to prepare and start syncer of account {{{}-{}}}, error: {:#?}",
|
||||
&account_id, &email, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SyncController { channel: tx }
|
||||
}
|
||||
|
||||
/// Trigger synchronization for a specific account
|
||||
pub async fn trigger_start(&self, account_id: u64, email: String) {
|
||||
if let Err(e) = self.channel.send((account_id, email)).await {
|
||||
error!(
|
||||
"Failed to trigger synchronization for account={{{}}}, error: {:?}",
|
||||
account_id, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_syncer(account_id: u64, email: String) -> BichonResult<Option<()>> {
|
||||
info!(
|
||||
"Account syncer starting for account: {}-{}.",
|
||||
account_id, email
|
||||
);
|
||||
SYNC_TASKS.start_account_sync_task(account_id, email).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
|
||||
//
|
||||
// This file is part of the Bichon Email Archiving Project
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::context::Initialize;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::raise_error;
|
||||
use crate::{
|
||||
modules::{
|
||||
account::migration::AccountModel,
|
||||
context::controller::SYNC_CONTROLLER,
|
||||
error::BichonResult,
|
||||
imap::{executor::ImapExecutor, pool::build_imap_pool},
|
||||
},
|
||||
utc_now,
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use tracing::info;
|
||||
|
||||
pub static MAIL_CONTEXT: LazyLock<EmailClientExecutors> =
|
||||
LazyLock::new(EmailClientExecutors::new);
|
||||
|
||||
pub struct EmailClientExecutors {
|
||||
start_at: i64,
|
||||
imap: DashMap<u64, Arc<ImapExecutor>>,
|
||||
}
|
||||
|
||||
impl Initialize for EmailClientExecutors {
|
||||
async fn initialize() -> BichonResult<()> {
|
||||
MAIL_CONTEXT.start_account_syncers().await
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailClientExecutors {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
start_at: utc_now!(),
|
||||
imap: DashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn uptime_ms(&self) -> i64 {
|
||||
utc_now!() - self.start_at
|
||||
}
|
||||
|
||||
pub async fn imap(&self, account_id: u64) -> BichonResult<Arc<ImapExecutor>> {
|
||||
if let Some(executor) = self.imap.get(&account_id) {
|
||||
return Ok(executor.value().clone());
|
||||
}
|
||||
|
||||
let pool = build_imap_pool(account_id).await?;
|
||||
let new_executor = Arc::new(ImapExecutor::new(pool));
|
||||
|
||||
match self.imap.try_entry(account_id) {
|
||||
Some(dashmap::mapref::entry::Entry::Occupied(entry)) => Ok(entry.get().clone()),
|
||||
Some(dashmap::mapref::entry::Entry::Vacant(entry)) => {
|
||||
entry.insert(new_executor.clone());
|
||||
Ok(new_executor)
|
||||
}
|
||||
None => Err(raise_error!(
|
||||
"DashMap locked".into(),
|
||||
ErrorCode::InternalError
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clean_account(&self, account_id: u64) -> BichonResult<()> {
|
||||
if self.imap.remove(&account_id).is_some() {
|
||||
info!(account_id, "Closed IMAP pool for account");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_account_syncers(&self) -> BichonResult<()> {
|
||||
let accounts = AccountModel::list_all().await?;
|
||||
let active_accounts: Vec<AccountModel> =
|
||||
accounts.into_iter().filter(|a| a.enabled).collect();
|
||||
|
||||
if active_accounts.is_empty() {
|
||||
info!("No active accounts found for account initialization.");
|
||||
return Ok(());
|
||||
}
|
||||
info!(
|
||||
"System has {} active accounts to initialize.",
|
||||
active_accounts.len()
|
||||
);
|
||||
for account in active_accounts {
|
||||
SYNC_CONTROLLER
|
||||
.trigger_start(account.id, account.email)
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
|
||||
//
|
||||
// This file is part of the Bichon Email Archiving Project
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::error::BichonResult;
|
||||
|
||||
pub mod controller;
|
||||
pub mod executors;
|
||||
pub mod status;
|
||||
|
||||
pub trait Initialize {
|
||||
async fn initialize() -> BichonResult<()>;
|
||||
}
|
||||
|
||||
pub trait RustMailTask {
|
||||
fn start();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
|
||||
//
|
||||
// This file is part of the Bichon Email Archiving Project
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::context::executors::MAIL_CONTEXT;
|
||||
use chrono::Local;
|
||||
use poem_openapi::Object;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use timeago::Formatter;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Object)]
|
||||
pub struct BichonStatus {
|
||||
/// The service uptime in milliseconds since it started.
|
||||
pub uptime_ms: i64,
|
||||
/// A human-readable string indicating the time elapsed since the service started (e.g., "2 hours ago").
|
||||
pub timeago: String,
|
||||
/// The timezone in which the service is operating (e.g., "UTC" or "Asia/Tokyo").
|
||||
pub timezone: String,
|
||||
/// The version of the RustMailer service currently running.
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl BichonStatus {
|
||||
pub fn get() -> Self {
|
||||
Self {
|
||||
uptime_ms: MAIL_CONTEXT.uptime_ms(),
|
||||
timeago: Formatter::new()
|
||||
.convert(Duration::from_millis(MAIL_CONTEXT.uptime_ms() as u64)),
|
||||
timezone: Local::now().offset().to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user