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