webext_storage/
store.rs

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
5use crate::api::{self, StorageChanges};
6use crate::db::{StorageDb, ThreadSafeStorageDb};
7use crate::error::*;
8use crate::migration::{migrate, MigrationInfo};
9use crate::sync;
10use std::path::Path;
11use std::sync::Arc;
12
13use interrupt_support::SqlInterruptHandle;
14use serde_json::Value as JsonValue;
15
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.
31pub struct WebExtStorageStore {
32    pub(crate) db: Arc<ThreadSafeStorageDb>,
33}
34
35impl 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    }
44
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    }
53
54    /// Returns an interrupt handle for this store.
55    pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
56        self.db.interrupt_handle()
57    }
58
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 conn = db.get_connection()?;
64        let tx = conn.unchecked_transaction()?;
65        let result = api::set(&tx, ext_id, val)?;
66        tx.commit()?;
67        Ok(result)
68    }
69
70    /// Returns information about per-extension usage
71    pub fn usage(&self) -> Result<Vec<crate::UsageInfo>> {
72        let db = &self.db.lock();
73        let conn = db.get_connection()?;
74        api::usage(conn)
75    }
76
77    /// Returns the values for one or more keys `keys` can be:
78    ///
79    /// - `null`, in which case all key-value pairs for the extension are
80    ///   returned, or an empty object if the extension doesn't have any
81    ///   stored data.
82    /// - A single string key, in which case an object with only that key
83    ///   and its value is returned, or an empty object if the key doesn't
84    //    exist.
85    /// - An array of string keys, in which case an object with only those
86    ///   keys and their values is returned. Any keys that don't exist will be
87    ///   omitted.
88    /// - An object where the property names are keys, and each value is the
89    ///   default value to return if the key doesn't exist.
90    ///
91    /// This method always returns an object (that is, a
92    /// `serde_json::Value::Object`).
93    pub fn get(&self, ext_id: &str, keys: JsonValue) -> Result<JsonValue> {
94        // Don't care about transactions here.
95        let db = &self.db.lock();
96        let conn = db.get_connection()?;
97        api::get(conn, ext_id, keys)
98    }
99
100    /// Returns the keys for a given extension ID.
101    pub fn get_keys(&self, ext_id: &str) -> Result<JsonValue> {
102        let db = &self.db.lock();
103        let conn = db.get_connection()?;
104        api::get_keys(conn, ext_id)
105    }
106
107    /// Deletes the values for one or more keys. As with `get`, `keys` can be
108    /// either a single string key, or an array of string keys. Returns a list
109    /// of changes, where each change contains the old value for each deleted
110    /// key.
111    pub fn remove(&self, ext_id: &str, keys: JsonValue) -> Result<StorageChanges> {
112        let db = &self.db.lock();
113        let conn = db.get_connection()?;
114        let tx = conn.unchecked_transaction()?;
115        let result = api::remove(&tx, ext_id, keys)?;
116        tx.commit()?;
117        Ok(result)
118    }
119
120    /// Deletes all key-value pairs for the extension. As with `remove`, returns
121    /// a list of changes, where each change contains the old value for each
122    /// deleted key.
123    pub fn clear(&self, ext_id: &str) -> Result<StorageChanges> {
124        let db = &self.db.lock();
125        let conn = db.get_connection()?;
126        let tx = conn.unchecked_transaction()?;
127        let result = api::clear(&tx, ext_id)?;
128        tx.commit()?;
129        Ok(result)
130    }
131
132    /// Returns the bytes in use for the specified items (which can be null,
133    /// a string, or an array)
134    pub fn get_bytes_in_use(&self, ext_id: &str, keys: JsonValue) -> Result<u64> {
135        let db = &self.db.lock();
136        let conn = db.get_connection()?;
137        Ok(api::get_bytes_in_use(conn, ext_id, keys)? as u64)
138    }
139
140    /// Closes the store and its database connection. See the docs for
141    /// `StorageDb::close` for more details on when this can fail.
142    pub fn close(&self) -> Result<()> {
143        let mut db = self.db.lock();
144        db.close()
145    }
146
147    /// Gets the changes which the current sync applied. Should be used
148    /// immediately after the bridged engine is told to apply incoming changes,
149    /// and can be used to notify observers of the StorageArea of the changes
150    /// that were applied.
151    /// The result is a Vec of already JSON stringified changes.
152    pub fn get_synced_changes(&self) -> Result<Vec<sync::SyncedExtensionChange>> {
153        let db = self.db.lock();
154        sync::get_synced_changes(&db)
155    }
156
157    /// Migrates data from a database in the format of the "old" kinto
158    /// implementation. Information about how the migration went is stored in
159    /// the database, and can be read using `Self::take_migration_info`.
160    ///
161    /// Note that `filename` isn't normalized or canonicalized.
162    pub fn migrate(&self, filename: impl AsRef<Path>) -> Result<()> {
163        let db = &self.db.lock();
164        let conn = db.get_connection()?;
165        let tx = conn.unchecked_transaction()?;
166        let result = migrate(&tx, filename.as_ref())?;
167        tx.commit()?;
168        // Failing to store this information should not cause migration failure.
169        if let Err(e) = result.store(conn) {
170            debug_assert!(false, "Migration error: {:?}", e);
171            warn!("Failed to record migration telmetry: {}", e);
172        }
173        Ok(())
174    }
175
176    /// Read-and-delete (e.g. `take` in rust parlance, see Option::take)
177    /// operation for any MigrationInfo stored in this database.
178    pub fn take_migration_info(&self) -> Result<Option<MigrationInfo>> {
179        let db = &self.db.lock();
180        let conn = db.get_connection()?;
181        let tx = conn.unchecked_transaction()?;
182        let result = MigrationInfo::take(&tx)?;
183        tx.commit()?;
184        Ok(result)
185    }
186}
187
188#[cfg(test)]
189pub mod test {
190    use super::*;
191    #[test]
192    fn test_send() {
193        fn ensure_send<T: Send>() {}
194        // Compile will fail if not send.
195        ensure_send::<WebExtStorageStore>();
196    }
197
198    pub fn new_mem_store() -> WebExtStorageStore {
199        WebExtStorageStore {
200            db: Arc::new(ThreadSafeStorageDb::new(crate::db::test::new_mem_db())),
201        }
202    }
203}