Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / third_party / rust / webext-storage / src / store.rs
blobd9c3281dd43d8e64bece4bfc198e42c2cfe23701
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/. */
5 use crate::api::{self, StorageChanges};
6 use crate::db::{StorageDb, ThreadSafeStorageDb};
7 use crate::error::*;
8 use crate::migration::{migrate, MigrationInfo};
9 use crate::sync;
10 use std::path::Path;
11 use std::sync::Arc;
13 use interrupt_support::SqlInterruptHandle;
14 use serde_json::Value as JsonValue;
16 /// A store is used to access `storage.sync` data. It manages an underlying
17 /// database connection, and exposes methods for reading and writing storage
18 /// items scoped to an extension ID. Each item is a JSON object, with one or
19 /// more string keys, and values of any type that can serialize to JSON.
20 ///
21 /// An application should create only one store, and manage the instance as a
22 /// singleton. While this isn't enforced, if you make multiple stores pointing
23 /// to the same database file, you are going to have a bad time: each store will
24 /// create its own database connection, using up extra memory and CPU cycles,
25 /// and causing write contention. For this reason, you should only call
26 /// `Store::new()` (or `webext_store_new()`, from the FFI) once.
27 ///
28 /// Note that our Db implementation is behind an Arc<> because we share that
29 /// connection with our sync engines - ie, these engines also hold an Arc<>
30 /// around the same object.
31 pub struct WebExtStorageStore {
32     db: Arc<ThreadSafeStorageDb>,
35 impl WebExtStorageStore {
36     /// Creates a store backed by a database at `db_path`. The path can be a
37     /// file path or `file:` URI.
38     pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
39         let db = StorageDb::new(db_path)?;
40         Ok(Self {
41             db: Arc::new(ThreadSafeStorageDb::new(db)),
42         })
43     }
45     /// Creates a store backed by an in-memory database.
46     #[cfg(test)]
47     pub fn new_memory(db_path: &str) -> Result<Self> {
48         let db = StorageDb::new_memory(db_path)?;
49         Ok(Self {
50             db: Arc::new(ThreadSafeStorageDb::new(db)),
51         })
52     }
54     /// Returns an interrupt handle for this store.
55     pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
56         self.db.interrupt_handle()
57     }
59     /// Sets one or more JSON key-value pairs for an extension ID. Returns a
60     /// list of changes, with existing and new values for each key in `val`.
61     pub fn set(&self, ext_id: &str, val: JsonValue) -> Result<StorageChanges> {
62         let db = self.db.lock();
63         let tx = db.unchecked_transaction()?;
64         let result = api::set(&tx, ext_id, val)?;
65         tx.commit()?;
66         Ok(result)
67     }
69     /// Returns information about per-extension usage
70     pub fn usage(&self) -> Result<Vec<crate::UsageInfo>> {
71         let db = self.db.lock();
72         api::usage(&db)
73     }
75     /// Returns the values for one or more keys `keys` can be:
76     ///
77     /// - `null`, in which case all key-value pairs for the extension are
78     ///   returned, or an empty object if the extension doesn't have any
79     ///   stored data.
80     /// - A single string key, in which case an object with only that key
81     ///   and its value is returned, or an empty object if the key doesn't
82     //    exist.
83     /// - An array of string keys, in which case an object with only those
84     ///   keys and their values is returned. Any keys that don't exist will be
85     ///   omitted.
86     /// - An object where the property names are keys, and each value is the
87     ///   default value to return if the key doesn't exist.
88     ///
89     /// This method always returns an object (that is, a
90     /// `serde_json::Value::Object`).
91     pub fn get(&self, ext_id: &str, keys: JsonValue) -> Result<JsonValue> {
92         // Don't care about transactions here.
93         let db = self.db.lock();
94         api::get(&db, ext_id, keys)
95     }
97     /// Deletes the values for one or more keys. As with `get`, `keys` can be
98     /// either a single string key, or an array of string keys. Returns a list
99     /// of changes, where each change contains the old value for each deleted
100     /// key.
101     pub fn remove(&self, ext_id: &str, keys: JsonValue) -> Result<StorageChanges> {
102         let db = self.db.lock();
103         let tx = db.unchecked_transaction()?;
104         let result = api::remove(&tx, ext_id, keys)?;
105         tx.commit()?;
106         Ok(result)
107     }
109     /// Deletes all key-value pairs for the extension. As with `remove`, returns
110     /// a list of changes, where each change contains the old value for each
111     /// deleted key.
112     pub fn clear(&self, ext_id: &str) -> Result<StorageChanges> {
113         let db = self.db.lock();
114         let tx = db.unchecked_transaction()?;
115         let result = api::clear(&tx, ext_id)?;
116         tx.commit()?;
117         Ok(result)
118     }
120     /// Returns the bytes in use for the specified items (which can be null,
121     /// a string, or an array)
122     pub fn get_bytes_in_use(&self, ext_id: &str, keys: JsonValue) -> Result<usize> {
123         let db = self.db.lock();
124         api::get_bytes_in_use(&db, ext_id, keys)
125     }
127     /// Returns a bridged sync engine for Desktop for this store.
128     pub fn bridged_engine(&self) -> sync::BridgedEngine {
129         sync::BridgedEngine::new(&self.db)
130     }
132     /// Closes the store and its database connection. See the docs for
133     /// `StorageDb::close` for more details on when this can fail.
134     pub fn close(self) -> Result<()> {
135         // Even though this consumes `self`, the fact we use an Arc<> means
136         // we can't guarantee we can actually consume the inner DB - so do
137         // the best we can.
138         let shared: ThreadSafeStorageDb = match Arc::try_unwrap(self.db) {
139             Ok(shared) => shared,
140             _ => {
141                 // The only way this is possible is if the sync engine has an operation
142                 // running - but that shouldn't be possible in practice because desktop
143                 // uses a single "task queue" such that the close operation can't possibly
144                 // be running concurrently with any sync or storage tasks.
146                 // If this *could* get hit, rusqlite will attempt to close the DB connection
147                 // as it is dropped, and if that close fails, then rusqlite 0.28.0 and earlier
148                 // would panic - but even that only happens if prepared statements are
149                 // not finalized, which ruqlite also does.
151                 // tl;dr - this should be impossible. If it was possible, rusqlite might panic,
152                 // but we've never seen it panic in practice other places we don't close
153                 // connections, and the next rusqlite version will not panic anyway.
154                 // So this-is-fine.jpg
155                 log::warn!("Attempting to close a store while other DB references exist.");
156                 return Err(Error::OtherConnectionReferencesExist);
157             }
158         };
159         // consume the mutex and get back the inner.
160         let db = shared.into_inner();
161         db.close()
162     }
164     /// Gets the changes which the current sync applied. Should be used
165     /// immediately after the bridged engine is told to apply incoming changes,
166     /// and can be used to notify observers of the StorageArea of the changes
167     /// that were applied.
168     /// The result is a Vec of already JSON stringified changes.
169     pub fn get_synced_changes(&self) -> Result<Vec<sync::SyncedExtensionChange>> {
170         let db = self.db.lock();
171         sync::get_synced_changes(&db)
172     }
174     /// Migrates data from a database in the format of the "old" kinto
175     /// implementation. Information about how the migration went is stored in
176     /// the database, and can be read using `Self::take_migration_info`.
177     ///
178     /// Note that `filename` isn't normalized or canonicalized.
179     pub fn migrate(&self, filename: impl AsRef<Path>) -> Result<()> {
180         let db = self.db.lock();
181         let tx = db.unchecked_transaction()?;
182         let result = migrate(&tx, filename.as_ref())?;
183         tx.commit()?;
184         // Failing to store this information should not cause migration failure.
185         if let Err(e) = result.store(&db) {
186             debug_assert!(false, "Migration error: {:?}", e);
187             log::warn!("Failed to record migration telmetry: {}", e);
188         }
189         Ok(())
190     }
192     /// Read-and-delete (e.g. `take` in rust parlance, see Option::take)
193     /// operation for any MigrationInfo stored in this database.
194     pub fn take_migration_info(&self) -> Result<Option<MigrationInfo>> {
195         let db = self.db.lock();
196         let tx = db.unchecked_transaction()?;
197         let result = MigrationInfo::take(&tx)?;
198         tx.commit()?;
199         Ok(result)
200     }
203 #[cfg(test)]
204 pub mod test {
205     use super::*;
206     #[test]
207     fn test_send() {
208         fn ensure_send<T: Send>() {}
209         // Compile will fail if not send.
210         ensure_send::<WebExtStorageStore>();
211     }
213     pub fn new_mem_store() -> WebExtStorageStore {
214         WebExtStorageStore {
215             db: Arc::new(ThreadSafeStorageDb::new(crate::db::test::new_mem_db())),
216         }
217     }