feat: add background sync for Matrix client on mobile platforms
Some checks failed
Some checks failed
- Introduced background sync functionality for the Matrix client, allowing notifications to be received even when the app is backgrounded. - Added new commands to start, stop, and get the state of background sync. - Implemented a BackgroundSyncManager to manage sync state and credentials. - Integrated matrix-sdk for handling Matrix interactions and notifications. - Updated Cargo.toml to include necessary dependencies for background sync. - Modified mobile capabilities to include opener permissions. - Enhanced lib.rs to handle new commands and integrate background sync logic.
This commit is contained in:
2
cinny
2
cinny
Submodule cinny updated: 1a452f52ca...4ca4af0e8b
1903
src-tauri/Cargo.lock
generated
1903
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
tauri = { version = "2", features = ["devtools", "tray-icon", "image-png"] }
|
tauri = { version = "2", features = ["devtools", "tray-icon", "image-png"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
[target."cfg(target_os = \"linux\")".dependencies]
|
[target."cfg(target_os = \"linux\")".dependencies]
|
||||||
arboard = { version = "3", features = ["wayland-data-control"] }
|
arboard = { version = "3", features = ["wayland-data-control"] }
|
||||||
@@ -35,6 +36,10 @@ gtk = "0.18"
|
|||||||
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
|
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
|
||||||
tauri-plugin-deep-link = "2"
|
tauri-plugin-deep-link = "2"
|
||||||
|
|
||||||
|
# Matrix SDK for native background sync (no sqlite on mobile - use in-memory store)
|
||||||
|
matrix-sdk = { version = "0.7", default-features = false, features = ["rustls-tls", "e2e-encryption"] }
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
|
||||||
|
|
||||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||||
tauri-plugin-localhost = "2"
|
tauri-plugin-localhost = "2"
|
||||||
tauri-plugin-window-state = "2"
|
tauri-plugin-window-state = "2"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"notification:default",
|
"notification:default",
|
||||||
"deep-link:default"
|
"deep-link:default",
|
||||||
|
"opener:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"default":{"identifier":"default","description":"Default capability for all windows","local":true,"windows":["*"],"permissions":["core:default","opener:default","opener:allow-open-url","opener:allow-default-urls","notification:default","autostart:default"],"platforms":["linux","windows","macOS"]},"mobile":{"identifier":"mobile","description":"Default capability for mobile","local":true,"windows":["*"],"permissions":["core:default","notification:default","deep-link:default"],"platforms":["android","iOS"]}}
|
{"default":{"identifier":"default","description":"Default capability for all windows","local":true,"windows":["*"],"permissions":["core:default","opener:default","opener:allow-open-url","opener:allow-default-urls","notification:default","autostart:default"],"platforms":["linux","windows","macOS"]},"mobile":{"identifier":"mobile","description":"Default capability for mobile","local":true,"windows":["*"],"permissions":["core:default","notification:default","deep-link:default","opener:default"],"platforms":["android","iOS"]}}
|
||||||
133
src-tauri/src/background_sync.rs
Normal file
133
src-tauri/src/background_sync.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
//! Background sync module for Matrix client
|
||||||
|
//!
|
||||||
|
//! This module provides native Rust-based Matrix sync that runs independently
|
||||||
|
//! of the WebView, allowing notifications to work even when the app is backgrounded.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Credentials needed to connect to Matrix homeserver
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MatrixCredentials {
|
||||||
|
pub homeserver_url: String,
|
||||||
|
pub user_id: String,
|
||||||
|
pub access_token: String,
|
||||||
|
pub device_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State of the background sync
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SyncState {
|
||||||
|
Stopped,
|
||||||
|
Starting,
|
||||||
|
Running,
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Background sync manager that handles Matrix sync in native Rust
|
||||||
|
pub struct BackgroundSyncManager {
|
||||||
|
credentials: RwLock<Option<MatrixCredentials>>,
|
||||||
|
sync_state: RwLock<SyncState>,
|
||||||
|
stop_flag: Mutex<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackgroundSyncManager {
|
||||||
|
/// Creates a new BackgroundSyncManager
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
credentials: RwLock::new(None),
|
||||||
|
sync_state: RwLock::new(SyncState::Stopped),
|
||||||
|
stop_flag: Mutex::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the Matrix credentials for syncing
|
||||||
|
pub async fn set_credentials(&self, credentials: MatrixCredentials) {
|
||||||
|
let mut creds = self.credentials.write().await;
|
||||||
|
*creds = Some(credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the stored credentials
|
||||||
|
pub async fn clear_credentials(&self) {
|
||||||
|
let mut creds = self.credentials.write().await;
|
||||||
|
*creds = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current sync state
|
||||||
|
pub async fn get_state(&self) -> SyncState {
|
||||||
|
self.sync_state.read().await.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts the background sync
|
||||||
|
pub async fn start_sync(&self) -> Result<(), String> {
|
||||||
|
// Check if we have credentials
|
||||||
|
let creds = self.credentials.read().await;
|
||||||
|
let credentials = creds.as_ref().ok_or("No credentials set")?;
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
{
|
||||||
|
let mut state = self.sync_state.write().await;
|
||||||
|
*state = SyncState::Starting;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset stop flag
|
||||||
|
{
|
||||||
|
let mut stop = self.stop_flag.lock().await;
|
||||||
|
*stop = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Background sync starting for user: {}",
|
||||||
|
credentials.user_id
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update state to running
|
||||||
|
{
|
||||||
|
let mut state = self.sync_state.write().await;
|
||||||
|
*state = SyncState::Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the background sync
|
||||||
|
pub async fn stop_sync(&self) {
|
||||||
|
{
|
||||||
|
let mut stop = self.stop_flag.lock().await;
|
||||||
|
*stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = self.sync_state.write().await;
|
||||||
|
*state = SyncState::Stopped;
|
||||||
|
|
||||||
|
log::info!("Background sync stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if sync should stop
|
||||||
|
pub async fn should_stop(&self) -> bool {
|
||||||
|
*self.stop_flag.lock().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the sync state to an error
|
||||||
|
pub async fn set_error(&self, error: String) {
|
||||||
|
let mut state = self.sync_state.write().await;
|
||||||
|
*state = SyncState::Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BackgroundSyncManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Global instance of the background sync manager
|
||||||
|
static SYNC_MANAGER: std::sync::OnceLock<Arc<BackgroundSyncManager>> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
/// Gets or creates the global sync manager instance
|
||||||
|
pub fn get_sync_manager() -> Arc<BackgroundSyncManager> {
|
||||||
|
SYNC_MANAGER
|
||||||
|
.get_or_init(|| Arc::new(BackgroundSyncManager::new()))
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
#[cfg(mobile)]
|
#[cfg(mobile)]
|
||||||
mod mobile;
|
mod mobile;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
mod background_sync;
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
mod matrix_sync;
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
use tauri::{
|
use tauri::{
|
||||||
Manager,
|
Manager,
|
||||||
@@ -64,6 +69,96 @@ fn open_external_url(url: String) -> Result<(), String> {
|
|||||||
open::that(&url).map_err(|e| e.to_string())
|
open::that(&url).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open a URL in the default browser on mobile platforms
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
#[tauri::command]
|
||||||
|
fn open_external_url(url: String) -> Result<(), String> {
|
||||||
|
tauri_plugin_opener::open_url(&url, None::<&str>).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start background Matrix sync with the given credentials (mobile only)
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
#[tauri::command]
|
||||||
|
async fn start_background_sync(
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
|
homeserver_url: String,
|
||||||
|
user_id: String,
|
||||||
|
access_token: String,
|
||||||
|
device_id: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
use crate::background_sync::{MatrixCredentials, get_sync_manager};
|
||||||
|
use crate::matrix_sync::{init_client, run_sync_loop};
|
||||||
|
|
||||||
|
let credentials = MatrixCredentials {
|
||||||
|
homeserver_url,
|
||||||
|
user_id,
|
||||||
|
access_token,
|
||||||
|
device_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set credentials
|
||||||
|
let manager = get_sync_manager();
|
||||||
|
manager.set_credentials(credentials.clone()).await;
|
||||||
|
|
||||||
|
// Initialize the Matrix client
|
||||||
|
init_client(&credentials).await?;
|
||||||
|
|
||||||
|
// Start sync in background task
|
||||||
|
let app = app_handle.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
if let Err(e) = run_sync_loop(app).await {
|
||||||
|
log::error!("Sync loop error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop background Matrix sync (mobile only)
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
#[tauri::command]
|
||||||
|
async fn stop_background_sync() -> Result<(), String> {
|
||||||
|
crate::matrix_sync::stop_sync().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get background sync state (mobile only)
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_background_sync_state() -> Result<String, String> {
|
||||||
|
use crate::background_sync::get_sync_manager;
|
||||||
|
|
||||||
|
let manager = get_sync_manager();
|
||||||
|
let state = manager.get_state().await;
|
||||||
|
|
||||||
|
Ok(format!("{:?}", state))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stub commands for desktop (no-op since background sync is mobile-only)
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
#[tauri::command]
|
||||||
|
async fn start_background_sync(
|
||||||
|
_app_handle: tauri::AppHandle,
|
||||||
|
_homeserver_url: String,
|
||||||
|
_user_id: String,
|
||||||
|
_access_token: String,
|
||||||
|
_device_id: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
Ok(()) // Desktop doesn't need background sync
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
#[tauri::command]
|
||||||
|
async fn stop_background_sync() -> Result<(), String> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_background_sync_state() -> Result<String, String> {
|
||||||
|
Ok("NotApplicable".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs the Tauri application
|
/// Runs the Tauri application
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
@@ -199,7 +294,13 @@ pub fn run() {
|
|||||||
api.prevent_close();
|
api.prevent_close();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![read_clipboard_image, open_external_url])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
read_clipboard_image,
|
||||||
|
open_external_url,
|
||||||
|
start_background_sync,
|
||||||
|
stop_background_sync,
|
||||||
|
get_background_sync_state
|
||||||
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while building tauri application");
|
.expect("error while building tauri application");
|
||||||
}
|
}
|
||||||
@@ -237,7 +338,13 @@ pub fn run() {
|
|||||||
.build()?;
|
.build()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![read_clipboard_image])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
read_clipboard_image,
|
||||||
|
open_external_url,
|
||||||
|
start_background_sync,
|
||||||
|
stop_background_sync,
|
||||||
|
get_background_sync_state
|
||||||
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while building tauri application");
|
.expect("error while building tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
174
src-tauri/src/matrix_sync.rs
Normal file
174
src-tauri/src/matrix_sync.rs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
//! Matrix sync implementation using matrix-sdk
|
||||||
|
//!
|
||||||
|
//! This module handles the actual Matrix sync loop and notification triggering.
|
||||||
|
|
||||||
|
use matrix_sdk::{
|
||||||
|
Client,
|
||||||
|
config::SyncSettings,
|
||||||
|
matrix_auth::MatrixSession,
|
||||||
|
ruma::{
|
||||||
|
events::room::message::{MessageType, SyncRoomMessageEvent},
|
||||||
|
OwnedUserId, OwnedDeviceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tauri::AppHandle;
|
||||||
|
use tauri_plugin_notification::NotificationExt;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::background_sync::{MatrixCredentials, get_sync_manager};
|
||||||
|
|
||||||
|
/// Active Matrix client for background sync
|
||||||
|
static MATRIX_CLIENT: std::sync::OnceLock<RwLock<Option<Client>>> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
fn get_client_lock() -> &'static RwLock<Option<Client>> {
|
||||||
|
MATRIX_CLIENT.get_or_init(|| RwLock::new(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the Matrix client with the given credentials
|
||||||
|
pub async fn init_client(credentials: &MatrixCredentials) -> Result<Client, String> {
|
||||||
|
// Pass the URL string directly - ClientBuilder::homeserver_url accepts impl AsRef<str>
|
||||||
|
let client = Client::builder()
|
||||||
|
.homeserver_url(&credentials.homeserver_url)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to build client: {}", e))?;
|
||||||
|
|
||||||
|
// Restore the session
|
||||||
|
let user_id: OwnedUserId = credentials.user_id.parse()
|
||||||
|
.map_err(|e| format!("Invalid user ID: {}", e))?;
|
||||||
|
|
||||||
|
let device_id: OwnedDeviceId = credentials.device_id.clone().into();
|
||||||
|
|
||||||
|
let session = MatrixSession {
|
||||||
|
meta: matrix_sdk::SessionMeta {
|
||||||
|
user_id,
|
||||||
|
device_id,
|
||||||
|
},
|
||||||
|
tokens: matrix_sdk::matrix_auth::MatrixSessionTokens {
|
||||||
|
access_token: credentials.access_token.clone(),
|
||||||
|
refresh_token: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
client.matrix_auth().restore_session(session).await
|
||||||
|
.map_err(|e| format!("Failed to restore session: {}", e))?;
|
||||||
|
|
||||||
|
// Store the client
|
||||||
|
{
|
||||||
|
let mut lock = get_client_lock().write().await;
|
||||||
|
*lock = Some(client.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the sync loop and triggers notifications for new messages
|
||||||
|
pub async fn run_sync_loop<R: tauri::Runtime>(app_handle: AppHandle<R>) -> Result<(), String> {
|
||||||
|
let manager = get_sync_manager();
|
||||||
|
|
||||||
|
let client = {
|
||||||
|
let lock = get_client_lock().read().await;
|
||||||
|
lock.clone().ok_or("Client not initialized")?
|
||||||
|
};
|
||||||
|
|
||||||
|
let own_user_id = client.user_id()
|
||||||
|
.ok_or("Not logged in")?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
log::info!("Starting sync loop for {}", own_user_id);
|
||||||
|
|
||||||
|
// Set up event handler for room messages
|
||||||
|
let app_handle_clone = app_handle.clone();
|
||||||
|
let own_user_clone = own_user_id.clone();
|
||||||
|
|
||||||
|
client.add_event_handler(move |event: SyncRoomMessageEvent, room: matrix_sdk::Room| {
|
||||||
|
let app = app_handle_clone.clone();
|
||||||
|
let own_user = own_user_clone.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
// Only process original messages (not edits/reactions)
|
||||||
|
if let SyncRoomMessageEvent::Original(original) = event {
|
||||||
|
// Don't notify for our own messages
|
||||||
|
if original.sender == own_user {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get room name for notification
|
||||||
|
let room_name = room.display_name().await
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.unwrap_or_else(|_| "Unknown room".to_string());
|
||||||
|
|
||||||
|
// Get sender display name
|
||||||
|
let sender_name = room.get_member(&original.sender).await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|m| m.display_name().map(|s| s.to_string()))
|
||||||
|
.unwrap_or_else(|| original.sender.to_string());
|
||||||
|
|
||||||
|
// Get message body
|
||||||
|
let body = match &original.content.msgtype {
|
||||||
|
MessageType::Text(text) => text.body.clone(),
|
||||||
|
MessageType::Image(_) => "📷 Image".to_string(),
|
||||||
|
MessageType::Video(_) => "🎥 Video".to_string(),
|
||||||
|
MessageType::Audio(_) => "🎵 Audio".to_string(),
|
||||||
|
MessageType::File(_) => "📎 File".to_string(),
|
||||||
|
MessageType::Location(_) => "📍 Location".to_string(),
|
||||||
|
MessageType::Emote(emote) => format!("* {} {}", sender_name, emote.body),
|
||||||
|
_ => "New message".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send notification
|
||||||
|
let title = format!("{} in {}", sender_name, room_name);
|
||||||
|
|
||||||
|
if let Err(e) = app.notification()
|
||||||
|
.builder()
|
||||||
|
.title(&title)
|
||||||
|
.body(&body)
|
||||||
|
.show()
|
||||||
|
{
|
||||||
|
log::error!("Failed to show notification: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run the sync loop
|
||||||
|
let settings = SyncSettings::default();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Check if we should stop
|
||||||
|
if manager.should_stop().await {
|
||||||
|
log::info!("Sync loop stopping due to stop flag");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform one sync iteration
|
||||||
|
match client.sync_once(settings.clone()).await {
|
||||||
|
Ok(response) => {
|
||||||
|
log::debug!("Sync completed, next_batch: {}", response.next_batch);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Sync error: {}", e);
|
||||||
|
// Update state to error
|
||||||
|
manager.set_error(e.to_string()).await;
|
||||||
|
// Wait a bit before retrying
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay between syncs to avoid hammering the server
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the sync and cleans up
|
||||||
|
pub async fn stop_sync() {
|
||||||
|
let manager = get_sync_manager();
|
||||||
|
manager.stop_sync().await;
|
||||||
|
|
||||||
|
// Clear the client
|
||||||
|
let mut lock = get_client_lock().write().await;
|
||||||
|
*lock = None;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user