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 crate::api::{self, StorageChanges};
6 use crate::db::StorageDb;
7 use crate::error::*;
8 use crate::migration::{migrate, MigrationInfo};
9 use crate::sync;
10 use std::path::Path;
11 use std::result;
12 
13 use serde_json::Value as JsonValue;
14 use sql_support::SqlInterruptHandle;
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 pub struct Store {
28     db: StorageDb,
29 }
30 
31 impl Store {
32     /// Creates a store backed by a database at `db_path`. The path can be a
33     /// file path or `file:` URI.
new(db_path: impl AsRef<Path>) -> Result<Self>34     pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
35         Ok(Self {
36             db: StorageDb::new(db_path)?,
37         })
38     }
39 
40     /// Creates a store backed by an in-memory database.
41     #[cfg(test)]
new_memory(db_path: &str) -> Result<Self>42     pub fn new_memory(db_path: &str) -> Result<Self> {
43         Ok(Self {
44             db: StorageDb::new_memory(db_path)?,
45         })
46     }
47 
48     /// Returns an interrupt handle for this store.
interrupt_handle(&self) -> SqlInterruptHandle49     pub fn interrupt_handle(&self) -> SqlInterruptHandle {
50         self.db.interrupt_handle()
51     }
52 
53     /// Sets one or more JSON key-value pairs for an extension ID. Returns a
54     /// list of changes, with existing and new values for each key in `val`.
set(&self, ext_id: &str, val: JsonValue) -> Result<StorageChanges>55     pub fn set(&self, ext_id: &str, val: JsonValue) -> Result<StorageChanges> {
56         let tx = self.db.unchecked_transaction()?;
57         let result = api::set(&tx, ext_id, val)?;
58         tx.commit()?;
59         Ok(result)
60     }
61 
62     /// Returns the values for one or more keys `keys` can be:
63     ///
64     /// - `null`, in which case all key-value pairs for the extension are
65     ///   returned, or an empty object if the extension doesn't have any
66     ///   stored data.
67     /// - A single string key, in which case an object with only that key
68     ///   and its value is returned, or an empty object if the key doesn't
69     //    exist.
70     /// - An array of string keys, in which case an object with only those
71     ///   keys and their values is returned. Any keys that don't exist will be
72     ///   omitted.
73     /// - An object where the property names are keys, and each value is the
74     ///   default value to return if the key doesn't exist.
75     ///
76     /// This method always returns an object (that is, a
77     /// `serde_json::Value::Object`).
get(&self, ext_id: &str, keys: JsonValue) -> Result<JsonValue>78     pub fn get(&self, ext_id: &str, keys: JsonValue) -> Result<JsonValue> {
79         // Don't care about transactions here.
80         api::get(&self.db, ext_id, keys)
81     }
82 
83     /// Deletes the values for one or more keys. As with `get`, `keys` can be
84     /// either a single string key, or an array of string keys. Returns a list
85     /// of changes, where each change contains the old value for each deleted
86     /// key.
remove(&self, ext_id: &str, keys: JsonValue) -> Result<StorageChanges>87     pub fn remove(&self, ext_id: &str, keys: JsonValue) -> Result<StorageChanges> {
88         let tx = self.db.unchecked_transaction()?;
89         let result = api::remove(&tx, ext_id, keys)?;
90         tx.commit()?;
91         Ok(result)
92     }
93 
94     /// Deletes all key-value pairs for the extension. As with `remove`, returns
95     /// a list of changes, where each change contains the old value for each
96     /// deleted key.
clear(&self, ext_id: &str) -> Result<StorageChanges>97     pub fn clear(&self, ext_id: &str) -> Result<StorageChanges> {
98         let tx = self.db.unchecked_transaction()?;
99         let result = api::clear(&tx, ext_id)?;
100         tx.commit()?;
101         Ok(result)
102     }
103 
104     /// Returns the bytes in use for the specified items (which can be null,
105     /// a string, or an array)
get_bytes_in_use(&self, ext_id: &str, keys: JsonValue) -> Result<usize>106     pub fn get_bytes_in_use(&self, ext_id: &str, keys: JsonValue) -> Result<usize> {
107         api::get_bytes_in_use(&self.db, ext_id, keys)
108     }
109 
110     /// Returns a bridged sync engine for Desktop for this store.
bridged_engine(&self) -> sync::BridgedEngine<'_>111     pub fn bridged_engine(&self) -> sync::BridgedEngine<'_> {
112         sync::BridgedEngine::new(&self.db)
113     }
114 
115     /// Closes the store and its database connection. See the docs for
116     /// `StorageDb::close` for more details on when this can fail.
close(self) -> result::Result<(), (Store, Error)>117     pub fn close(self) -> result::Result<(), (Store, Error)> {
118         self.db.close().map_err(|(db, err)| (Store { db }, err))
119     }
120 
121     /// Gets the changes which the current sync applied. Should be used
122     /// immediately after the bridged engine is told to apply incoming changes,
123     /// and can be used to notify observers of the StorageArea of the changes
124     /// that were applied.
125     /// The result is a Vec of already JSON stringified changes.
get_synced_changes(&self) -> Result<Vec<sync::SyncedExtensionChange>>126     pub fn get_synced_changes(&self) -> Result<Vec<sync::SyncedExtensionChange>> {
127         sync::get_synced_changes(&self.db)
128     }
129 
130     /// Migrates data from a database in the format of the "old" kinto
131     /// implementation. Information about how the migration went is stored in
132     /// the database, and can be read using `Self::take_migration_info`.
133     ///
134     /// Note that `filename` isn't normalized or canonicalized.
migrate(&self, filename: impl AsRef<Path>) -> Result<()>135     pub fn migrate(&self, filename: impl AsRef<Path>) -> Result<()> {
136         let tx = self.db.unchecked_transaction()?;
137         let result = migrate(&tx, filename.as_ref())?;
138         tx.commit()?;
139         // Failing to store this information should not cause migration failure.
140         if let Err(e) = result.store(&self.db) {
141             debug_assert!(false, "Migration error: {:?}", e);
142             log::warn!("Failed to record migration telmetry: {}", e);
143         }
144         Ok(())
145     }
146 
147     /// Read-and-delete (e.g. `take` in rust parlance, see Option::take)
148     /// operation for any MigrationInfo stored in this database.
take_migration_info(&self) -> Result<Option<MigrationInfo>>149     pub fn take_migration_info(&self) -> Result<Option<MigrationInfo>> {
150         let tx = self.db.unchecked_transaction()?;
151         let result = MigrationInfo::take(&tx)?;
152         tx.commit()?;
153         Ok(result)
154     }
155 }
156 
157 #[cfg(test)]
158 pub mod test {
159     use super::*;
160     #[test]
test_send()161     fn test_send() {
162         fn ensure_send<T: Send>() {}
163         // Compile will fail if not send.
164         ensure_send::<Store>();
165     }
166 
new_mem_store() -> Store167     pub fn new_mem_store() -> Store {
168         Store {
169             db: crate::db::test::new_mem_db(),
170         }
171     }
172 }
173