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