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