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