add isolated tab feature
This commit is contained in:
@@ -24,6 +24,7 @@ import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/container.drift.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart';
|
||||
|
||||
@@ -75,18 +76,17 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
|
||||
Selectable<String> getAllTabIds({
|
||||
bool includeRegular = true,
|
||||
bool includePrivate = true,
|
||||
bool includeIsolated = true,
|
||||
}) {
|
||||
final query = selectOnly(db.tab)..addColumns([db.tab.id]);
|
||||
|
||||
if (!includeRegular) {
|
||||
query.where(
|
||||
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(false),
|
||||
);
|
||||
}
|
||||
if (!includePrivate) {
|
||||
query.where(
|
||||
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(true),
|
||||
);
|
||||
final excludedModes = <TabModeDbValue>[];
|
||||
if (!includeRegular) excludedModes.add(TabModeDbValue.regular);
|
||||
if (!includePrivate) excludedModes.add(TabModeDbValue.private);
|
||||
if (!includeIsolated) excludedModes.add(TabModeDbValue.isolated);
|
||||
|
||||
if (excludedModes.isNotEmpty) {
|
||||
query.where(db.tab.tabMode.isNotInValues(excludedModes));
|
||||
}
|
||||
|
||||
return query.map((row) => row.read(db.tab.id)!);
|
||||
@@ -96,6 +96,7 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
|
||||
String? containerId, {
|
||||
bool includeRegular = true,
|
||||
bool includePrivate = true,
|
||||
bool includeIsolated = true,
|
||||
}) {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.id])
|
||||
@@ -106,15 +107,13 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
|
||||
)
|
||||
..orderBy([OrderingTerm.asc(db.tab.orderKey)]);
|
||||
|
||||
if (!includeRegular) {
|
||||
query.where(
|
||||
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(false),
|
||||
);
|
||||
}
|
||||
if (!includePrivate) {
|
||||
query.where(
|
||||
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(true),
|
||||
);
|
||||
final excludedModes = <TabModeDbValue>[];
|
||||
if (!includeRegular) excludedModes.add(TabModeDbValue.regular);
|
||||
if (!includePrivate) excludedModes.add(TabModeDbValue.private);
|
||||
if (!includeIsolated) excludedModes.add(TabModeDbValue.isolated);
|
||||
|
||||
if (excludedModes.isNotEmpty) {
|
||||
query.where(db.tab.tabMode.isNotInValues(excludedModes));
|
||||
}
|
||||
|
||||
return query.map((row) => row.read(db.tab.id)!);
|
||||
|
||||
@@ -26,11 +26,22 @@ import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/tab.drift.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class SyncTabsResult {
|
||||
final Set<String> deletedIsolationContextIds;
|
||||
final int deletedCount;
|
||||
|
||||
const SyncTabsResult({
|
||||
required this.deletedIsolationContextIds,
|
||||
required this.deletedCount,
|
||||
});
|
||||
}
|
||||
|
||||
class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
final _undoHistory = <String, TabData>{};
|
||||
Timer? _clearHistoryTimer;
|
||||
@@ -45,12 +56,25 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
SingleOrNullSelectable<TabData> getTabDataById(String id) =>
|
||||
db.tab.select()..where((t) => t.id.equals(id));
|
||||
|
||||
SingleOrNullSelectable<bool?> getTabIsPrivate(String tabId) {
|
||||
SingleOrNullSelectable<TabMode> getTabMode(String tabId) {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.isPrivate])
|
||||
..addColumns([db.tab.tabMode, db.tab.isolationContextId])
|
||||
..where(db.tab.id.equals(tabId));
|
||||
|
||||
return query.map((row) => row.read(db.tab.isPrivate));
|
||||
return query.map(
|
||||
(row) => TabMode.fromDbValue(
|
||||
row.readWithConverter(db.tab.tabMode)!,
|
||||
isolationContextId: row.read(db.tab.isolationContextId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<String?> getTabIsolationContextId(String tabId) {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.isolationContextId])
|
||||
..where(db.tab.id.equals(tabId));
|
||||
|
||||
return query.map((row) => row.read(db.tab.isolationContextId));
|
||||
}
|
||||
|
||||
Selectable<String> getAllTabIds() {
|
||||
@@ -124,18 +148,24 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
|
||||
Future<String> upsertTabTransactional(
|
||||
Future<String> Function() createTab, {
|
||||
required Value<bool?> isPrivate,
|
||||
required Value<String?> parentId,
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
Value<String?> orderKey = const Value.absent(),
|
||||
Value<Uri?> url = const Value.absent(),
|
||||
Value<String?> title = const Value.absent(),
|
||||
Value<TabMode> tabMode = const Value.absent(),
|
||||
}) {
|
||||
return db.transaction(() async {
|
||||
final tabId = await createTab();
|
||||
final currentOrderKey =
|
||||
orderKey.value ??
|
||||
await _generateOrderKey(parentId: parentId, containerId: containerId);
|
||||
final Value<TabModeDbValue> persistedTabMode = tabMode.present
|
||||
? Value(tabMode.value.toDbValue())
|
||||
: const Value.absent();
|
||||
final Value<String?> isolationContextId = tabMode.present
|
||||
? Value(tabMode.value.isolationContextId)
|
||||
: const Value.absent();
|
||||
|
||||
await db.tab.insertOne(
|
||||
TabCompanion.insert(
|
||||
@@ -146,8 +176,9 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
containerId: containerId,
|
||||
url: url,
|
||||
title: title,
|
||||
isPrivate: isPrivate,
|
||||
orderKey: currentOrderKey,
|
||||
tabMode: persistedTabMode,
|
||||
isolationContextId: isolationContextId,
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(old) => TabCompanion(
|
||||
@@ -156,8 +187,9 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
containerId: containerId,
|
||||
url: url,
|
||||
title: title,
|
||||
isPrivate: isPrivate,
|
||||
orderKey: Value.absentIfNull(orderKey.value),
|
||||
tabMode: persistedTabMode,
|
||||
isolationContextId: isolationContextId,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -170,17 +202,23 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
Future<String> insertTab(
|
||||
String tabId, {
|
||||
required TabSource source,
|
||||
required Value<bool?> isPrivate,
|
||||
required Value<String?> parentId,
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
Value<String?> orderKey = const Value.absent(),
|
||||
Value<Uri?> url = const Value.absent(),
|
||||
Value<String?> title = const Value.absent(),
|
||||
Value<TabMode> tabMode = const Value.absent(),
|
||||
}) {
|
||||
return db.transaction(() async {
|
||||
final currentOrderKey =
|
||||
orderKey.value ??
|
||||
await _generateOrderKey(parentId: parentId, containerId: containerId);
|
||||
final Value<TabModeDbValue> persistedTabMode = tabMode.present
|
||||
? Value(tabMode.value.toDbValue())
|
||||
: const Value.absent();
|
||||
final Value<String?> isolationContextId = tabMode.present
|
||||
? Value(tabMode.value.isolationContextId)
|
||||
: const Value.absent();
|
||||
|
||||
await db.tab.insertOne(
|
||||
TabCompanion.insert(
|
||||
@@ -190,9 +228,10 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
timestamp: DateTime.now(),
|
||||
containerId: containerId,
|
||||
orderKey: currentOrderKey,
|
||||
isPrivate: isPrivate,
|
||||
url: url,
|
||||
title: title,
|
||||
tabMode: persistedTabMode,
|
||||
isolationContextId: isolationContextId,
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(old) => TabCompanion(
|
||||
@@ -201,8 +240,9 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
containerId: containerId,
|
||||
url: url,
|
||||
title: title,
|
||||
isPrivate: isPrivate,
|
||||
orderKey: Value.absentIfNull(orderKey.value),
|
||||
tabMode: persistedTabMode,
|
||||
isolationContextId: isolationContextId,
|
||||
),
|
||||
where: (old) => old.source.isSmallerThanValue(source.index),
|
||||
),
|
||||
@@ -301,7 +341,8 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
if (previousState == null ||
|
||||
previousState.url != state.url ||
|
||||
previousState.title != state.title ||
|
||||
previousState.parentId != state.parentId) {
|
||||
previousState.parentId != state.parentId ||
|
||||
previousState.tabMode != state.tabMode) {
|
||||
batch.update(
|
||||
db.tab,
|
||||
TabCompanion(
|
||||
@@ -314,6 +355,12 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
title: (previousState?.title != state.title)
|
||||
? Value(state.title)
|
||||
: const Value.absent(),
|
||||
tabMode: (previousState?.tabMode != state.tabMode)
|
||||
? Value(state.tabMode.toDbValue())
|
||||
: const Value.absent(),
|
||||
isolationContextId: (previousState?.tabMode != state.tabMode)
|
||||
? Value(state.isolationContextId)
|
||||
: const Value.absent(),
|
||||
),
|
||||
where: (t) => t.id.equals(state.id),
|
||||
);
|
||||
@@ -323,12 +370,19 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> syncTabs({required List<String> retainTabIds}) {
|
||||
/// Syncs DB tab rows with the engine's active tab list.
|
||||
/// Returns metadata about deleted rows for follow-up cleanup.
|
||||
Future<SyncTabsResult> syncTabs({required List<String> retainTabIds}) {
|
||||
return db.transaction(() async {
|
||||
final deleted =
|
||||
await (db.tab.delete()..where((t) => t.id.isNotIn(retainTabIds)))
|
||||
.goAndReturn();
|
||||
|
||||
final deletedIsolationContextIds = <String>{
|
||||
for (final tab in deleted)
|
||||
if (tab.isolationContextId != null) tab.isolationContextId!,
|
||||
};
|
||||
|
||||
if (deleted.isNotEmpty) {
|
||||
_clearHistoryTimer?.cancel();
|
||||
|
||||
@@ -361,6 +415,11 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
}),
|
||||
onConflict: DoNothing(),
|
||||
);
|
||||
|
||||
return SyncTabsResult(
|
||||
deletedIsolationContextIds: deletedIsolationContextIds,
|
||||
deletedCount: deleted.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -391,6 +450,19 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
|
||||
}
|
||||
}
|
||||
|
||||
SingleSelectable<int> tabsInIsolationGroup(String contextId) {
|
||||
return db.definitionsDrift.tabsInIsolationGroup(contextId: contextId);
|
||||
}
|
||||
|
||||
Selectable<String?> allIsolationContextIds() {
|
||||
return db.definitionsDrift.allIsolationContextIds();
|
||||
}
|
||||
|
||||
Selectable<IsolatedContextContainerPairsResult>
|
||||
isolatedContextContainerPairs() {
|
||||
return db.definitionsDrift.isolatedContextContainerPairs();
|
||||
}
|
||||
|
||||
Future<List<String>> getUnassignedTabsOlderThan(DateTime threshold) {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.id])
|
||||
|
||||
@@ -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 = 5;
|
||||
final int schemaVersion = 6;
|
||||
|
||||
@override
|
||||
final int ftsTokenLimit = 10;
|
||||
@@ -99,5 +99,21 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
|
||||
await m.drop(schema.tabMaintainParentChainOnDelete);
|
||||
await m.create(schema.tabMaintainParentChainOnDelete);
|
||||
},
|
||||
from5To6: (m, schema) async {
|
||||
await m.alterTable(
|
||||
TableMigration(
|
||||
schema.tab,
|
||||
columnTransformer: {
|
||||
// Backfill tab_mode from is_private: private=true -> 1 (private), else -> 0 (regular)
|
||||
schema.tab.tabMode: const CustomExpression(
|
||||
'CASE WHEN is_private = 1 THEN 1 ELSE 0 END',
|
||||
),
|
||||
},
|
||||
newColumns: [schema.tab.tabMode, schema.tab.isolationContextId],
|
||||
),
|
||||
);
|
||||
|
||||
await m.alterTable(TableMigration(schema.tab));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// dart format width=80
|
||||
import 'package:drift/internal/versioned_schema.dart' as i0;
|
||||
import 'package:drift/drift.dart' as i1;
|
||||
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
|
||||
import 'package:drift/drift.dart'; // GENERATED BY drift_dev, DO NOT MODIFY.
|
||||
|
||||
// GENERATED BY drift_dev, DO NOT MODIFY.
|
||||
// ignore_for_file: type=lint,unused_import
|
||||
//
|
||||
final class Schema3 extends i0.VersionedSchema {
|
||||
Schema3({required super.database}) : super(version: 3);
|
||||
@override
|
||||
@@ -461,10 +462,143 @@ final class Schema5 extends i0.VersionedSchema {
|
||||
);
|
||||
}
|
||||
|
||||
final class Schema6 extends i0.VersionedSchema {
|
||||
Schema6({required super.database}) : super(version: 6);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
container,
|
||||
tab,
|
||||
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 Shape4 tab = Shape4(
|
||||
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_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_13,
|
||||
_column_14,
|
||||
_column_15,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
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 Shape4 extends i0.VersionedTable {
|
||||
Shape4({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get id =>
|
||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get source =>
|
||||
columnsByName['source']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get parentId =>
|
||||
columnsByName['parent_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get containerId =>
|
||||
columnsByName['container_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get orderKey =>
|
||||
columnsByName['order_key']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get url =>
|
||||
columnsByName['url']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get title =>
|
||||
columnsByName['title']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get tabMode =>
|
||||
columnsByName['tab_mode']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get isolationContextId =>
|
||||
columnsByName['isolation_context_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get isProbablyReaderable =>
|
||||
columnsByName['is_probably_readerable']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get extractedContentMarkdown =>
|
||||
columnsByName['extracted_content_markdown']!
|
||||
as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get extractedContentPlain =>
|
||||
columnsByName['extracted_content_plain']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get fullContentMarkdown =>
|
||||
columnsByName['full_content_markdown']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get fullContentPlain =>
|
||||
columnsByName['full_content_plain']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get timestamp =>
|
||||
columnsByName['timestamp']! as i1.GeneratedColumn<int>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<int> _column_17(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'tab_mode',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints: 'NOT NULL DEFAULT 0',
|
||||
defaultValue: const i1.CustomExpression('0'),
|
||||
);
|
||||
i1.GeneratedColumn<String> _column_18(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'isolation_context_id',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i1.DriftSqlType.string,
|
||||
$customConstraints: '',
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
@@ -483,6 +617,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from4To5(migrator, schema);
|
||||
return 5;
|
||||
case 5:
|
||||
final schema = Schema6(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from5To6(migrator, schema);
|
||||
return 6;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
@@ -493,10 +632,12 @@ i1.OnUpgrade stepByStep({
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from2To3: from2To3,
|
||||
from3To4: from3To4,
|
||||
from4To5: from4To5,
|
||||
from5To6: from5To6,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:weblibre/features/geckoview/features/tabs/data/models/container_
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart';
|
||||
|
||||
CREATE TABLE container (
|
||||
@@ -13,7 +14,7 @@ CREATE TABLE container (
|
||||
metadata TEXT MAPPED BY `const ContainerMetadataConverter()`
|
||||
) WITH ContainerData;
|
||||
|
||||
CREATE TABLE tab(
|
||||
CREATE TABLE tab(
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
source ENUM(TabSource) NOT NULL,
|
||||
parent_id TEXT REFERENCES tab (id) ON DELETE SET NULL,
|
||||
@@ -21,13 +22,18 @@ CREATE TABLE tab(
|
||||
order_key TEXT NOT NULL,
|
||||
url TEXT MAPPED BY `const UriConverterNullable()`,
|
||||
title TEXT,
|
||||
is_private BOOL,
|
||||
tab_mode ENUM(TabModeDbValue) NOT NULL DEFAULT 0,
|
||||
isolation_context_id TEXT,
|
||||
is_probably_readerable BOOL,
|
||||
extracted_content_markdown TEXT,
|
||||
extracted_content_plain TEXT,
|
||||
full_content_markdown TEXT,
|
||||
full_content_plain TEXT,
|
||||
timestamp DATETIME NOT NULL
|
||||
timestamp DATETIME NOT NULL,
|
||||
CHECK (
|
||||
(tab_mode = 2 AND isolation_context_id IS NOT NULL) OR
|
||||
(tab_mode != 2 AND isolation_context_id IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE tab_fts
|
||||
@@ -142,58 +148,58 @@ orderKeyBeforeTab(:tab_id AS TEXT, REQUIRED :container_id AS TEXT OR NULL):
|
||||
|
||||
queryTabsBasic WITH TabQueryResult:
|
||||
WITH weights AS (
|
||||
SELECT
|
||||
SELECT
|
||||
-- Customize these weights (higher = more important)
|
||||
10.0 as title_weight, -- Title matches are most important
|
||||
5.0 as url_weight -- URL matches are quite important
|
||||
)
|
||||
SELECT
|
||||
SELECT
|
||||
t.id,
|
||||
t.container_id,
|
||||
t.is_private,
|
||||
t.tab_mode,
|
||||
t.title,
|
||||
CAST(t.url AS TEXT) AS url,
|
||||
t.url AS clean_url,
|
||||
bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank
|
||||
FROM tab_fts fts
|
||||
INNER JOIN
|
||||
tab t ON t.rowid = fts.rowid
|
||||
tab t ON t.rowid = fts.rowid
|
||||
CROSS JOIN weights
|
||||
WHERE
|
||||
WHERE
|
||||
fts.title LIKE :query OR
|
||||
fts.url LIKE :query
|
||||
ORDER BY
|
||||
ORDER BY
|
||||
weighted_rank ASC,
|
||||
t.timestamp DESC
|
||||
LIMIT :limit;
|
||||
|
||||
queryTabsFullContent WITH TabQueryResult:
|
||||
WITH weights AS (
|
||||
SELECT
|
||||
SELECT
|
||||
-- Customize these weights (higher = more important)
|
||||
10.0 as title_weight, -- Title matches are most important
|
||||
5.0 as url_weight, -- URL matches are quite important
|
||||
3.0 as extracted_weight, -- Extracted content matches
|
||||
1.0 as full_weight -- Full content matches less important
|
||||
)
|
||||
SELECT
|
||||
SELECT
|
||||
t.id,
|
||||
t.container_id,
|
||||
t.is_private,
|
||||
t.tab_mode,
|
||||
highlight(tab_fts, 0, :beforeMatch, :afterMatch) AS title,
|
||||
highlight(tab_fts, 1, :beforeMatch, :afterMatch) AS url,
|
||||
snippet(tab_fts, 2, :beforeMatch, :afterMatch, :ellipsis, :snippetLength) AS extracted_content,
|
||||
snippet(tab_fts, 3, :beforeMatch, :afterMatch, :ellipsis, :snippetLength) AS full_content,
|
||||
t.url AS clean_url,
|
||||
(
|
||||
bm25(tab_fts, weights.title_weight, weights.url_weight,
|
||||
bm25(tab_fts, weights.title_weight, weights.url_weight,
|
||||
weights.extracted_weight, weights.full_weight)
|
||||
) AS weighted_rank
|
||||
FROM tab_fts(:query) fts
|
||||
INNER JOIN
|
||||
tab t ON t.rowid = fts.rowid
|
||||
CROSS JOIN weights
|
||||
ORDER BY
|
||||
ORDER BY
|
||||
weighted_rank ASC,
|
||||
t.timestamp DESC
|
||||
LIMIT :limit;
|
||||
@@ -334,3 +340,16 @@ containersToClearOnExit:
|
||||
WHERE
|
||||
json_extract(container.metadata, '$.clearDataOnExit') = 1
|
||||
AND container.metadata ->> '$.contextualIdentity' IS NOT NULL;
|
||||
|
||||
tabsInIsolationGroup:
|
||||
SELECT COUNT(*) AS count FROM tab WHERE isolation_context_id = :contextId;
|
||||
|
||||
allIsolationContextIds:
|
||||
SELECT DISTINCT isolation_context_id FROM tab WHERE isolation_context_id IS NOT NULL;
|
||||
|
||||
isolatedContextContainerPairs:
|
||||
SELECT DISTINCT t.isolation_context_id, t.container_id
|
||||
FROM tab t
|
||||
WHERE t.tab_mode = 2
|
||||
AND t.isolation_context_id IS NOT NULL
|
||||
AND t.container_id IS NOT NULL;
|
||||
|
||||
@@ -11,12 +11,14 @@ import 'package:weblibre/features/geckoview/features/tabs/data/database/converte
|
||||
as i5;
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart'
|
||||
as i6;
|
||||
import 'package:weblibre/data/database/converters/uri.dart' as i7;
|
||||
import 'package:drift/internal/modular.dart' as i8;
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart'
|
||||
as i7;
|
||||
import 'package:weblibre/data/database/converters/uri.dart' as i8;
|
||||
import 'package:drift/internal/modular.dart' as i9;
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart'
|
||||
as i9;
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart'
|
||||
as i10;
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart'
|
||||
as i11;
|
||||
|
||||
typedef $ContainerCreateCompanionBuilder =
|
||||
i3.ContainerCompanion Function({
|
||||
@@ -47,10 +49,10 @@ final class $ContainerReferences
|
||||
static i0.MultiTypedResultKey<i3.Tab, List<i3.TabData>> _tabRefsTable(
|
||||
i0.GeneratedDatabase db,
|
||||
) => i0.MultiTypedResultKey.fromTable(
|
||||
i8.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab'),
|
||||
i9.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab'),
|
||||
aliasName: i0.$_aliasNameGenerator(
|
||||
i8.ReadDatabaseContainer(db).resultSet<i3.Container>('container').id,
|
||||
i8.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab').containerId,
|
||||
i9.ReadDatabaseContainer(db).resultSet<i3.Container>('container').id,
|
||||
i9.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab').containerId,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -58,7 +60,7 @@ final class $ContainerReferences
|
||||
final manager = i3
|
||||
.$TabTableManager(
|
||||
$_db,
|
||||
i8.ReadDatabaseContainer($_db).resultSet<i3.Tab>('tab'),
|
||||
i9.ReadDatabaseContainer($_db).resultSet<i3.Tab>('tab'),
|
||||
)
|
||||
.filter((f) => f.containerId.id.sqlEquals($_itemColumn<String>('id')!));
|
||||
|
||||
@@ -110,7 +112,7 @@ class $ContainerFilterComposer
|
||||
final i3.$TabFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: i8.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
referencedTable: i9.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
getReferencedColumn: (t) => t.containerId,
|
||||
builder:
|
||||
(
|
||||
@@ -119,7 +121,7 @@ class $ContainerFilterComposer
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => i3.$TabFilterComposer(
|
||||
$db: $db,
|
||||
$table: i8.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
$table: i9.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -188,7 +190,7 @@ class $ContainerAnnotationComposer
|
||||
final i3.$TabAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: i8.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
referencedTable: i9.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
getReferencedColumn: (t) => t.containerId,
|
||||
builder:
|
||||
(
|
||||
@@ -197,7 +199,7 @@ class $ContainerAnnotationComposer
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => i3.$TabAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i8.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
$table: i9.ReadDatabaseContainer($db).resultSet<i3.Tab>('tab'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -275,7 +277,7 @@ class $ContainerTableManager
|
||||
db: db,
|
||||
explicitlyWatchedTables: [
|
||||
if (tabRefs)
|
||||
i8.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab'),
|
||||
i9.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab'),
|
||||
],
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
@@ -329,7 +331,8 @@ typedef $TabCreateCompanionBuilder =
|
||||
required String orderKey,
|
||||
i0.Value<Uri?> url,
|
||||
i0.Value<String?> title,
|
||||
i0.Value<bool?> isPrivate,
|
||||
i0.Value<i7.TabModeDbValue> tabMode,
|
||||
i0.Value<String?> isolationContextId,
|
||||
i0.Value<bool?> isProbablyReaderable,
|
||||
i0.Value<String?> extractedContentMarkdown,
|
||||
i0.Value<String?> extractedContentPlain,
|
||||
@@ -347,7 +350,8 @@ typedef $TabUpdateCompanionBuilder =
|
||||
i0.Value<String> orderKey,
|
||||
i0.Value<Uri?> url,
|
||||
i0.Value<String?> title,
|
||||
i0.Value<bool?> isPrivate,
|
||||
i0.Value<i7.TabModeDbValue> tabMode,
|
||||
i0.Value<String?> isolationContextId,
|
||||
i0.Value<bool?> isProbablyReaderable,
|
||||
i0.Value<String?> extractedContentMarkdown,
|
||||
i0.Value<String?> extractedContentPlain,
|
||||
@@ -362,12 +366,12 @@ final class $TabReferences
|
||||
$TabReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static i3.Container _containerIdTable(i0.GeneratedDatabase db) =>
|
||||
i8.ReadDatabaseContainer(db)
|
||||
i9.ReadDatabaseContainer(db)
|
||||
.resultSet<i3.Container>('container')
|
||||
.createAlias(
|
||||
i0.$_aliasNameGenerator(
|
||||
i8.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab').containerId,
|
||||
i8.ReadDatabaseContainer(
|
||||
i9.ReadDatabaseContainer(db).resultSet<i3.Tab>('tab').containerId,
|
||||
i9.ReadDatabaseContainer(
|
||||
db,
|
||||
).resultSet<i3.Container>('container').id,
|
||||
),
|
||||
@@ -379,7 +383,7 @@ final class $TabReferences
|
||||
final manager = i3
|
||||
.$ContainerTableManager(
|
||||
$_db,
|
||||
i8.ReadDatabaseContainer($_db).resultSet<i3.Container>('container'),
|
||||
i9.ReadDatabaseContainer($_db).resultSet<i3.Container>('container'),
|
||||
)
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_containerIdTable($_db));
|
||||
@@ -430,8 +434,14 @@ class $TabFilterComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<bool> get isPrivate => $composableBuilder(
|
||||
column: $table.isPrivate,
|
||||
i0.ColumnWithTypeConverterFilters<i7.TabModeDbValue, i7.TabModeDbValue, int>
|
||||
get tabMode => $composableBuilder(
|
||||
column: $table.tabMode,
|
||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<String> get isolationContextId => $composableBuilder(
|
||||
column: $table.isolationContextId,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
@@ -469,7 +479,7 @@ class $TabFilterComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
final i3.$ContainerFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.containerId,
|
||||
referencedTable: i8.ReadDatabaseContainer(
|
||||
referencedTable: i9.ReadDatabaseContainer(
|
||||
$db,
|
||||
).resultSet<i3.Container>('container'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
@@ -480,7 +490,7 @@ class $TabFilterComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => i3.$ContainerFilterComposer(
|
||||
$db: $db,
|
||||
$table: i8.ReadDatabaseContainer(
|
||||
$table: i9.ReadDatabaseContainer(
|
||||
$db,
|
||||
).resultSet<i3.Container>('container'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
@@ -531,8 +541,13 @@ class $TabOrderingComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<bool> get isPrivate => $composableBuilder(
|
||||
column: $table.isPrivate,
|
||||
i0.ColumnOrderings<int> get tabMode => $composableBuilder(
|
||||
column: $table.tabMode,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<String> get isolationContextId => $composableBuilder(
|
||||
column: $table.isolationContextId,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
@@ -570,7 +585,7 @@ class $TabOrderingComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
final i3.$ContainerOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.containerId,
|
||||
referencedTable: i8.ReadDatabaseContainer(
|
||||
referencedTable: i9.ReadDatabaseContainer(
|
||||
$db,
|
||||
).resultSet<i3.Container>('container'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
@@ -581,7 +596,7 @@ class $TabOrderingComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => i3.$ContainerOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i8.ReadDatabaseContainer(
|
||||
$table: i9.ReadDatabaseContainer(
|
||||
$db,
|
||||
).resultSet<i3.Container>('container'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
@@ -620,8 +635,13 @@ class $TabAnnotationComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
i0.GeneratedColumn<String> get title =>
|
||||
$composableBuilder(column: $table.title, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<bool> get isPrivate =>
|
||||
$composableBuilder(column: $table.isPrivate, builder: (column) => column);
|
||||
i0.GeneratedColumnWithTypeConverter<i7.TabModeDbValue, int> get tabMode =>
|
||||
$composableBuilder(column: $table.tabMode, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get isolationContextId => $composableBuilder(
|
||||
column: $table.isolationContextId,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<bool> get isProbablyReaderable => $composableBuilder(
|
||||
column: $table.isProbablyReaderable,
|
||||
@@ -655,7 +675,7 @@ class $TabAnnotationComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
final i3.$ContainerAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.containerId,
|
||||
referencedTable: i8.ReadDatabaseContainer(
|
||||
referencedTable: i9.ReadDatabaseContainer(
|
||||
$db,
|
||||
).resultSet<i3.Container>('container'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
@@ -666,7 +686,7 @@ class $TabAnnotationComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => i3.$ContainerAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i8.ReadDatabaseContainer(
|
||||
$table: i9.ReadDatabaseContainer(
|
||||
$db,
|
||||
).resultSet<i3.Container>('container'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
@@ -714,7 +734,8 @@ class $TabTableManager
|
||||
i0.Value<String> orderKey = const i0.Value.absent(),
|
||||
i0.Value<Uri?> url = const i0.Value.absent(),
|
||||
i0.Value<String?> title = const i0.Value.absent(),
|
||||
i0.Value<bool?> isPrivate = const i0.Value.absent(),
|
||||
i0.Value<i7.TabModeDbValue> tabMode = const i0.Value.absent(),
|
||||
i0.Value<String?> isolationContextId = const i0.Value.absent(),
|
||||
i0.Value<bool?> isProbablyReaderable = const i0.Value.absent(),
|
||||
i0.Value<String?> extractedContentMarkdown =
|
||||
const i0.Value.absent(),
|
||||
@@ -732,7 +753,8 @@ class $TabTableManager
|
||||
orderKey: orderKey,
|
||||
url: url,
|
||||
title: title,
|
||||
isPrivate: isPrivate,
|
||||
tabMode: tabMode,
|
||||
isolationContextId: isolationContextId,
|
||||
isProbablyReaderable: isProbablyReaderable,
|
||||
extractedContentMarkdown: extractedContentMarkdown,
|
||||
extractedContentPlain: extractedContentPlain,
|
||||
@@ -750,7 +772,8 @@ class $TabTableManager
|
||||
required String orderKey,
|
||||
i0.Value<Uri?> url = const i0.Value.absent(),
|
||||
i0.Value<String?> title = const i0.Value.absent(),
|
||||
i0.Value<bool?> isPrivate = const i0.Value.absent(),
|
||||
i0.Value<i7.TabModeDbValue> tabMode = const i0.Value.absent(),
|
||||
i0.Value<String?> isolationContextId = const i0.Value.absent(),
|
||||
i0.Value<bool?> isProbablyReaderable = const i0.Value.absent(),
|
||||
i0.Value<String?> extractedContentMarkdown =
|
||||
const i0.Value.absent(),
|
||||
@@ -768,7 +791,8 @@ class $TabTableManager
|
||||
orderKey: orderKey,
|
||||
url: url,
|
||||
title: title,
|
||||
isPrivate: isPrivate,
|
||||
tabMode: tabMode,
|
||||
isolationContextId: isolationContextId,
|
||||
isProbablyReaderable: isProbablyReaderable,
|
||||
extractedContentMarkdown: extractedContentMarkdown,
|
||||
extractedContentPlain: extractedContentPlain,
|
||||
@@ -1269,14 +1293,25 @@ class Tab extends i0.Table with i0.TableInfo<Tab, i3.TabData> {
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '',
|
||||
);
|
||||
late final i0.GeneratedColumn<bool> isPrivate = i0.GeneratedColumn<bool>(
|
||||
'is_private',
|
||||
late final i0.GeneratedColumnWithTypeConverter<i7.TabModeDbValue, int>
|
||||
tabMode = i0.GeneratedColumn<int>(
|
||||
'tab_mode',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i0.DriftSqlType.bool,
|
||||
false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '',
|
||||
);
|
||||
$customConstraints: 'NOT NULL DEFAULT 0',
|
||||
defaultValue: const i0.CustomExpression('0'),
|
||||
).withConverter<i7.TabModeDbValue>(i3.Tab.$convertertabMode);
|
||||
late final i0.GeneratedColumn<String> isolationContextId =
|
||||
i0.GeneratedColumn<String>(
|
||||
'isolation_context_id',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '',
|
||||
);
|
||||
late final i0.GeneratedColumn<bool> isProbablyReaderable =
|
||||
i0.GeneratedColumn<bool>(
|
||||
'is_probably_readerable',
|
||||
@@ -1340,7 +1375,8 @@ class Tab extends i0.Table with i0.TableInfo<Tab, i3.TabData> {
|
||||
orderKey,
|
||||
url,
|
||||
title,
|
||||
isPrivate,
|
||||
tabMode,
|
||||
isolationContextId,
|
||||
isProbablyReaderable,
|
||||
extractedContentMarkdown,
|
||||
extractedContentPlain,
|
||||
@@ -1391,9 +1427,15 @@ class Tab extends i0.Table with i0.TableInfo<Tab, i3.TabData> {
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}title'],
|
||||
),
|
||||
isPrivate: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool,
|
||||
data['${effectivePrefix}is_private'],
|
||||
tabMode: i3.Tab.$convertertabMode.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int,
|
||||
data['${effectivePrefix}tab_mode'],
|
||||
)!,
|
||||
),
|
||||
isolationContextId: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}isolation_context_id'],
|
||||
),
|
||||
isProbablyReaderable: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool,
|
||||
@@ -1430,7 +1472,13 @@ class Tab extends i0.Table with i0.TableInfo<Tab, i3.TabData> {
|
||||
static i0.JsonTypeConverter2<i6.TabSource, int, int> $convertersource =
|
||||
const i0.EnumIndexConverter<i6.TabSource>(i6.TabSource.values);
|
||||
static i0.TypeConverter<Uri?, String?> $converterurl =
|
||||
const i7.UriConverterNullable();
|
||||
const i8.UriConverterNullable();
|
||||
static i0.JsonTypeConverter2<i7.TabModeDbValue, int, int> $convertertabMode =
|
||||
const i0.EnumIndexConverter<i7.TabModeDbValue>(i7.TabModeDbValue.values);
|
||||
@override
|
||||
List<String> get customConstraints => const [
|
||||
'CHECK((tab_mode = 2 AND isolation_context_id IS NOT NULL)OR(tab_mode != 2 AND isolation_context_id IS NULL))',
|
||||
];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
@@ -1443,7 +1491,8 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
final String orderKey;
|
||||
final Uri? url;
|
||||
final String? title;
|
||||
final bool? isPrivate;
|
||||
final i7.TabModeDbValue tabMode;
|
||||
final String? isolationContextId;
|
||||
final bool? isProbablyReaderable;
|
||||
final String? extractedContentMarkdown;
|
||||
final String? extractedContentPlain;
|
||||
@@ -1458,7 +1507,8 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
required this.orderKey,
|
||||
this.url,
|
||||
this.title,
|
||||
this.isPrivate,
|
||||
required this.tabMode,
|
||||
this.isolationContextId,
|
||||
this.isProbablyReaderable,
|
||||
this.extractedContentMarkdown,
|
||||
this.extractedContentPlain,
|
||||
@@ -1486,8 +1536,13 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
if (!nullToAbsent || title != null) {
|
||||
map['title'] = i0.Variable<String>(title);
|
||||
}
|
||||
if (!nullToAbsent || isPrivate != null) {
|
||||
map['is_private'] = i0.Variable<bool>(isPrivate);
|
||||
{
|
||||
map['tab_mode'] = i0.Variable<int>(
|
||||
i3.Tab.$convertertabMode.toSql(tabMode),
|
||||
);
|
||||
}
|
||||
if (!nullToAbsent || isolationContextId != null) {
|
||||
map['isolation_context_id'] = i0.Variable<String>(isolationContextId);
|
||||
}
|
||||
if (!nullToAbsent || isProbablyReaderable != null) {
|
||||
map['is_probably_readerable'] = i0.Variable<bool>(isProbablyReaderable);
|
||||
@@ -1527,7 +1582,12 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
orderKey: serializer.fromJson<String>(json['order_key']),
|
||||
url: serializer.fromJson<Uri?>(json['url']),
|
||||
title: serializer.fromJson<String?>(json['title']),
|
||||
isPrivate: serializer.fromJson<bool?>(json['is_private']),
|
||||
tabMode: i3.Tab.$convertertabMode.fromJson(
|
||||
serializer.fromJson<int>(json['tab_mode']),
|
||||
),
|
||||
isolationContextId: serializer.fromJson<String?>(
|
||||
json['isolation_context_id'],
|
||||
),
|
||||
isProbablyReaderable: serializer.fromJson<bool?>(
|
||||
json['is_probably_readerable'],
|
||||
),
|
||||
@@ -1557,7 +1617,10 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
'order_key': serializer.toJson<String>(orderKey),
|
||||
'url': serializer.toJson<Uri?>(url),
|
||||
'title': serializer.toJson<String?>(title),
|
||||
'is_private': serializer.toJson<bool?>(isPrivate),
|
||||
'tab_mode': serializer.toJson<int>(
|
||||
i3.Tab.$convertertabMode.toJson(tabMode),
|
||||
),
|
||||
'isolation_context_id': serializer.toJson<String?>(isolationContextId),
|
||||
'is_probably_readerable': serializer.toJson<bool?>(isProbablyReaderable),
|
||||
'extracted_content_markdown': serializer.toJson<String?>(
|
||||
extractedContentMarkdown,
|
||||
@@ -1579,7 +1642,8 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
String? orderKey,
|
||||
i0.Value<Uri?> url = const i0.Value.absent(),
|
||||
i0.Value<String?> title = const i0.Value.absent(),
|
||||
i0.Value<bool?> isPrivate = const i0.Value.absent(),
|
||||
i7.TabModeDbValue? tabMode,
|
||||
i0.Value<String?> isolationContextId = const i0.Value.absent(),
|
||||
i0.Value<bool?> isProbablyReaderable = const i0.Value.absent(),
|
||||
i0.Value<String?> extractedContentMarkdown = const i0.Value.absent(),
|
||||
i0.Value<String?> extractedContentPlain = const i0.Value.absent(),
|
||||
@@ -1594,7 +1658,10 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
orderKey: orderKey ?? this.orderKey,
|
||||
url: url.present ? url.value : this.url,
|
||||
title: title.present ? title.value : this.title,
|
||||
isPrivate: isPrivate.present ? isPrivate.value : this.isPrivate,
|
||||
tabMode: tabMode ?? this.tabMode,
|
||||
isolationContextId: isolationContextId.present
|
||||
? isolationContextId.value
|
||||
: this.isolationContextId,
|
||||
isProbablyReaderable: isProbablyReaderable.present
|
||||
? isProbablyReaderable.value
|
||||
: this.isProbablyReaderable,
|
||||
@@ -1623,7 +1690,10 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey,
|
||||
url: data.url.present ? data.url.value : this.url,
|
||||
title: data.title.present ? data.title.value : this.title,
|
||||
isPrivate: data.isPrivate.present ? data.isPrivate.value : this.isPrivate,
|
||||
tabMode: data.tabMode.present ? data.tabMode.value : this.tabMode,
|
||||
isolationContextId: data.isolationContextId.present
|
||||
? data.isolationContextId.value
|
||||
: this.isolationContextId,
|
||||
isProbablyReaderable: data.isProbablyReaderable.present
|
||||
? data.isProbablyReaderable.value
|
||||
: this.isProbablyReaderable,
|
||||
@@ -1653,7 +1723,8 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
..write('orderKey: $orderKey, ')
|
||||
..write('url: $url, ')
|
||||
..write('title: $title, ')
|
||||
..write('isPrivate: $isPrivate, ')
|
||||
..write('tabMode: $tabMode, ')
|
||||
..write('isolationContextId: $isolationContextId, ')
|
||||
..write('isProbablyReaderable: $isProbablyReaderable, ')
|
||||
..write('extractedContentMarkdown: $extractedContentMarkdown, ')
|
||||
..write('extractedContentPlain: $extractedContentPlain, ')
|
||||
@@ -1673,7 +1744,8 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
orderKey,
|
||||
url,
|
||||
title,
|
||||
isPrivate,
|
||||
tabMode,
|
||||
isolationContextId,
|
||||
isProbablyReaderable,
|
||||
extractedContentMarkdown,
|
||||
extractedContentPlain,
|
||||
@@ -1692,7 +1764,8 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
|
||||
other.orderKey == this.orderKey &&
|
||||
other.url == this.url &&
|
||||
other.title == this.title &&
|
||||
other.isPrivate == this.isPrivate &&
|
||||
other.tabMode == this.tabMode &&
|
||||
other.isolationContextId == this.isolationContextId &&
|
||||
other.isProbablyReaderable == this.isProbablyReaderable &&
|
||||
other.extractedContentMarkdown == this.extractedContentMarkdown &&
|
||||
other.extractedContentPlain == this.extractedContentPlain &&
|
||||
@@ -1709,7 +1782,8 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
final i0.Value<String> orderKey;
|
||||
final i0.Value<Uri?> url;
|
||||
final i0.Value<String?> title;
|
||||
final i0.Value<bool?> isPrivate;
|
||||
final i0.Value<i7.TabModeDbValue> tabMode;
|
||||
final i0.Value<String?> isolationContextId;
|
||||
final i0.Value<bool?> isProbablyReaderable;
|
||||
final i0.Value<String?> extractedContentMarkdown;
|
||||
final i0.Value<String?> extractedContentPlain;
|
||||
@@ -1725,7 +1799,8 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
this.orderKey = const i0.Value.absent(),
|
||||
this.url = const i0.Value.absent(),
|
||||
this.title = const i0.Value.absent(),
|
||||
this.isPrivate = const i0.Value.absent(),
|
||||
this.tabMode = const i0.Value.absent(),
|
||||
this.isolationContextId = const i0.Value.absent(),
|
||||
this.isProbablyReaderable = const i0.Value.absent(),
|
||||
this.extractedContentMarkdown = const i0.Value.absent(),
|
||||
this.extractedContentPlain = const i0.Value.absent(),
|
||||
@@ -1742,7 +1817,8 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
required String orderKey,
|
||||
this.url = const i0.Value.absent(),
|
||||
this.title = const i0.Value.absent(),
|
||||
this.isPrivate = const i0.Value.absent(),
|
||||
this.tabMode = const i0.Value.absent(),
|
||||
this.isolationContextId = const i0.Value.absent(),
|
||||
this.isProbablyReaderable = const i0.Value.absent(),
|
||||
this.extractedContentMarkdown = const i0.Value.absent(),
|
||||
this.extractedContentPlain = const i0.Value.absent(),
|
||||
@@ -1762,7 +1838,8 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
i0.Expression<String>? orderKey,
|
||||
i0.Expression<String>? url,
|
||||
i0.Expression<String>? title,
|
||||
i0.Expression<bool>? isPrivate,
|
||||
i0.Expression<int>? tabMode,
|
||||
i0.Expression<String>? isolationContextId,
|
||||
i0.Expression<bool>? isProbablyReaderable,
|
||||
i0.Expression<String>? extractedContentMarkdown,
|
||||
i0.Expression<String>? extractedContentPlain,
|
||||
@@ -1779,7 +1856,9 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
if (orderKey != null) 'order_key': orderKey,
|
||||
if (url != null) 'url': url,
|
||||
if (title != null) 'title': title,
|
||||
if (isPrivate != null) 'is_private': isPrivate,
|
||||
if (tabMode != null) 'tab_mode': tabMode,
|
||||
if (isolationContextId != null)
|
||||
'isolation_context_id': isolationContextId,
|
||||
if (isProbablyReaderable != null)
|
||||
'is_probably_readerable': isProbablyReaderable,
|
||||
if (extractedContentMarkdown != null)
|
||||
@@ -1802,7 +1881,8 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
i0.Value<String>? orderKey,
|
||||
i0.Value<Uri?>? url,
|
||||
i0.Value<String?>? title,
|
||||
i0.Value<bool?>? isPrivate,
|
||||
i0.Value<i7.TabModeDbValue>? tabMode,
|
||||
i0.Value<String?>? isolationContextId,
|
||||
i0.Value<bool?>? isProbablyReaderable,
|
||||
i0.Value<String?>? extractedContentMarkdown,
|
||||
i0.Value<String?>? extractedContentPlain,
|
||||
@@ -1819,7 +1899,8 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
orderKey: orderKey ?? this.orderKey,
|
||||
url: url ?? this.url,
|
||||
title: title ?? this.title,
|
||||
isPrivate: isPrivate ?? this.isPrivate,
|
||||
tabMode: tabMode ?? this.tabMode,
|
||||
isolationContextId: isolationContextId ?? this.isolationContextId,
|
||||
isProbablyReaderable: isProbablyReaderable ?? this.isProbablyReaderable,
|
||||
extractedContentMarkdown:
|
||||
extractedContentMarkdown ?? this.extractedContentMarkdown,
|
||||
@@ -1858,8 +1939,15 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
if (title.present) {
|
||||
map['title'] = i0.Variable<String>(title.value);
|
||||
}
|
||||
if (isPrivate.present) {
|
||||
map['is_private'] = i0.Variable<bool>(isPrivate.value);
|
||||
if (tabMode.present) {
|
||||
map['tab_mode'] = i0.Variable<int>(
|
||||
i3.Tab.$convertertabMode.toSql(tabMode.value),
|
||||
);
|
||||
}
|
||||
if (isolationContextId.present) {
|
||||
map['isolation_context_id'] = i0.Variable<String>(
|
||||
isolationContextId.value,
|
||||
);
|
||||
}
|
||||
if (isProbablyReaderable.present) {
|
||||
map['is_probably_readerable'] = i0.Variable<bool>(
|
||||
@@ -1903,7 +1991,8 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
|
||||
..write('orderKey: $orderKey, ')
|
||||
..write('url: $url, ')
|
||||
..write('title: $title, ')
|
||||
..write('isPrivate: $isPrivate, ')
|
||||
..write('tabMode: $tabMode, ')
|
||||
..write('isolationContextId: $isolationContextId, ')
|
||||
..write('isProbablyReaderable: $isProbablyReaderable, ')
|
||||
..write('extractedContentMarkdown: $extractedContentMarkdown, ')
|
||||
..write('extractedContentPlain: $extractedContentPlain, ')
|
||||
@@ -2211,7 +2300,7 @@ i0.Trigger get tabAfterUpdate => i0.Trigger(
|
||||
'tab_after_update',
|
||||
);
|
||||
|
||||
class DefinitionsDrift extends i8.ModularAccessor {
|
||||
class DefinitionsDrift extends i9.ModularAccessor {
|
||||
DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
|
||||
Future<int> optimizeFtsIndex() {
|
||||
return customInsert(
|
||||
@@ -2284,19 +2373,19 @@ class DefinitionsDrift extends i8.ModularAccessor {
|
||||
).map((i0.QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
i0.Selectable<i9.TabQueryResult> queryTabsBasic({
|
||||
i0.Selectable<i10.TabQueryResult> queryTabsBasic({
|
||||
required String query,
|
||||
required int limit,
|
||||
}) {
|
||||
return customSelect(
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight) SELECT t.id, t.container_id, t.is_private, t.title, CAST(t.url AS TEXT) AS url, t.url AS clean_url, bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank FROM tab_fts AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights WHERE fts.title LIKE ?1 OR fts.url LIKE ?1 ORDER BY weighted_rank ASC, t.timestamp DESC LIMIT ?2',
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight) SELECT t.id, t.container_id, t.tab_mode, t.title, CAST(t.url AS TEXT) AS url, t.url AS clean_url, bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank FROM tab_fts AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights WHERE fts.title LIKE ?1 OR fts.url LIKE ?1 ORDER BY weighted_rank ASC, t.timestamp DESC LIMIT ?2',
|
||||
variables: [i0.Variable<String>(query), i0.Variable<int>(limit)],
|
||||
readsFrom: {tab, tabFts},
|
||||
).map(
|
||||
(i0.QueryRow row) => i9.TabQueryResult(
|
||||
(i0.QueryRow row) => i10.TabQueryResult(
|
||||
id: row.read<String>('id'),
|
||||
containerId: row.readNullable<String>('container_id'),
|
||||
isPrivate: row.readNullable<bool>('is_private'),
|
||||
tabMode: i3.Tab.$convertertabMode.fromSql(row.read<int>('tab_mode')),
|
||||
title: row.readNullable<String>('title'),
|
||||
url: row.readNullable<String>('url'),
|
||||
cleanUrl: i3.Tab.$converterurl.fromSql(
|
||||
@@ -2307,7 +2396,7 @@ class DefinitionsDrift extends i8.ModularAccessor {
|
||||
);
|
||||
}
|
||||
|
||||
i0.Selectable<i9.TabQueryResult> queryTabsFullContent({
|
||||
i0.Selectable<i10.TabQueryResult> queryTabsFullContent({
|
||||
required String beforeMatch,
|
||||
required String afterMatch,
|
||||
required String ellipsis,
|
||||
@@ -2316,7 +2405,7 @@ class DefinitionsDrift extends i8.ModularAccessor {
|
||||
required int limit,
|
||||
}) {
|
||||
return customSelect(
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT t.id, t.container_id, t.is_private, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content, t.url AS clean_url,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC LIMIT ?6',
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT t.id, t.container_id, t.tab_mode, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content, t.url AS clean_url,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC LIMIT ?6',
|
||||
variables: [
|
||||
i0.Variable<String>(beforeMatch),
|
||||
i0.Variable<String>(afterMatch),
|
||||
@@ -2327,10 +2416,10 @@ class DefinitionsDrift extends i8.ModularAccessor {
|
||||
],
|
||||
readsFrom: {tab, tabFts},
|
||||
).map(
|
||||
(i0.QueryRow row) => i9.TabQueryResult(
|
||||
(i0.QueryRow row) => i10.TabQueryResult(
|
||||
id: row.read<String>('id'),
|
||||
containerId: row.readNullable<String>('container_id'),
|
||||
isPrivate: row.readNullable<bool>('is_private'),
|
||||
tabMode: i3.Tab.$convertertabMode.fromSql(row.read<int>('tab_mode')),
|
||||
title: row.readNullable<String>('title'),
|
||||
url: row.readNullable<String>('url'),
|
||||
cleanUrl: i3.Tab.$converterurl.fromSql(
|
||||
@@ -2443,13 +2532,13 @@ class DefinitionsDrift extends i8.ModularAccessor {
|
||||
).map((i0.QueryRow row) => row.read<String>('id'));
|
||||
}
|
||||
|
||||
i0.Selectable<i10.SiteAssignment> allAssignedSites() {
|
||||
i0.Selectable<i11.SiteAssignment> allAssignedSites() {
|
||||
return customSelect(
|
||||
'SELECT container.id, COALESCE(container.metadata ->> \'\$.contextualIdentity\', \'general\') AS contextualIdentity, value AS assigned_site FROM container CROSS JOIN json_each(container.metadata, \'\$.assignedSites\')WHERE value IS NOT NULL',
|
||||
variables: [],
|
||||
readsFrom: {container},
|
||||
).map(
|
||||
(i0.QueryRow row) => i10.SiteAssignment(
|
||||
(i0.QueryRow row) => i11.SiteAssignment(
|
||||
id: row.read<String>('id'),
|
||||
contextualIdentity: row.read<String>('contextualIdentity'),
|
||||
assignedSite: row.readNullable<String>('assigned_site'),
|
||||
@@ -2465,14 +2554,46 @@ class DefinitionsDrift extends i8.ModularAccessor {
|
||||
).map((i0.QueryRow row) => row.readNullable<String>('contextual_identity'));
|
||||
}
|
||||
|
||||
i3.TabFts get tabFts => i8.ReadDatabaseContainer(
|
||||
i0.Selectable<int> tabsInIsolationGroup({String? contextId}) {
|
||||
return customSelect(
|
||||
'SELECT COUNT(*) AS count FROM tab WHERE isolation_context_id = ?1',
|
||||
variables: [i0.Variable<String>(contextId)],
|
||||
readsFrom: {tab},
|
||||
).map((i0.QueryRow row) => row.read<int>('count'));
|
||||
}
|
||||
|
||||
i0.Selectable<String?> allIsolationContextIds() {
|
||||
return customSelect(
|
||||
'SELECT DISTINCT isolation_context_id FROM tab WHERE isolation_context_id IS NOT NULL',
|
||||
variables: [],
|
||||
readsFrom: {tab},
|
||||
).map(
|
||||
(i0.QueryRow row) => row.readNullable<String>('isolation_context_id'),
|
||||
);
|
||||
}
|
||||
|
||||
i0.Selectable<IsolatedContextContainerPairsResult>
|
||||
isolatedContextContainerPairs() {
|
||||
return customSelect(
|
||||
'SELECT DISTINCT t.isolation_context_id, t.container_id FROM tab AS t WHERE t.tab_mode = 2 AND t.isolation_context_id IS NOT NULL AND t.container_id IS NOT NULL',
|
||||
variables: [],
|
||||
readsFrom: {tab},
|
||||
).map(
|
||||
(i0.QueryRow row) => IsolatedContextContainerPairsResult(
|
||||
isolationContextId: row.readNullable<String>('isolation_context_id'),
|
||||
containerId: row.readNullable<String>('container_id'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
i3.TabFts get tabFts => i9.ReadDatabaseContainer(
|
||||
attachedDatabase,
|
||||
).resultSet<i3.TabFts>('tab_fts');
|
||||
i3.Container get container => i8.ReadDatabaseContainer(
|
||||
i3.Container get container => i9.ReadDatabaseContainer(
|
||||
attachedDatabase,
|
||||
).resultSet<i3.Container>('container');
|
||||
i3.Tab get tab =>
|
||||
i8.ReadDatabaseContainer(attachedDatabase).resultSet<i3.Tab>('tab');
|
||||
i9.ReadDatabaseContainer(attachedDatabase).resultSet<i3.Tab>('tab');
|
||||
}
|
||||
|
||||
class TabTreesResult {
|
||||
@@ -2493,3 +2614,12 @@ class UnorderedTabDescendantsResult {
|
||||
final String? parentId;
|
||||
UnorderedTabDescendantsResult({required this.id, this.parentId});
|
||||
}
|
||||
|
||||
class IsolatedContextContainerPairsResult {
|
||||
final String? isolationContextId;
|
||||
final String? containerId;
|
||||
IsolatedContextContainerPairsResult({
|
||||
this.isolationContextId,
|
||||
this.containerId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:weblibre/core/uuid.dart';
|
||||
|
||||
const _isolatedPrefix = 'iso1_';
|
||||
|
||||
/// Generates a new unique isolation context ID in the format `iso1_<uuid-v4>`.
|
||||
String newIsolatedContextId() {
|
||||
final id = uuid.v4();
|
||||
return '$_isolatedPrefix$id';
|
||||
}
|
||||
|
||||
/// Returns `true` if the given context ID identifies an isolated tab context.
|
||||
bool isIsolatedContextId(String? contextId) {
|
||||
if (contextId == null) return false;
|
||||
return contextId.startsWith(_isolatedPrefix);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/isolation_context.dart';
|
||||
|
||||
/// Persisted tab privacy/isolation mode.
|
||||
///
|
||||
/// Values map to integer values stored in the `tab_mode` column:
|
||||
/// - 0 = regular
|
||||
/// - 1 = private
|
||||
/// - 2 = isolated
|
||||
enum TabModeDbValue { regular, private, isolated }
|
||||
|
||||
sealed class TabMode {
|
||||
static const TabMode regular = RegularTabMode();
|
||||
static const TabMode private = PrivateTabMode();
|
||||
|
||||
const TabMode();
|
||||
|
||||
factory TabMode.isolated(String isolationContextId) =>
|
||||
IsolatedTabMode(isolationContextId);
|
||||
|
||||
factory TabMode.newIsolated() => IsolatedTabMode(newIsolatedContextId());
|
||||
|
||||
factory TabMode.fromTabType(TabType tabType) => switch (tabType) {
|
||||
TabType.private => TabMode.private,
|
||||
TabType.isolated => TabMode.newIsolated(),
|
||||
_ => TabMode.regular,
|
||||
};
|
||||
|
||||
TabModeDbValue toDbValue() => switch (this) {
|
||||
RegularTabMode() => TabModeDbValue.regular,
|
||||
PrivateTabMode() => TabModeDbValue.private,
|
||||
IsolatedTabMode() => TabModeDbValue.isolated,
|
||||
};
|
||||
|
||||
String? get isolationContextId => switch (this) {
|
||||
IsolatedTabMode(:final isolationContextId) => isolationContextId,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
TabType toTabType() => switch (this) {
|
||||
RegularTabMode() => TabType.regular,
|
||||
PrivateTabMode() => TabType.private,
|
||||
IsolatedTabMode() => TabType.isolated,
|
||||
};
|
||||
|
||||
factory TabMode.fromDbValue(
|
||||
TabModeDbValue dbValue, {
|
||||
required String? isolationContextId,
|
||||
}) {
|
||||
return switch (dbValue) {
|
||||
TabModeDbValue.regular => regular,
|
||||
TabModeDbValue.private => private,
|
||||
TabModeDbValue.isolated when isolationContextId != null =>
|
||||
TabMode.isolated(isolationContextId),
|
||||
TabModeDbValue.isolated => regular,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is TabMode &&
|
||||
other.toDbValue() == toDbValue() &&
|
||||
other.isolationContextId == isolationContextId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(toDbValue(), isolationContextId);
|
||||
}
|
||||
|
||||
final class RegularTabMode extends TabMode {
|
||||
const RegularTabMode();
|
||||
}
|
||||
|
||||
final class PrivateTabMode extends TabMode {
|
||||
const PrivateTabMode();
|
||||
}
|
||||
|
||||
final class IsolatedTabMode extends TabMode {
|
||||
@override
|
||||
final String isolationContextId;
|
||||
|
||||
const IsolatedTabMode(this.isolationContextId);
|
||||
}
|
||||
@@ -18,11 +18,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||
|
||||
class TabQueryResult with FastEquatable {
|
||||
final String id;
|
||||
final String? containerId;
|
||||
final bool? isPrivate;
|
||||
final TabModeDbValue tabMode;
|
||||
|
||||
final String? title;
|
||||
final Uri? cleanUrl;
|
||||
@@ -36,7 +37,7 @@ class TabQueryResult with FastEquatable {
|
||||
TabQueryResult({
|
||||
required this.id,
|
||||
required this.containerId,
|
||||
required this.isPrivate,
|
||||
required this.tabMode,
|
||||
required this.title,
|
||||
required this.url,
|
||||
required this.cleanUrl,
|
||||
@@ -49,7 +50,7 @@ class TabQueryResult with FastEquatable {
|
||||
List<Object?> get hashParameters => [
|
||||
id,
|
||||
containerId,
|
||||
isPrivate,
|
||||
tabMode,
|
||||
title,
|
||||
cleanUrl,
|
||||
url,
|
||||
|
||||
Reference in New Issue
Block a user