1use std::fmt::Debug;
5use std::path::PathBuf;
6use std::thread;
7
8use rusqlite::{Connection, Transaction};
9use servo_base::generic_channel::{self, GenericReceiver, GenericSender};
10use servo_base::id::{BrowsingContextId, WebViewId};
11use servo_url::ImmutableOrigin;
12use storage_traits::client_storage::{
13 ClientStorageErrorr, ClientStorageThreadHandle, ClientStorageThreadMessage, Mode,
14 StorageIdentifier, StorageProxyMap, StorageType,
15};
16use uuid::Uuid;
17
18trait RegistryEngine {
19 type Error: Debug;
20 fn create_database(
21 &mut self,
22 bottle_id: i64,
23 name: String,
24 ) -> Result<PathBuf, ClientStorageErrorr<Self::Error>>;
25 fn delete_database(
26 &mut self,
27 bottle_id: i64,
28 name: String,
29 ) -> Result<(), ClientStorageErrorr<Self::Error>>;
30 fn obtain_a_storage_bottle_map(
31 &mut self,
32 storage_type: StorageType,
33 webview: WebViewId,
34 storage_identifier: StorageIdentifier,
35 origin: ImmutableOrigin,
36 sender: &GenericSender<ClientStorageThreadMessage>,
37 ) -> Result<StorageProxyMap, ClientStorageErrorr<Self::Error>>;
38}
39
40struct SqliteEngine {
41 connection: Connection,
42 base_dir: PathBuf,
43}
44
45impl SqliteEngine {
46 fn new(base_dir: PathBuf) -> rusqlite::Result<Self> {
47 let db_path = base_dir.join("reg.sqlite");
48 let connection = Connection::open(db_path)?;
49 Self::init(&connection)?;
50 Ok(SqliteEngine {
51 connection,
52 base_dir,
53 })
54 }
55
56 fn init(connection: &Connection) -> rusqlite::Result<()> {
57 connection.execute(r#"PRAGMA foreign_keys = ON;"#, [])?;
58 connection.execute(
59 r#"CREATE TABLE IF NOT EXISTS sheds (
60 id INTEGER PRIMARY KEY,
61 storage_type TEXT NOT NULL,
62 browsing_context TEXT
63 );"#,
64 [],
65 )?;
66
67 connection.execute(
69 r#"CREATE UNIQUE INDEX IF NOT EXISTS idx_sheds_local
70 ON sheds(storage_type) WHERE browsing_context IS NULL;"#,
71 [],
72 )?;
73 connection.execute(
74 r#"CREATE UNIQUE INDEX IF NOT EXISTS idx_sheds_session
75 ON sheds(browsing_context) WHERE browsing_context IS NOT NULL;"#,
76 [],
77 )?;
78
79 connection.execute(
80 r#"CREATE TABLE IF NOT EXISTS shelves (
81 id INTEGER PRIMARY KEY,
82 shed_id INTEGER NOT NULL,
83 origin TEXT NOT NULL,
84 UNIQUE (shed_id, origin),
85 FOREIGN KEY (shed_id) REFERENCES sheds(id) ON DELETE CASCADE
86 );"#,
87 [],
88 )?;
89
90 connection.execute(
92 r#"CREATE TABLE IF NOT EXISTS buckets (
93 id INTEGER PRIMARY KEY,
94 shelf_id INTEGER NOT NULL UNIQUE,
95 persisted BOOLEAN DEFAULT 0,
96 name TEXT,
97 mode TEXT,
98 expires DATETIME,
99 FOREIGN KEY (shelf_id) REFERENCES shelves(id) ON DELETE CASCADE
100 );"#,
101 [],
102 )?;
103
104 connection.execute(
106 r#"CREATE TABLE IF NOT EXISTS bottles (
107 id INTEGER PRIMARY KEY,
108 bucket_id INTEGER NOT NULL,
109 identifier TEXT NOT NULL, -- "idb", "ls", "opfs", "cache"
110 UNIQUE (bucket_id, identifier),
111 FOREIGN KEY (bucket_id) REFERENCES buckets(id) ON DELETE CASCADE
112 );"#,
113 [],
114 )?;
115
116 connection.execute(
117 r#"CREATE TABLE IF NOT EXISTS databases (
118 id INTEGER PRIMARY KEY,
119 bottle_id INTEGER NOT NULL,
120 name TEXT NOT NULL,
121 UNIQUE (bottle_id, name),
122 FOREIGN KEY (bottle_id) REFERENCES bottles(id) ON DELETE CASCADE
123 );
124 "#,
125 [],
126 )?;
127
128 connection.execute(
129 r#"CREATE TABLE IF NOT EXISTS directories (
130 id INTEGER PRIMARY KEY,
131 database_id INTEGER NOT NULL UNIQUE,
132 path TEXT NOT NULL,
133 FOREIGN KEY (database_id) REFERENCES databases(id) ON DELETE CASCADE
134 );"#,
135 [],
136 )?;
137
138 connection.execute_batch(
139 r#"
140 CREATE UNIQUE INDEX IF NOT EXISTS sheds_local_identity_idx
141 ON sheds(storage_type)
142 WHERE storage_type = 'local' AND browsing_context IS NULL;
143
144 CREATE UNIQUE INDEX IF NOT EXISTS sheds_session_identity_idx
145 ON sheds(storage_type, browsing_context)
146 WHERE storage_type = 'session' AND browsing_context IS NOT NULL;
147
148 CREATE UNIQUE INDEX IF NOT EXISTS shelves_origin_shed_identity_idx
149 ON shelves(origin, shed_id);
150 "#,
151 )?;
152 Ok(())
154 }
155}
156
157fn ensure_storage_shed(
158 storage_type: &StorageType,
159 browsing_context: Option<String>,
160 tx: &Transaction,
161) -> rusqlite::Result<i64> {
162 match browsing_context {
163 Some(browsing_context) => {
164 tx.execute(
165 "INSERT INTO sheds (storage_type, browsing_context) VALUES (?1, ?2) ON CONFLICT DO NOTHING;",
166 (storage_type.as_str(), browsing_context.as_str()),
167 )?;
168
169 tx.query_row(
170 "SELECT id FROM sheds WHERE storage_type = ?1 AND browsing_context = ?2;",
171 (storage_type.as_str(), browsing_context.as_str()),
172 |row| row.get(0),
173 )
174 },
175 None => {
176 tx.execute(
177 "INSERT INTO sheds (storage_type, browsing_context) VALUES (?1, NULL) ON CONFLICT DO NOTHING;",
178 [storage_type.as_str()],
179 )?;
180
181 tx.query_row(
182 "SELECT id FROM sheds WHERE storage_type = ?1 AND browsing_context IS NULL;",
183 [storage_type.as_str()],
184 |row| row.get(0),
185 )
186 },
187 }
188}
189
190fn create_a_storage_bucket(
192 shelf_id: i64,
193 storage_type: StorageType,
194 tx: &Transaction,
195) -> rusqlite::Result<i64> {
196 let bucket_id: i64 = if let StorageType::Local = storage_type {
199 tx.query_row(
200 "INSERT INTO buckets (mode, shelf_id) VALUES (?1, ?2)
201 ON CONFLICT(shelf_id) DO UPDATE SET shelf_id = excluded.shelf_id
202 RETURNING id;",
203 [Mode::default().as_str(), &shelf_id.to_string()],
204 |row| row.get(0),
205 )?
206 } else {
207 tx.query_row(
211 "INSERT INTO buckets (shelf_id) VALUES (?1)
212 ON CONFLICT(shelf_id) DO UPDATE SET shelf_id = excluded.shelf_id
213 RETURNING id;",
214 [&shelf_id.to_string()],
215 |row| row.get(0),
216 )?
217 };
218
219 let registered_endpoints = match storage_type {
225 StorageType::Local => vec![
226 StorageIdentifier::Caches,
227 StorageIdentifier::IndexedDB,
228 StorageIdentifier::LocalStorage,
229 StorageIdentifier::ServiceWorkerRegistrations,
230 ],
231 StorageType::Session => vec![StorageIdentifier::SessionStorage],
232 };
233
234 for identifier in registered_endpoints {
235 tx.execute(
236 "INSERT INTO bottles (bucket_id, identifier) VALUES (?1, ?2)
237 ON CONFLICT(bucket_id, identifier) DO NOTHING;",
238 (bucket_id, identifier.as_str()),
239 )?;
240 }
241
242 Ok(bucket_id)
244}
245
246fn create_a_storage_shelf(
248 shed: i64,
249 origin: &ImmutableOrigin,
250 storage_type: StorageType,
251 tx: &Transaction,
252) -> rusqlite::Result<i64> {
253 let shelf_id: i64 = tx.query_row(
255 "INSERT INTO shelves (shed_id, origin) VALUES (?1, ?2)
256 ON CONFLICT(shed_id, origin) DO UPDATE SET origin = excluded.origin
257 RETURNING id;",
258 [&shed.to_string(), &origin.ascii_serialization()],
259 |row| row.get(0),
260 )?;
261
262 create_a_storage_bucket(shelf_id, storage_type, tx)
265}
266
267fn obtain_a_storage_shelf(
269 shed: i64,
270 origin: &ImmutableOrigin,
271 storage_type: StorageType,
272 tx: &Transaction,
273) -> rusqlite::Result<i64> {
274 let bucket_id = create_a_storage_shelf(shed, origin, storage_type, tx)?;
283
284 Ok(bucket_id)
287}
288
289impl RegistryEngine for SqliteEngine {
290 type Error = rusqlite::Error;
291
292 fn create_database(
294 &mut self,
295 bottle_id: i64,
296 name: String,
297 ) -> Result<PathBuf, ClientStorageErrorr<Self::Error>> {
298 let tx = self.connection.transaction()?;
299
300 let dir = Uuid::new_v4().to_string();
301 let cluster = dir.chars().last().unwrap();
302 let path = self
303 .base_dir
304 .join("bottles")
305 .join(cluster.to_string())
306 .join(dir);
307
308 let path_str = path.to_str().ok_or_else(|| {
309 ClientStorageErrorr::Internal(rusqlite::Error::InvalidParameterName(String::from(
310 "path",
311 )))
312 })?;
313
314 let database_id: i64 = tx
315 .query_row(
316 "INSERT INTO databases (bottle_id, name) VALUES (?1, ?2)
317 ON CONFLICT(bottle_id, name) DO NOTHING
318 RETURNING id;",
319 (bottle_id, name),
320 |row| row.get(0),
321 )
322 .map_err(|e| match e {
323 rusqlite::Error::QueryReturnedNoRows => ClientStorageErrorr::DatabaseAlreadyExists,
324 e => ClientStorageErrorr::Internal(e),
325 })?;
326
327 tx.execute(
328 "INSERT INTO directories (database_id, path) VALUES (?1, ?2);",
329 (database_id, path_str),
330 )?;
331
332 std::fs::create_dir_all(&path).map_err(|_| ClientStorageErrorr::DirectoryCreationFailed)?;
333
334 tx.commit()?;
335
336 Ok(path)
337 }
338
339 fn delete_database(
341 &mut self,
342 bottle_id: i64,
343 name: String,
344 ) -> Result<(), ClientStorageErrorr<Self::Error>> {
345 let tx = self.connection.transaction()?;
346
347 let database_id: i64 = tx.query_row(
348 "SELECT id FROM databases WHERE bottle_id = ?1 AND name = ?2;",
349 (bottle_id, name.clone()),
350 |row| row.get(0),
351 )?;
352
353 let path: String = tx.query_row(
354 "SELECT path FROM directories WHERE database_id = ?1;",
355 [database_id],
356 |row| row.get(0),
357 )?;
358
359 tx.execute(
360 "DELETE FROM databases WHERE bottle_id = ?1 AND name = ?2;",
361 (bottle_id, name),
362 )?;
363
364 if tx.changes() == 0 {
365 return Err(ClientStorageErrorr::DatabaseDoesNotExist);
366 }
367 std::fs::remove_dir_all(&path).map_err(|_| ClientStorageErrorr::DirectoryDeletionFailed)?;
371
372 tx.commit()?;
373
374 Ok(())
375 }
376
377 fn obtain_a_storage_bottle_map(
379 &mut self,
380 storage_type: StorageType,
381 webview: WebViewId,
382 storage_identifier: StorageIdentifier,
383 origin: ImmutableOrigin,
384 sender: &GenericSender<ClientStorageThreadMessage>,
385 ) -> Result<StorageProxyMap, ClientStorageErrorr<Self::Error>> {
386 let tx = self.connection.transaction()?;
387
388 let shed_id: i64 = match storage_type {
390 StorageType::Local => {
391 ensure_storage_shed(&storage_type, None, &tx)?
393 },
394 StorageType::Session => {
395 ensure_storage_shed(
401 &storage_type,
402 Some(Into::<BrowsingContextId>::into(webview).to_string()),
403 &tx,
404 )?
405 },
406 };
407
408 let bucket_id = obtain_a_storage_shelf(shed_id, &origin, storage_type, &tx)?;
412
413 let bottle_id: i64 = tx.query_row(
417 "SELECT id FROM bottles WHERE bucket_id = ?1 AND identifier = ?2;",
418 (bucket_id, storage_identifier.as_str()),
419 |row| row.get(0),
420 )?;
421
422 tx.commit()?;
423
424 Ok(StorageProxyMap {
433 bottle_id,
434 handle: ClientStorageThreadHandle::new(sender.clone()),
435 })
436 }
437}
438
439pub trait ClientStorageThreadFactory {
440 fn new(config_dir: Option<PathBuf>) -> Self;
441}
442
443impl ClientStorageThreadFactory for ClientStorageThreadHandle {
444 fn new(config_dir: Option<PathBuf>) -> ClientStorageThreadHandle {
445 let (generic_sender, generic_receiver) = generic_channel::channel().unwrap();
446
447 let storage_dir = config_dir
448 .unwrap_or_else(|| {
449 let tmp_dir = tempfile::tempdir().unwrap();
450 tmp_dir.path().to_path_buf()
451 })
452 .join("clientstorage");
453 std::fs::create_dir_all(&storage_dir)
454 .expect("Failed to create ClientStorage storage directory");
455 let sender_clone = generic_sender.clone();
456 thread::Builder::new()
457 .name("ClientStorageThread".to_owned())
458 .spawn(move || {
459 let engine = SqliteEngine::new(storage_dir)
460 .expect("Failed to initialize ClientStorage registry engine");
461 ClientStorageThread::new(sender_clone, generic_receiver, engine).start();
462 })
463 .expect("Thread spawning failed");
464
465 ClientStorageThreadHandle::new(generic_sender)
466 }
467}
468
469struct ClientStorageThread<E: RegistryEngine> {
470 receiver: GenericReceiver<ClientStorageThreadMessage>,
471 sender: GenericSender<ClientStorageThreadMessage>,
472 engine: E,
473}
474
475impl<E> ClientStorageThread<E>
476where
477 E: RegistryEngine,
478{
479 pub fn new(
480 sender: GenericSender<ClientStorageThreadMessage>,
481 receiver: GenericReceiver<ClientStorageThreadMessage>,
482 engine: E,
483 ) -> ClientStorageThread<E> {
484 ClientStorageThread {
485 sender,
486 receiver,
487 engine,
488 }
489 }
490
491 pub fn start(&mut self) {
492 while let Ok(message) = self.receiver.recv() {
493 match message {
494 ClientStorageThreadMessage::ObtainBottleMap {
495 storage_type,
496 storage_identifier,
497 webview,
498 origin,
499 sender,
500 } => {
501 let result = self.engine.obtain_a_storage_bottle_map(
502 storage_type,
503 webview,
504 storage_identifier,
505 origin,
506 &self.sender,
507 );
508 let _ = sender.send(result.map_err(|e| format!("{:?}", e)));
509 },
510 ClientStorageThreadMessage::CreateDatabase {
511 bottle_id,
512 name,
513 sender,
514 } => {
515 let result = self.engine.create_database(bottle_id, name);
516 let _ = sender.send(result.map_err(|e| format!("{:?}", e)));
517 },
518 ClientStorageThreadMessage::DeleteDatabase {
519 bottle_id,
520 name,
521 sender,
522 } => {
523 let result = self.engine.delete_database(bottle_id, name);
524 let _ = sender.send(result.map_err(|e| format!("{:?}", e)));
525 },
526 ClientStorageThreadMessage::Exit(sender) => {
527 let _ = sender.send(());
528 break;
529 },
530 }
531 }
532 }
533}