make tab restore more resilient
This commit is contained in:
@@ -0,0 +1,535 @@
|
||||
{
|
||||
"_meta": {
|
||||
"description": "This file contains a serialized version of schema entities for drift.",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
"options": {
|
||||
"store_date_time_values_as_text": false
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"id": 0,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "container",
|
||||
"was_declared_in_moor": true,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "PRIMARY KEY NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
"primary-key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"getter_name": "name",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"getter_name": "color",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "const ColorConverter()",
|
||||
"dart_type_name": "Color"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metadata",
|
||||
"getter_name": "metadata",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "const ContainerMetadataConverter()",
|
||||
"dart_type_name": "ContainerMetadata"
|
||||
}
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"without_rowid": false,
|
||||
"constraints": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"references": [
|
||||
0
|
||||
],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "tab",
|
||||
"was_declared_in_moor": true,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "PRIMARY KEY NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
"primary-key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"getter_name": "source",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "const EnumIndexConverter<TabSource>(TabSource.values)",
|
||||
"dart_type_name": "TabSource"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "parent_id",
|
||||
"getter_name": "parentId",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "REFERENCES tab(id)ON DELETE SET NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "container_id",
|
||||
"getter_name": "containerId",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "REFERENCES container(id)ON DELETE CASCADE",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
{
|
||||
"foreign_key": {
|
||||
"to": {
|
||||
"table": "container",
|
||||
"column": "id"
|
||||
},
|
||||
"initially_deferred": false,
|
||||
"on_update": null,
|
||||
"on_delete": "cascade"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "order_key",
|
||||
"getter_name": "orderKey",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"getter_name": "url",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "const UriConverterNullable()",
|
||||
"dart_type_name": "Uri?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"getter_name": "title",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "tab_mode",
|
||||
"getter_name": "tabMode",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL DEFAULT 0",
|
||||
"default_dart": "const CustomExpression('0')",
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "const EnumIndexConverter<TabModeDbValue>(TabModeDbValue.values)",
|
||||
"dart_type_name": "TabModeDbValue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isolation_context_id",
|
||||
"getter_name": "isolationContextId",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "is_pinned",
|
||||
"getter_name": "isPinned",
|
||||
"moor_type": "bool",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL DEFAULT 0",
|
||||
"default_dart": "const CustomExpression('0')",
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "is_probably_readerable",
|
||||
"getter_name": "isProbablyReaderable",
|
||||
"moor_type": "bool",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "extracted_content_markdown",
|
||||
"getter_name": "extractedContentMarkdown",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "extracted_content_plain",
|
||||
"getter_name": "extractedContentPlain",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "full_content_markdown",
|
||||
"getter_name": "fullContentMarkdown",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "full_content_plain",
|
||||
"getter_name": "fullContentPlain",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "timestamp",
|
||||
"getter_name": "timestamp",
|
||||
"moor_type": "dateTime",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"without_rowid": false,
|
||||
"constraints": [
|
||||
"CHECK((tab_mode = 2 AND isolation_context_id IS NOT NULL)OR(tab_mode != 2 AND isolation_context_id IS NULL))"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "closed_tab_tombstone",
|
||||
"was_declared_in_moor": true,
|
||||
"columns": [
|
||||
{
|
||||
"name": "tab_id",
|
||||
"getter_name": "tabId",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "PRIMARY KEY NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
"primary-key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "closed_at",
|
||||
"getter_name": "closedAt",
|
||||
"moor_type": "dateTime",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"without_rowid": false,
|
||||
"constraints": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"name": "idx_tab_parent_container",
|
||||
"sql": "CREATE INDEX idx_tab_parent_container ON tab(parent_id, container_id);",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "tab_fts",
|
||||
"was_declared_in_moor": true,
|
||||
"columns": [
|
||||
{
|
||||
"name": "title",
|
||||
"getter_name": "title",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"getter_name": "url",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "extracted_content_plain",
|
||||
"getter_name": "extractedContentPlain",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "full_content_plain",
|
||||
"getter_name": "fullContentPlain",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": true,
|
||||
"create_virtual_stmt": "CREATE VIRTUAL TABLE \"tab_fts\" USING fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize=\"trigram\")",
|
||||
"without_rowid": false,
|
||||
"constraints": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "trigger",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"references_in_body": [
|
||||
1
|
||||
],
|
||||
"name": "tab_maintain_parent_chain_on_delete",
|
||||
"sql": "CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN\n -- Update all children of the deleted row to point to its parent (grandparent)\n -- Only if the grandparent exists, otherwise set to NULL\n UPDATE tab\n SET parent_id = CASE\n WHEN OLD.parent_id IS NOT NULL AND\n EXISTS(SELECT 1 FROM tab WHERE id = OLD.parent_id)\n THEN OLD.parent_id\n ELSE NULL\n END\n WHERE parent_id = OLD.id;\nEND;"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"references": [
|
||||
1,
|
||||
4
|
||||
],
|
||||
"type": "trigger",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"references_in_body": [
|
||||
1,
|
||||
4
|
||||
],
|
||||
"name": "tab_after_insert",
|
||||
"sql": "CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN\n INSERT INTO \n tab_fts(rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES (new.rowid, new.title, new.url, new.extracted_content_plain, new.full_content_plain);\nEND;"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"references": [
|
||||
1,
|
||||
4
|
||||
],
|
||||
"type": "trigger",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"references_in_body": [
|
||||
1,
|
||||
4
|
||||
],
|
||||
"name": "tab_after_delete",
|
||||
"sql": "CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN\n INSERT INTO \n tab_fts(tab_fts, rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES('delete', old.rowid, old.title, old.url, old.extracted_content_plain, old.full_content_plain);\nEND;"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"references": [
|
||||
1,
|
||||
4
|
||||
],
|
||||
"type": "trigger",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"references_in_body": [
|
||||
1,
|
||||
4
|
||||
],
|
||||
"name": "tab_after_update",
|
||||
"sql": "CREATE TRIGGER tab_after_update AFTER UPDATE ON tab BEGIN\n INSERT INTO \n tab_fts(tab_fts, rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES('delete', old.rowid, old.title, old.url, old.extracted_content_plain, old.full_content_plain);\n INSERT INTO \n tab_fts(rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES (new.rowid, new.title, new.url, new.extracted_content_plain, new.full_content_plain);\nEND;"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fixed_sql": [
|
||||
{
|
||||
"name": "container",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"container\" (\"id\" TEXT PRIMARY KEY NOT NULL, \"name\" TEXT, \"color\" INTEGER NOT NULL, \"metadata\" TEXT);"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tab",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"tab\" (\"id\" TEXT PRIMARY KEY NOT NULL, \"source\" INTEGER NOT NULL, \"parent_id\" TEXT REFERENCES tab(id)ON DELETE SET NULL, \"container_id\" TEXT REFERENCES container(id)ON DELETE CASCADE, \"order_key\" TEXT NOT NULL, \"url\" TEXT, \"title\" TEXT, \"tab_mode\" INTEGER NOT NULL DEFAULT 0, \"isolation_context_id\" TEXT, \"is_pinned\" INTEGER NOT NULL DEFAULT 0, \"is_probably_readerable\" INTEGER, \"extracted_content_markdown\" TEXT, \"extracted_content_plain\" TEXT, \"full_content_markdown\" TEXT, \"full_content_plain\" TEXT, \"timestamp\" INTEGER NOT NULL, CHECK((tab_mode = 2 AND isolation_context_id IS NOT NULL)OR(tab_mode != 2 AND isolation_context_id IS NULL)));"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "closed_tab_tombstone",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"closed_tab_tombstone\" (\"tab_id\" TEXT PRIMARY KEY NOT NULL, \"closed_at\" INTEGER NOT NULL);"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_tab_parent_container",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE INDEX idx_tab_parent_container ON tab (parent_id, container_id)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tab_fts",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE VIRTUAL TABLE IF NOT EXISTS \"tab_fts\" USING fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize=\"trigram\");"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tab_maintain_parent_chain_on_delete",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN UPDATE tab SET parent_id = CASE WHEN OLD.parent_id IS NOT NULL AND EXISTS (SELECT 1 FROM tab WHERE id = OLD.parent_id) THEN OLD.parent_id ELSE NULL END WHERE parent_id = OLD.id;END"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tab_after_insert",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts (\"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES (new.\"rowid\", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tab_after_delete",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN INSERT INTO tab_fts (tab_fts, \"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES ('delete', old.\"rowid\", old.title, old.url, old.extracted_content_plain, old.full_content_plain);END"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tab_after_update",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TRIGGER tab_after_update AFTER UPDATE ON tab BEGIN INSERT INTO tab_fts (tab_fts, \"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES ('delete', old.\"rowid\", old.title, old.url, old.extracted_content_plain, old.full_content_plain);INSERT INTO tab_fts (\"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES (new.\"rowid\", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -52,6 +52,10 @@ part 'tab.g.dart';
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabRepository extends _$TabRepository {
|
||||
final _tabsService = GeckoTabService();
|
||||
final _sessionStartedAt = DateTime.now();
|
||||
bool _didPruneTombstones = false;
|
||||
bool _reclosing = false;
|
||||
bool _suppressNextReclose = false;
|
||||
|
||||
final _tabFromIntent = <String>{};
|
||||
final _closeLock = Lock();
|
||||
@@ -550,38 +554,28 @@ class TabRepository extends _$TabRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> closeTab(String tabId) {
|
||||
Future<void> _closeTabsInternal(
|
||||
List<String> tabIds, {
|
||||
required bool recordTombstones,
|
||||
}) {
|
||||
return _closeLock.synchronized(() async {
|
||||
// Collect isolation context before close
|
||||
final isolationContextId = ref
|
||||
.read(tabStatesProvider)[tabId]
|
||||
?.isolationContextId;
|
||||
|
||||
if (ref.read(selectedTabProvider) == tabId) {
|
||||
await _selectNextTab(tabId);
|
||||
if (tabIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _preservePromotedChildOrderOnClose([tabId]);
|
||||
|
||||
await _tabsService.removeTab(tabId: tabId);
|
||||
|
||||
// Queue isolation cleanup — actual cleanup runs after syncTabs
|
||||
// deletes the DB row, so the count check is accurate.
|
||||
if (isolationContextId != null) {
|
||||
_pendingIsolationCleanup.add(isolationContextId);
|
||||
if (recordTombstones) {
|
||||
await ref
|
||||
.read(tabDatabaseProvider)
|
||||
.tabDao
|
||||
.addClosedTabTombstones(tabIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> closeTabs(List<String> tabIds) {
|
||||
return _closeLock.synchronized(() async {
|
||||
// Collect isolation contexts from tabs being closed
|
||||
for (final tabId in tabIds) {
|
||||
final contextId = ref
|
||||
final isolationContextId = ref
|
||||
.read(tabStatesProvider)[tabId]
|
||||
?.isolationContextId;
|
||||
if (contextId != null) {
|
||||
_pendingIsolationCleanup.add(contextId);
|
||||
if (isolationContextId != null) {
|
||||
_pendingIsolationCleanup.add(isolationContextId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,10 +586,29 @@ class TabRepository extends _$TabRepository {
|
||||
|
||||
await _preservePromotedChildOrderOnClose(tabIds);
|
||||
|
||||
await _tabsService.removeTabs(ids: tabIds);
|
||||
if (tabIds.length == 1) {
|
||||
await _tabsService.removeTab(tabId: tabIds.single);
|
||||
} else {
|
||||
await _tabsService.removeTabs(ids: tabIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> closeTab(String tabId) {
|
||||
return _closeTabsInternal([tabId], recordTombstones: true);
|
||||
}
|
||||
|
||||
Future<void> closeTabs(List<String> tabIds) {
|
||||
return _closeTabsInternal(tabIds, recordTombstones: true);
|
||||
}
|
||||
|
||||
Future<void> _clearTombstonesForCurrentTabs(List<String> tabIds) {
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.tabDao
|
||||
.deleteClosedTabTombstones(tabIds);
|
||||
}
|
||||
|
||||
Future<void> _preservePromotedChildOrderOnClose(List<String> tabIds) {
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
@@ -662,9 +675,49 @@ class TabRepository extends _$TabRepository {
|
||||
}
|
||||
|
||||
Future<void> undoClose() {
|
||||
// Suppress the next reclose pass: undo can resurrect a tab whose
|
||||
// tombstone is still on disk (from a previous session); without this
|
||||
// flag the listener would immediately re-close it.
|
||||
_suppressNextReclose = true;
|
||||
return _tabsService.undo();
|
||||
}
|
||||
|
||||
Future<bool> _recloseRestoredClosedTabs(List<String> tabIds) async {
|
||||
if (_reclosing) {
|
||||
return false;
|
||||
}
|
||||
_reclosing = true;
|
||||
try {
|
||||
final tabDao = ref.read(tabDatabaseProvider).tabDao;
|
||||
|
||||
if (!_didPruneTombstones) {
|
||||
await tabDao.pruneExpiredClosedTabTombstones();
|
||||
_didPruneTombstones = true;
|
||||
}
|
||||
|
||||
final restoredClosedTabIds = await tabDao.getStartupRestoredClosedTabIds(
|
||||
tabIds,
|
||||
sessionStartedAt: _sessionStartedAt,
|
||||
);
|
||||
|
||||
if (restoredClosedTabIds.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// recordTombstones: false — the tombstone already exists from the
|
||||
// original close; rewriting it would bump closed_at into this session
|
||||
// and disable the resurrection check on the next emission.
|
||||
await _closeTabsInternal(
|
||||
restoredClosedTabIds.toList(growable: false),
|
||||
recordTombstones: false,
|
||||
);
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
_reclosing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleans up isolation contexts from previous crashed sessions.
|
||||
/// Called once after tab list stabilizes on startup.
|
||||
// Future<void> _cleanupOrphanedIsolationContexts() async {
|
||||
@@ -845,6 +898,15 @@ class TabRepository extends _$TabRepository {
|
||||
ref.listen(
|
||||
tabListProvider,
|
||||
(previous, next) async {
|
||||
if (_suppressNextReclose) {
|
||||
_suppressNextReclose = false;
|
||||
// Drop tombstones for the tabs that just came back via undo so
|
||||
// future emissions don't treat them as resurrections.
|
||||
await _clearTombstonesForCurrentTabs(next.value);
|
||||
} else if (await _recloseRestoredClosedTabs(next.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Only sync tabs if there has been a previous value or is not empty
|
||||
final shouldSyncTabs =
|
||||
next.value.isNotEmpty || (previous?.value.isNotEmpty ?? false);
|
||||
|
||||
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabRepositoryHash() => r'4c5bda4d0ddcc66cfa420d3c2ca97db42b73b878';
|
||||
String _$tabRepositoryHash() => r'84ecfed72f17c367fc7bf0100f6367d4e7609596';
|
||||
|
||||
abstract class _$TabRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
@@ -47,6 +47,8 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
final _undoHistory = <String, TabData>{};
|
||||
Timer? _clearHistoryTimer;
|
||||
|
||||
static const closedTabTombstoneTtl = Duration(hours: 24);
|
||||
|
||||
TabDao(super.db);
|
||||
|
||||
UpdateStatement<Tab, TabData> _updateByIdStatement(String id) =>
|
||||
@@ -317,6 +319,103 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
return statement.write(TabCompanion(containerId: Value(containerId)));
|
||||
}
|
||||
|
||||
Future<void> addClosedTabTombstones(
|
||||
Iterable<String> tabIds, {
|
||||
DateTime? closedAt,
|
||||
}) async {
|
||||
final uniqueIds = tabIds.toSet();
|
||||
if (uniqueIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final effectiveClosedAt = closedAt ?? DateTime.now();
|
||||
|
||||
await batch((batch) {
|
||||
for (final tabId in uniqueIds) {
|
||||
batch.insert(
|
||||
db.closedTabTombstone,
|
||||
ClosedTabTombstoneCompanion.insert(
|
||||
tabId: tabId,
|
||||
closedAt: effectiveClosedAt,
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(_) =>
|
||||
ClosedTabTombstoneCompanion(closedAt: Value(effectiveClosedAt)),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteClosedTabTombstones(Iterable<String> tabIds) {
|
||||
final uniqueIds = tabIds.toSet();
|
||||
if (uniqueIds.isEmpty) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
return (db.closedTabTombstone.delete()
|
||||
..where((t) => t.tabId.isIn(uniqueIds)))
|
||||
.go();
|
||||
}
|
||||
|
||||
Future<void> pruneExpiredClosedTabTombstones({
|
||||
Duration ttl = closedTabTombstoneTtl,
|
||||
DateTime? now,
|
||||
}) {
|
||||
final cutoff = (now ?? DateTime.now()).subtract(ttl);
|
||||
|
||||
return (db.closedTabTombstone.delete()
|
||||
..where((t) => t.closedAt.isSmallerOrEqualValue(cutoff)))
|
||||
.go();
|
||||
}
|
||||
|
||||
Future<Set<String>> getStartupRestoredClosedTabIds(
|
||||
Iterable<String> tabIds, {
|
||||
required DateTime sessionStartedAt,
|
||||
Duration ttl = closedTabTombstoneTtl,
|
||||
DateTime? now,
|
||||
}) async {
|
||||
final uniqueIds = tabIds.toSet();
|
||||
if (uniqueIds.isEmpty) {
|
||||
return const <String>{};
|
||||
}
|
||||
|
||||
final freshnessCutoff = (now ?? DateTime.now()).subtract(ttl);
|
||||
final query = selectOnly(db.closedTabTombstone)
|
||||
..addColumns([db.closedTabTombstone.tabId])
|
||||
..where(
|
||||
db.closedTabTombstone.tabId.isIn(uniqueIds) &
|
||||
db.closedTabTombstone.closedAt.isSmallerThanValue(
|
||||
sessionStartedAt,
|
||||
) &
|
||||
db.closedTabTombstone.closedAt.isBiggerOrEqualValue(
|
||||
freshnessCutoff,
|
||||
),
|
||||
);
|
||||
|
||||
return query
|
||||
.map((row) => row.read(db.closedTabTombstone.tabId)!)
|
||||
.get()
|
||||
.then((rows) => rows.toSet());
|
||||
}
|
||||
|
||||
Future<Map<String, String>> _containerIdsByContextualIdentity(
|
||||
Iterable<String> contextIds,
|
||||
) async {
|
||||
final uniqueContextIds = contextIds.toSet();
|
||||
if (uniqueContextIds.isEmpty) {
|
||||
return const <String, String>{};
|
||||
}
|
||||
|
||||
final rows = await db.definitionsDrift
|
||||
.containerIdsByContextualIdentities(
|
||||
contextIds: uniqueContextIds.toList(growable: false),
|
||||
)
|
||||
.get();
|
||||
|
||||
return {for (final row in rows) row.contextualIdentity: row.id};
|
||||
}
|
||||
|
||||
Future<void> reorderTabs({
|
||||
required List<String> movingTabIds,
|
||||
required String? previousTabId,
|
||||
@@ -633,6 +732,32 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
parentIdsToValidate,
|
||||
).get().then((ids) => ids.toSet());
|
||||
|
||||
final containerRepairCandidates = {
|
||||
for (final state in next.values)
|
||||
if (state.contextId != null &&
|
||||
(previous?[state.id]?.contextId != state.contextId ||
|
||||
previous?[state.id] == null))
|
||||
state.id: state.contextId!,
|
||||
};
|
||||
final currentContainerIds = containerRepairCandidates.isEmpty
|
||||
? const <String, String?>{}
|
||||
: await getTabsContainerId(
|
||||
containerRepairCandidates.keys,
|
||||
).get().then(Map.fromEntries);
|
||||
final repairableContexts = containerRepairCandidates.entries
|
||||
.where((entry) => currentContainerIds[entry.key] == null)
|
||||
.map((entry) => entry.value)
|
||||
.toSet();
|
||||
final containerIdsByContext = await _containerIdsByContextualIdentity(
|
||||
repairableContexts,
|
||||
);
|
||||
final repairedContainerIds = <String, String>{
|
||||
for (final entry in containerRepairCandidates.entries)
|
||||
if (currentContainerIds[entry.key] == null)
|
||||
if (containerIdsByContext[entry.value] case final containerId?)
|
||||
entry.key: containerId,
|
||||
};
|
||||
|
||||
// Complete validation map
|
||||
for (final state in next.values) {
|
||||
final previousState = previous?[state.id];
|
||||
@@ -658,13 +783,17 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
previousState.url != state.url ||
|
||||
previousState.title != state.title ||
|
||||
previousState.parentId != state.parentId ||
|
||||
previousState.tabMode != state.tabMode) {
|
||||
previousState.tabMode != state.tabMode ||
|
||||
repairedContainerIds.containsKey(state.id)) {
|
||||
batch.update(
|
||||
db.tab,
|
||||
TabCompanion(
|
||||
parentId: validatedParentIds.containsKey(state.id)
|
||||
? Value(validatedParentIds[state.id])
|
||||
: const Value.absent(),
|
||||
containerId:
|
||||
repairedContainerIds[state.id].mapNotNull(Value.new) ??
|
||||
const Value.absent(),
|
||||
url: (previousState?.url != state.url)
|
||||
? Value(state.url)
|
||||
: const Value.absent(),
|
||||
|
||||
@@ -31,7 +31,7 @@ import 'package:weblibre/features/search/domain/fts_tokenizer.dart';
|
||||
@DriftDatabase(include: {'definitions.drift'}, daos: [ContainerDao, TabDao])
|
||||
class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
||||
@override
|
||||
final int schemaVersion = 8;
|
||||
final int schemaVersion = 9;
|
||||
|
||||
@override
|
||||
final int ftsTokenLimit = 10;
|
||||
@@ -137,5 +137,8 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
||||
|
||||
await m.createIndex(schema.idxTabParentContainer);
|
||||
},
|
||||
from8To9: (m, schema) async {
|
||||
await m.create(schema.closedTabTombstone);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ abstract class $TabDatabase extends i0.GeneratedDatabase {
|
||||
$TabDatabaseManager get managers => $TabDatabaseManager(this);
|
||||
late final i1.Container container = i1.Container(this);
|
||||
late final i1.Tab tab = i1.Tab(this);
|
||||
late final i1.ClosedTabTombstone closedTabTombstone = i1.ClosedTabTombstone(
|
||||
this,
|
||||
);
|
||||
late final i1.TabFts tabFts = i1.TabFts(this);
|
||||
late final i2.ContainerDao containerDao = i2.ContainerDao(
|
||||
this as i3.TabDatabase,
|
||||
@@ -32,6 +35,7 @@ abstract class $TabDatabase extends i0.GeneratedDatabase {
|
||||
List<i0.DatabaseSchemaEntity> get allSchemaEntities => [
|
||||
container,
|
||||
tab,
|
||||
closedTabTombstone,
|
||||
i1.idxTabParentContainer,
|
||||
tabFts,
|
||||
i1.tabMaintainParentChainOnDelete,
|
||||
@@ -86,6 +90,8 @@ class $TabDatabaseManager {
|
||||
i1.$ContainerTableManager get container =>
|
||||
i1.$ContainerTableManager(_db, _db.container);
|
||||
i1.$TabTableManager get tab => i1.$TabTableManager(_db, _db.tab);
|
||||
i1.$ClosedTabTombstoneTableManager get closedTabTombstone =>
|
||||
i1.$ClosedTabTombstoneTableManager(_db, _db.closedTabTombstone);
|
||||
i1.$TabFtsTableManager get tabFts => i1.$TabFtsTableManager(_db, _db.tabFts);
|
||||
}
|
||||
|
||||
|
||||
@@ -809,6 +809,128 @@ final class Schema8 extends i0.VersionedSchema {
|
||||
);
|
||||
}
|
||||
|
||||
final class Schema9 extends i0.VersionedSchema {
|
||||
Schema9({required super.database}) : super(version: 9);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
container,
|
||||
tab,
|
||||
closedTabTombstone,
|
||||
idxTabParentContainer,
|
||||
tabFts,
|
||||
tabMaintainParentChainOnDelete,
|
||||
tabAfterInsert,
|
||||
tabAfterDelete,
|
||||
tabAfterUpdate,
|
||||
];
|
||||
late final Shape0 container = Shape0(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'container',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_0, _column_1, _column_2, _column_3],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape5 tab = Shape5(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'tab',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'CHECK((tab_mode = 2 AND isolation_context_id IS NOT NULL)OR(tab_mode != 2 AND isolation_context_id IS NULL))',
|
||||
],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_16,
|
||||
_column_4,
|
||||
_column_5,
|
||||
_column_6,
|
||||
_column_7,
|
||||
_column_8,
|
||||
_column_17,
|
||||
_column_18,
|
||||
_column_19,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_13,
|
||||
_column_14,
|
||||
_column_15,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape6 closedTabTombstone = Shape6(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'closed_tab_tombstone',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_20, _column_21],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxTabParentContainer = i1.Index(
|
||||
'idx_tab_parent_container',
|
||||
'CREATE INDEX idx_tab_parent_container ON tab (parent_id, container_id)',
|
||||
);
|
||||
late final Shape2 tabFts = Shape2(
|
||||
source: i0.VersionedVirtualTable(
|
||||
entityName: 'tab_fts',
|
||||
moduleAndArgs:
|
||||
'fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize="trigram")',
|
||||
columns: [_column_8, _column_7, _column_12, _column_14],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Trigger tabMaintainParentChainOnDelete = i1.Trigger(
|
||||
'CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN UPDATE tab SET parent_id = CASE WHEN OLD.parent_id IS NOT NULL AND EXISTS (SELECT 1 FROM tab WHERE id = OLD.parent_id) THEN OLD.parent_id ELSE NULL END WHERE parent_id = OLD.id;END',
|
||||
'tab_maintain_parent_chain_on_delete',
|
||||
);
|
||||
final i1.Trigger tabAfterInsert = i1.Trigger(
|
||||
'CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END',
|
||||
'tab_after_insert',
|
||||
);
|
||||
final i1.Trigger tabAfterDelete = i1.Trigger(
|
||||
'CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);END',
|
||||
'tab_after_delete',
|
||||
);
|
||||
final i1.Trigger tabAfterUpdate = i1.Trigger(
|
||||
'CREATE TRIGGER tab_after_update AFTER UPDATE ON tab BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END',
|
||||
'tab_after_update',
|
||||
);
|
||||
}
|
||||
|
||||
class Shape6 extends i0.VersionedTable {
|
||||
Shape6({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get tabId =>
|
||||
columnsByName['tab_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get closedAt =>
|
||||
columnsByName['closed_at']! as i1.GeneratedColumn<int>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_20(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'tab_id',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.string,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL',
|
||||
);
|
||||
i1.GeneratedColumn<int> _column_21(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'closed_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||
@@ -816,6 +938,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
@@ -849,6 +972,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from7To8(migrator, schema);
|
||||
return 8;
|
||||
case 8:
|
||||
final schema = Schema9(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from8To9(migrator, schema);
|
||||
return 9;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
@@ -862,6 +990,7 @@ i1.OnUpgrade stepByStep({
|
||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from2To3: from2To3,
|
||||
@@ -870,5 +999,6 @@ i1.OnUpgrade stepByStep({
|
||||
from5To6: from5To6,
|
||||
from6To7: from6To7,
|
||||
from7To8: from7To8,
|
||||
from8To9: from8To9,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -37,6 +37,11 @@ CREATE TABLE tab(
|
||||
)
|
||||
);
|
||||
|
||||
CREATE TABLE closed_tab_tombstone(
|
||||
tab_id TEXT PRIMARY KEY NOT NULL,
|
||||
closed_at DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- Composite index used by `tabsWithRootAndDepth` (the seed checks
|
||||
-- `parent_id IS NULL OR NOT EXISTS(... WHERE p.id = parent_id AND
|
||||
-- p.container_id IS :container_id)`) and by `lastChildTabId`
|
||||
@@ -390,6 +395,13 @@ containerByContextualIdentity:
|
||||
WHERE container.metadata ->> '$.contextualIdentity' = :contextId
|
||||
LIMIT 1;
|
||||
|
||||
containerIdsByContextualIdentities:
|
||||
SELECT
|
||||
id,
|
||||
CAST(container.metadata ->> '$.contextualIdentity' AS TEXT) AS contextual_identity
|
||||
FROM container
|
||||
WHERE CAST(container.metadata ->> '$.contextualIdentity' AS TEXT) IN :contextIds;
|
||||
|
||||
containersToClearOnExit:
|
||||
SELECT container.metadata ->> '$.contextualIdentity' AS contextual_identity
|
||||
FROM container
|
||||
|
||||
+369
@@ -882,6 +882,159 @@ typedef $TabProcessedTableManager =
|
||||
i3.TabData,
|
||||
i0.PrefetchHooks Function({bool containerId})
|
||||
>;
|
||||
typedef $ClosedTabTombstoneCreateCompanionBuilder =
|
||||
i3.ClosedTabTombstoneCompanion Function({
|
||||
required String tabId,
|
||||
required DateTime closedAt,
|
||||
i0.Value<int> rowid,
|
||||
});
|
||||
typedef $ClosedTabTombstoneUpdateCompanionBuilder =
|
||||
i3.ClosedTabTombstoneCompanion Function({
|
||||
i0.Value<String> tabId,
|
||||
i0.Value<DateTime> closedAt,
|
||||
i0.Value<int> rowid,
|
||||
});
|
||||
|
||||
class $ClosedTabTombstoneFilterComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i3.ClosedTabTombstone> {
|
||||
$ClosedTabTombstoneFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnFilters<String> get tabId => $composableBuilder(
|
||||
column: $table.tabId,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<DateTime> get closedAt => $composableBuilder(
|
||||
column: $table.closedAt,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $ClosedTabTombstoneOrderingComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i3.ClosedTabTombstone> {
|
||||
$ClosedTabTombstoneOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnOrderings<String> get tabId => $composableBuilder(
|
||||
column: $table.tabId,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<DateTime> get closedAt => $composableBuilder(
|
||||
column: $table.closedAt,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $ClosedTabTombstoneAnnotationComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i3.ClosedTabTombstone> {
|
||||
$ClosedTabTombstoneAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumn<String> get tabId =>
|
||||
$composableBuilder(column: $table.tabId, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<DateTime> get closedAt =>
|
||||
$composableBuilder(column: $table.closedAt, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $ClosedTabTombstoneTableManager
|
||||
extends
|
||||
i0.RootTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i3.ClosedTabTombstone,
|
||||
i3.ClosedTabTombstoneData,
|
||||
i3.$ClosedTabTombstoneFilterComposer,
|
||||
i3.$ClosedTabTombstoneOrderingComposer,
|
||||
i3.$ClosedTabTombstoneAnnotationComposer,
|
||||
$ClosedTabTombstoneCreateCompanionBuilder,
|
||||
$ClosedTabTombstoneUpdateCompanionBuilder,
|
||||
(
|
||||
i3.ClosedTabTombstoneData,
|
||||
i0.BaseReferences<
|
||||
i0.GeneratedDatabase,
|
||||
i3.ClosedTabTombstone,
|
||||
i3.ClosedTabTombstoneData
|
||||
>,
|
||||
),
|
||||
i3.ClosedTabTombstoneData,
|
||||
i0.PrefetchHooks Function()
|
||||
> {
|
||||
$ClosedTabTombstoneTableManager(
|
||||
i0.GeneratedDatabase db,
|
||||
i3.ClosedTabTombstone table,
|
||||
) : super(
|
||||
i0.TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
i3.$ClosedTabTombstoneFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
i3.$ClosedTabTombstoneOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
i3.$ClosedTabTombstoneAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback:
|
||||
({
|
||||
i0.Value<String> tabId = const i0.Value.absent(),
|
||||
i0.Value<DateTime> closedAt = const i0.Value.absent(),
|
||||
i0.Value<int> rowid = const i0.Value.absent(),
|
||||
}) => i3.ClosedTabTombstoneCompanion(
|
||||
tabId: tabId,
|
||||
closedAt: closedAt,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
required String tabId,
|
||||
required DateTime closedAt,
|
||||
i0.Value<int> rowid = const i0.Value.absent(),
|
||||
}) => i3.ClosedTabTombstoneCompanion.insert(
|
||||
tabId: tabId,
|
||||
closedAt: closedAt,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
typedef $ClosedTabTombstoneProcessedTableManager =
|
||||
i0.ProcessedTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i3.ClosedTabTombstone,
|
||||
i3.ClosedTabTombstoneData,
|
||||
i3.$ClosedTabTombstoneFilterComposer,
|
||||
i3.$ClosedTabTombstoneOrderingComposer,
|
||||
i3.$ClosedTabTombstoneAnnotationComposer,
|
||||
$ClosedTabTombstoneCreateCompanionBuilder,
|
||||
$ClosedTabTombstoneUpdateCompanionBuilder,
|
||||
(
|
||||
i3.ClosedTabTombstoneData,
|
||||
i0.BaseReferences<
|
||||
i0.GeneratedDatabase,
|
||||
i3.ClosedTabTombstone,
|
||||
i3.ClosedTabTombstoneData
|
||||
>,
|
||||
),
|
||||
i3.ClosedTabTombstoneData,
|
||||
i0.PrefetchHooks Function()
|
||||
>;
|
||||
typedef $TabFtsCreateCompanionBuilder =
|
||||
i3.TabFtsCompanion Function({
|
||||
required String title,
|
||||
@@ -2060,6 +2213,196 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
}
|
||||
}
|
||||
|
||||
class ClosedTabTombstone extends i0.Table
|
||||
with i0.TableInfo<ClosedTabTombstone, i3.ClosedTabTombstoneData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
ClosedTabTombstone(this.attachedDatabase, [this._alias]);
|
||||
late final i0.GeneratedColumn<String> tabId = i0.GeneratedColumn<String>(
|
||||
'tab_id',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL',
|
||||
);
|
||||
late final i0.GeneratedColumn<DateTime> closedAt =
|
||||
i0.GeneratedColumn<DateTime>(
|
||||
'closed_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [tabId, closedAt];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'closed_tab_tombstone';
|
||||
@override
|
||||
Set<i0.GeneratedColumn> get $primaryKey => {tabId};
|
||||
@override
|
||||
i3.ClosedTabTombstoneData map(
|
||||
Map<String, dynamic> data, {
|
||||
String? tablePrefix,
|
||||
}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i3.ClosedTabTombstoneData(
|
||||
tabId: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}tab_id'],
|
||||
)!,
|
||||
closedAt: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}closed_at'],
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ClosedTabTombstone createAlias(String alias) {
|
||||
return ClosedTabTombstone(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class ClosedTabTombstoneData extends i0.DataClass
|
||||
implements i0.Insertable<i3.ClosedTabTombstoneData> {
|
||||
final String tabId;
|
||||
final DateTime closedAt;
|
||||
const ClosedTabTombstoneData({required this.tabId, required this.closedAt});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['tab_id'] = i0.Variable<String>(tabId);
|
||||
map['closed_at'] = i0.Variable<DateTime>(closedAt);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory ClosedTabTombstoneData.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
i0.ValueSerializer? serializer,
|
||||
}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return ClosedTabTombstoneData(
|
||||
tabId: serializer.fromJson<String>(json['tab_id']),
|
||||
closedAt: serializer.fromJson<DateTime>(json['closed_at']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'tab_id': serializer.toJson<String>(tabId),
|
||||
'closed_at': serializer.toJson<DateTime>(closedAt),
|
||||
};
|
||||
}
|
||||
|
||||
i3.ClosedTabTombstoneData copyWith({String? tabId, DateTime? closedAt}) =>
|
||||
i3.ClosedTabTombstoneData(
|
||||
tabId: tabId ?? this.tabId,
|
||||
closedAt: closedAt ?? this.closedAt,
|
||||
);
|
||||
ClosedTabTombstoneData copyWithCompanion(
|
||||
i3.ClosedTabTombstoneCompanion data,
|
||||
) {
|
||||
return ClosedTabTombstoneData(
|
||||
tabId: data.tabId.present ? data.tabId.value : this.tabId,
|
||||
closedAt: data.closedAt.present ? data.closedAt.value : this.closedAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('ClosedTabTombstoneData(')
|
||||
..write('tabId: $tabId, ')
|
||||
..write('closedAt: $closedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(tabId, closedAt);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i3.ClosedTabTombstoneData &&
|
||||
other.tabId == this.tabId &&
|
||||
other.closedAt == this.closedAt);
|
||||
}
|
||||
|
||||
class ClosedTabTombstoneCompanion
|
||||
extends i0.UpdateCompanion<i3.ClosedTabTombstoneData> {
|
||||
final i0.Value<String> tabId;
|
||||
final i0.Value<DateTime> closedAt;
|
||||
final i0.Value<int> rowid;
|
||||
const ClosedTabTombstoneCompanion({
|
||||
this.tabId = const i0.Value.absent(),
|
||||
this.closedAt = const i0.Value.absent(),
|
||||
this.rowid = const i0.Value.absent(),
|
||||
});
|
||||
ClosedTabTombstoneCompanion.insert({
|
||||
required String tabId,
|
||||
required DateTime closedAt,
|
||||
this.rowid = const i0.Value.absent(),
|
||||
}) : tabId = i0.Value(tabId),
|
||||
closedAt = i0.Value(closedAt);
|
||||
static i0.Insertable<i3.ClosedTabTombstoneData> custom({
|
||||
i0.Expression<String>? tabId,
|
||||
i0.Expression<DateTime>? closedAt,
|
||||
i0.Expression<int>? rowid,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (tabId != null) 'tab_id': tabId,
|
||||
if (closedAt != null) 'closed_at': closedAt,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
i3.ClosedTabTombstoneCompanion copyWith({
|
||||
i0.Value<String>? tabId,
|
||||
i0.Value<DateTime>? closedAt,
|
||||
i0.Value<int>? rowid,
|
||||
}) {
|
||||
return i3.ClosedTabTombstoneCompanion(
|
||||
tabId: tabId ?? this.tabId,
|
||||
closedAt: closedAt ?? this.closedAt,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (tabId.present) {
|
||||
map['tab_id'] = i0.Variable<String>(tabId.value);
|
||||
}
|
||||
if (closedAt.present) {
|
||||
map['closed_at'] = i0.Variable<DateTime>(closedAt.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = i0.Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('ClosedTabTombstoneCompanion(')
|
||||
..write('tabId: $tabId, ')
|
||||
..write('closedAt: $closedAt, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
i0.Index get idxTabParentContainer => i0.Index(
|
||||
'idx_tab_parent_container',
|
||||
'CREATE INDEX idx_tab_parent_container ON tab (parent_id, container_id)',
|
||||
@@ -2643,6 +2986,23 @@ class DefinitionsDrift extends i9.ModularAccessor {
|
||||
).asyncMap(container.mapFromRow);
|
||||
}
|
||||
|
||||
i0.Selectable<ContainerIdsByContextualIdentitiesResult>
|
||||
containerIdsByContextualIdentities({required List<String> contextIds}) {
|
||||
var $arrayStartIndex = 1;
|
||||
final expandedcontextIds = $expandVar($arrayStartIndex, contextIds.length);
|
||||
$arrayStartIndex += contextIds.length;
|
||||
return customSelect(
|
||||
'SELECT id, CAST(container.metadata ->> \'\$.contextualIdentity\' AS TEXT) AS contextual_identity FROM container WHERE CAST(container.metadata ->> \'\$.contextualIdentity\' AS TEXT) IN ($expandedcontextIds)',
|
||||
variables: [for (var $ in contextIds) i0.Variable<String>($)],
|
||||
readsFrom: {container},
|
||||
).map(
|
||||
(i0.QueryRow row) => ContainerIdsByContextualIdentitiesResult(
|
||||
id: row.read<String>('id'),
|
||||
contextualIdentity: row.read<String>('contextual_identity'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
i0.Selectable<String?> containersToClearOnExit() {
|
||||
return customSelect(
|
||||
'SELECT container.metadata ->> \'\$.contextualIdentity\' AS contextual_identity FROM container WHERE json_extract(container.metadata, \'\$.clearDataOnExit\') = 1 AND container.metadata ->> \'\$.contextualIdentity\' IS NOT NULL',
|
||||
@@ -2727,6 +3087,15 @@ class UnorderedTabDescendantsResult {
|
||||
UnorderedTabDescendantsResult({required this.id, this.parentId});
|
||||
}
|
||||
|
||||
class ContainerIdsByContextualIdentitiesResult {
|
||||
final String id;
|
||||
final String contextualIdentity;
|
||||
ContainerIdsByContextualIdentitiesResult({
|
||||
required this.id,
|
||||
required this.contextualIdentity,
|
||||
});
|
||||
}
|
||||
|
||||
class IsolatedContextContainerPairsResult {
|
||||
final String? isolationContextId;
|
||||
final String? containerId;
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'schema_v5.dart' as v5;
|
||||
import 'schema_v6.dart' as v6;
|
||||
import 'schema_v7.dart' as v7;
|
||||
import 'schema_v8.dart' as v8;
|
||||
import 'schema_v9.dart' as v9;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@@ -30,10 +31,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v7.DatabaseAtV7(db);
|
||||
case 8:
|
||||
return v8.DatabaseAtV8(db);
|
||||
case 9:
|
||||
return v9.DatabaseAtV9(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [2, 3, 4, 5, 6, 7, 8];
|
||||
static const versions = const [2, 3, 4, 5, 6, 7, 8, 9];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user