1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use std::{cell::RefCell, fmt::Write, mem, sync::Arc}; 6 7 use atomic_refcell::AtomicRefCell; 8 use dogear::Store; 9 use log::LevelFilter; 10 use moz_task::{Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder}; 11 use nserror::{nsresult, NS_ERROR_NOT_AVAILABLE, NS_OK}; 12 use nsstring::nsString; 13 use storage::Conn; 14 use xpcom::{ 15 interfaces::{ 16 mozIPlacesPendingOperation, mozIServicesLogSink, mozIStorageConnection, 17 mozISyncedBookmarksMirrorCallback, mozISyncedBookmarksMirrorProgressListener, 18 }, 19 RefPtr, XpCom, 20 }; 21 22 use crate::driver::{AbortController, Driver, Logger}; 23 use crate::error; 24 use crate::store; 25 26 #[derive(xpcom)] 27 #[xpimplements(mozISyncedBookmarksMerger)] 28 #[refcnt = "nonatomic"] 29 pub struct InitSyncedBookmarksMerger { 30 db: RefCell<Option<Conn>>, 31 logger: RefCell<Option<RefPtr<mozIServicesLogSink>>>, 32 } 33 34 impl SyncedBookmarksMerger { new() -> RefPtr<SyncedBookmarksMerger>35 pub fn new() -> RefPtr<SyncedBookmarksMerger> { 36 SyncedBookmarksMerger::allocate(InitSyncedBookmarksMerger { 37 db: RefCell::default(), 38 logger: RefCell::default(), 39 }) 40 } 41 42 xpcom_method!(get_db => GetDb() -> *const mozIStorageConnection); get_db(&self) -> Result<RefPtr<mozIStorageConnection>, nsresult>43 fn get_db(&self) -> Result<RefPtr<mozIStorageConnection>, nsresult> { 44 self.db 45 .borrow() 46 .as_ref() 47 .map(|db| RefPtr::new(db.connection())) 48 .ok_or(NS_OK) 49 } 50 51 xpcom_method!(set_db => SetDb(connection: *const mozIStorageConnection)); set_db(&self, connection: Option<&mozIStorageConnection>) -> Result<(), nsresult>52 fn set_db(&self, connection: Option<&mozIStorageConnection>) -> Result<(), nsresult> { 53 self.db 54 .replace(connection.map(|connection| Conn::wrap(RefPtr::new(connection)))); 55 Ok(()) 56 } 57 58 xpcom_method!(get_logger => GetLogger() -> *const mozIServicesLogSink); get_logger(&self) -> Result<RefPtr<mozIServicesLogSink>, nsresult>59 fn get_logger(&self) -> Result<RefPtr<mozIServicesLogSink>, nsresult> { 60 match *self.logger.borrow() { 61 Some(ref logger) => Ok(logger.clone()), 62 None => Err(NS_OK), 63 } 64 } 65 66 xpcom_method!(set_logger => SetLogger(logger: *const mozIServicesLogSink)); set_logger(&self, logger: Option<&mozIServicesLogSink>) -> Result<(), nsresult>67 fn set_logger(&self, logger: Option<&mozIServicesLogSink>) -> Result<(), nsresult> { 68 self.logger.replace(logger.map(RefPtr::new)); 69 Ok(()) 70 } 71 72 xpcom_method!( 73 merge => Merge( 74 local_time_seconds: i64, 75 remote_time_seconds: i64, 76 callback: *const mozISyncedBookmarksMirrorCallback 77 ) -> *const mozIPlacesPendingOperation 78 ); merge( &self, local_time_seconds: i64, remote_time_seconds: i64, callback: &mozISyncedBookmarksMirrorCallback, ) -> Result<RefPtr<mozIPlacesPendingOperation>, nsresult>79 fn merge( 80 &self, 81 local_time_seconds: i64, 82 remote_time_seconds: i64, 83 callback: &mozISyncedBookmarksMirrorCallback, 84 ) -> Result<RefPtr<mozIPlacesPendingOperation>, nsresult> { 85 let callback = RefPtr::new(callback); 86 let db = match *self.db.borrow() { 87 Some(ref db) => db.clone(), 88 None => return Err(NS_ERROR_NOT_AVAILABLE), 89 }; 90 let logger = &*self.logger.borrow(); 91 let async_thread = db.thread()?; 92 let controller = Arc::new(AbortController::default()); 93 let task = MergeTask::new( 94 &db, 95 Arc::clone(&controller), 96 logger.as_ref().cloned(), 97 local_time_seconds, 98 remote_time_seconds, 99 callback, 100 )?; 101 let runnable = TaskRunnable::new( 102 "bookmark_sync::SyncedBookmarksMerger::merge", 103 Box::new(task), 104 )?; 105 TaskRunnable::dispatch(runnable, &async_thread)?; 106 let op = MergeOp::new(controller); 107 Ok(RefPtr::new(op.coerce())) 108 } 109 110 xpcom_method!(reset => Reset()); reset(&self) -> Result<(), nsresult>111 fn reset(&self) -> Result<(), nsresult> { 112 mem::drop(self.db.borrow_mut().take()); 113 mem::drop(self.logger.borrow_mut().take()); 114 Ok(()) 115 } 116 } 117 118 struct MergeTask { 119 db: Conn, 120 controller: Arc<AbortController>, 121 max_log_level: LevelFilter, 122 logger: Option<ThreadPtrHandle<mozIServicesLogSink>>, 123 local_time_millis: i64, 124 remote_time_millis: i64, 125 progress: Option<ThreadPtrHandle<mozISyncedBookmarksMirrorProgressListener>>, 126 callback: ThreadPtrHandle<mozISyncedBookmarksMirrorCallback>, 127 result: AtomicRefCell<error::Result<store::ApplyStatus>>, 128 } 129 130 impl MergeTask { new( db: &Conn, controller: Arc<AbortController>, logger: Option<RefPtr<mozIServicesLogSink>>, local_time_seconds: i64, remote_time_seconds: i64, callback: RefPtr<mozISyncedBookmarksMirrorCallback>, ) -> Result<MergeTask, nsresult>131 fn new( 132 db: &Conn, 133 controller: Arc<AbortController>, 134 logger: Option<RefPtr<mozIServicesLogSink>>, 135 local_time_seconds: i64, 136 remote_time_seconds: i64, 137 callback: RefPtr<mozISyncedBookmarksMirrorCallback>, 138 ) -> Result<MergeTask, nsresult> { 139 let max_log_level = logger 140 .as_ref() 141 .and_then(|logger| { 142 let mut level = 0i16; 143 unsafe { logger.GetMaxLevel(&mut level) }.to_result().ok()?; 144 Some(level) 145 }) 146 .map(|level| match level as i64 { 147 mozIServicesLogSink::LEVEL_ERROR => LevelFilter::Error, 148 mozIServicesLogSink::LEVEL_WARN => LevelFilter::Warn, 149 mozIServicesLogSink::LEVEL_DEBUG => LevelFilter::Debug, 150 mozIServicesLogSink::LEVEL_TRACE => LevelFilter::Trace, 151 _ => LevelFilter::Off, 152 }) 153 .unwrap_or(LevelFilter::Off); 154 let logger = match logger { 155 Some(logger) => Some(ThreadPtrHolder::new(cstr!("mozIServicesLogSink"), logger)?), 156 None => None, 157 }; 158 let progress = callback 159 .query_interface::<mozISyncedBookmarksMirrorProgressListener>() 160 .and_then(|p| { 161 ThreadPtrHolder::new(cstr!("mozISyncedBookmarksMirrorProgressListener"), p).ok() 162 }); 163 Ok(MergeTask { 164 db: db.clone(), 165 controller, 166 max_log_level, 167 logger, 168 local_time_millis: local_time_seconds * 1000, 169 remote_time_millis: remote_time_seconds * 1000, 170 progress, 171 callback: ThreadPtrHolder::new(cstr!("mozISyncedBookmarksMirrorCallback"), callback)?, 172 result: AtomicRefCell::new(Err(error::Error::DidNotRun)), 173 }) 174 } 175 merge(&self) -> error::Result<store::ApplyStatus>176 fn merge(&self) -> error::Result<store::ApplyStatus> { 177 let mut db = self.db.clone(); 178 if db.transaction_in_progress()? { 179 // If a transaction is already open, we can avoid an unnecessary 180 // merge, since we won't be able to apply the merged tree back to 181 // Places. This is common, especially if the user makes lots of 182 // changes at once. In that case, our merge task might run in the 183 // middle of a `Sqlite.jsm` transaction, and fail when we try to 184 // open our own transaction in `Store::apply`. Since the local 185 // tree might be in an inconsistent state, we can't safely update 186 // Places. 187 return Err(error::Error::StorageBusy); 188 } 189 let log = Logger::new(self.max_log_level, self.logger.clone()); 190 let driver = Driver::new(log, self.progress.clone()); 191 let mut store = store::Store::new( 192 &mut db, 193 &driver, 194 &self.controller, 195 self.local_time_millis, 196 self.remote_time_millis, 197 ); 198 store.validate()?; 199 store.prepare()?; 200 let status = store.merge_with_driver(&driver, &*self.controller)?; 201 Ok(status) 202 } 203 } 204 205 impl Task for MergeTask { run(&self)206 fn run(&self) { 207 *self.result.borrow_mut() = self.merge(); 208 } 209 done(&self) -> Result<(), nsresult>210 fn done(&self) -> Result<(), nsresult> { 211 let callback = self.callback.get().unwrap(); 212 match mem::replace(&mut *self.result.borrow_mut(), Err(error::Error::DidNotRun)) { 213 Ok(status) => unsafe { callback.HandleSuccess(status.into()) }, 214 Err(err) => { 215 let mut message = nsString::new(); 216 write!(message, "{}", err).unwrap(); 217 unsafe { callback.HandleError(err.into(), &*message) } 218 } 219 } 220 .to_result() 221 } 222 } 223 224 #[derive(xpcom)] 225 #[xpimplements(mozIPlacesPendingOperation)] 226 #[refcnt = "atomic"] 227 pub struct InitMergeOp { 228 controller: Arc<AbortController>, 229 } 230 231 impl MergeOp { new(controller: Arc<AbortController>) -> RefPtr<MergeOp>232 pub fn new(controller: Arc<AbortController>) -> RefPtr<MergeOp> { 233 MergeOp::allocate(InitMergeOp { controller }) 234 } 235 236 xpcom_method!(cancel => Cancel()); cancel(&self) -> Result<(), nsresult>237 fn cancel(&self) -> Result<(), nsresult> { 238 self.controller.abort(); 239 Ok(()) 240 } 241 } 242