[client] Add bookmarks

This commit is contained in:
Abhishek Shroff
2024-11-21 13:06:33 +05:30
parent 024e78bb37
commit 32536c268e
15 changed files with 481 additions and 77 deletions

View File

@@ -0,0 +1,5 @@
import 'package:phylum/libphylum/phylum_api_types.dart';
mixin BookmarkAction on PhylumAction {
String get resourceId;
}

View File

@@ -0,0 +1,93 @@
import 'dart:async';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/actions/bookmark_action.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
class BookmarkAddAction extends PhylumAction with JsonApiAction, BookmarkAction {
static const actionName = 'bookmarkAdd';
@override
String get name => actionName;
@override
String get method => 'POST';
@override
String get endpoint => '/api/v1/my/bookmarks/add/$resourceId?${Uri.encodeComponent(bookmarkName)}';
@override
final String resourceId;
final String bookmarkName;
final int createdMillis;
BookmarkAddAction._({required this.resourceId, required this.bookmarkName, required this.createdMillis});
BookmarkAddAction({
required Resource r,
String? name,
}) : this._(
resourceId: r.id,
bookmarkName: name ?? r.name,
createdMillis: DateTime.now().millisecondsSinceEpoch,
);
static BookmarkAddAction fromMap(Map<String, dynamic> map) {
return BookmarkAddAction._(
resourceId: map['resourceId'],
bookmarkName: map['bookmarkName'],
createdMillis: map['createdMillis'],
);
}
@override
String generateDescription(PhylumAccount account) {
return 'Bookmarking $bookmarkName';
}
@override
Map<String, dynamic>? generateRequestBody() => null;
@override
Future<void> initialize(PhylumAccount account) {
return account.resourceRepository.trackServerData(resourceId);
}
@override
Future<void> dispose(PhylumAccount account) {
return account.resourceRepository.stopTrackingServerData(resourceId);
}
@override
Future<void> applyOptimisticUpdate(PhylumAccount account) {
return account.bookmarkRepository.create(Bookmark(
resourceId: resourceId,
name: name,
created: createdMillis,
deleted: false,
));
}
@override
Future<void> revertOptimisticUpdate(PhylumAccount account) {
return account.bookmarkRepository.delete(resourceId);
}
@override
Map<String, dynamic> toMap() => {
'resourceId': resourceId,
'bookmarkName': bookmarkName,
'createdMillis': createdMillis,
};
@override
FutureOr<void> processResponse(PhylumAccount account, ApiResponse response) async {
if (response is PhylumApiSuccessResponse) {
await account.bookmarkRepository.processSingleBookmarkResponse(response.data);
}
}
@override
bool dependsOn(OfflineAction<PhylumAccount> action) => action is BookmarkAction && action.resourceId == resourceId;
}

View File

@@ -7,27 +7,36 @@ import 'unsupported.dart' if (dart.library.ffi) 'native.dart' if (dart.library.h
part 'db.g.dart';
@DriftDatabase(include: {
'bookmarks.drift',
'resources.drift',
'users.drift',
'sql/bookmarks.drift',
'sql/resources.drift',
'sql/users.drift',
})
class AppDatabase extends _$AppDatabase {
static Directory? storageDir;
static Directory? tmpDir;
final String id;
bool _cleared = false;
bool get cleared => _cleared;
AppDatabase({required this.id}) : super(_openConnection(id));
@override
int get schemaVersion => 13;
int get schemaVersion => 27;
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (m) => m.createAll(),
onUpgrade: (m, from, to) async {
await m.drop(resources);
await m.createAll();
});
onCreate: (m) {
_cleared = true;
return m.createAll();
},
onUpgrade: (m, from, to) async {
_cleared = true;
await m.drop(resources);
await m.drop(users);
await m.drop(bookmarks);
await m.createAll();
},
);
Future<void> dropDatabase() async {
await super.close();

View File

@@ -870,6 +870,13 @@ class Bookmarks extends Table with TableInfo<Bookmarks, Bookmark> {
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
static const VerificationMeta _createdMeta =
const VerificationMeta('created');
late final GeneratedColumn<int> created = GeneratedColumn<int>(
'created', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
static const VerificationMeta _deletedMeta =
const VerificationMeta('deleted');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
@@ -878,7 +885,7 @@ class Bookmarks extends Table with TableInfo<Bookmarks, Bookmark> {
requiredDuringInsert: true,
$customConstraints: 'NOT NULL CHECK (deleted IN (0, 1))');
@override
List<GeneratedColumn> get $columns => [resourceId, name, deleted];
List<GeneratedColumn> get $columns => [resourceId, name, created, deleted];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -903,6 +910,12 @@ class Bookmarks extends Table with TableInfo<Bookmarks, Bookmark> {
} else if (isInserting) {
context.missing(_nameMeta);
}
if (data.containsKey('created')) {
context.handle(_createdMeta,
created.isAcceptableOrUnknown(data['created']!, _createdMeta));
} else if (isInserting) {
context.missing(_createdMeta);
}
if (data.containsKey('deleted')) {
context.handle(_deletedMeta,
deleted.isAcceptableOrUnknown(data['deleted']!, _deletedMeta));
@@ -922,6 +935,8 @@ class Bookmarks extends Table with TableInfo<Bookmarks, Bookmark> {
.read(DriftSqlType.string, data['${effectivePrefix}resource_id'])!,
name: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
created: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}created'])!,
deleted: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}deleted'])!,
);
@@ -939,14 +954,19 @@ class Bookmarks extends Table with TableInfo<Bookmarks, Bookmark> {
class Bookmark extends DataClass implements Insertable<Bookmark> {
final String resourceId;
final String name;
final int created;
final bool deleted;
const Bookmark(
{required this.resourceId, required this.name, required this.deleted});
{required this.resourceId,
required this.name,
required this.created,
required this.deleted});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['resource_id'] = Variable<String>(resourceId);
map['name'] = Variable<String>(name);
map['created'] = Variable<int>(created);
map['deleted'] = Variable<bool>(deleted);
return map;
}
@@ -955,6 +975,7 @@ class Bookmark extends DataClass implements Insertable<Bookmark> {
return BookmarksCompanion(
resourceId: Value(resourceId),
name: Value(name),
created: Value(created),
deleted: Value(deleted),
);
}
@@ -965,6 +986,7 @@ class Bookmark extends DataClass implements Insertable<Bookmark> {
return Bookmark(
resourceId: serializer.fromJson<String>(json['resource_id']),
name: serializer.fromJson<String>(json['name']),
created: serializer.fromJson<int>(json['created']),
deleted: serializer.fromJson<bool>(json['deleted']),
);
}
@@ -974,14 +996,17 @@ class Bookmark extends DataClass implements Insertable<Bookmark> {
return <String, dynamic>{
'resource_id': serializer.toJson<String>(resourceId),
'name': serializer.toJson<String>(name),
'created': serializer.toJson<int>(created),
'deleted': serializer.toJson<bool>(deleted),
};
}
Bookmark copyWith({String? resourceId, String? name, bool? deleted}) =>
Bookmark copyWith(
{String? resourceId, String? name, int? created, bool? deleted}) =>
Bookmark(
resourceId: resourceId ?? this.resourceId,
name: name ?? this.name,
created: created ?? this.created,
deleted: deleted ?? this.deleted,
);
Bookmark copyWithCompanion(BookmarksCompanion data) {
@@ -989,6 +1014,7 @@ class Bookmark extends DataClass implements Insertable<Bookmark> {
resourceId:
data.resourceId.present ? data.resourceId.value : this.resourceId,
name: data.name.present ? data.name.value : this.name,
created: data.created.present ? data.created.value : this.created,
deleted: data.deleted.present ? data.deleted.value : this.deleted,
);
}
@@ -998,50 +1024,58 @@ class Bookmark extends DataClass implements Insertable<Bookmark> {
return (StringBuffer('Bookmark(')
..write('resourceId: $resourceId, ')
..write('name: $name, ')
..write('created: $created, ')
..write('deleted: $deleted')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(resourceId, name, deleted);
int get hashCode => Object.hash(resourceId, name, created, deleted);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is Bookmark &&
other.resourceId == this.resourceId &&
other.name == this.name &&
other.created == this.created &&
other.deleted == this.deleted);
}
class BookmarksCompanion extends UpdateCompanion<Bookmark> {
final Value<String> resourceId;
final Value<String> name;
final Value<int> created;
final Value<bool> deleted;
final Value<int> rowid;
const BookmarksCompanion({
this.resourceId = const Value.absent(),
this.name = const Value.absent(),
this.created = const Value.absent(),
this.deleted = const Value.absent(),
this.rowid = const Value.absent(),
});
BookmarksCompanion.insert({
required String resourceId,
required String name,
required int created,
required bool deleted,
this.rowid = const Value.absent(),
}) : resourceId = Value(resourceId),
name = Value(name),
created = Value(created),
deleted = Value(deleted);
static Insertable<Bookmark> custom({
Expression<String>? resourceId,
Expression<String>? name,
Expression<int>? created,
Expression<bool>? deleted,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (resourceId != null) 'resource_id': resourceId,
if (name != null) 'name': name,
if (created != null) 'created': created,
if (deleted != null) 'deleted': deleted,
if (rowid != null) 'rowid': rowid,
});
@@ -1050,11 +1084,13 @@ class BookmarksCompanion extends UpdateCompanion<Bookmark> {
BookmarksCompanion copyWith(
{Value<String>? resourceId,
Value<String>? name,
Value<int>? created,
Value<bool>? deleted,
Value<int>? rowid}) {
return BookmarksCompanion(
resourceId: resourceId ?? this.resourceId,
name: name ?? this.name,
created: created ?? this.created,
deleted: deleted ?? this.deleted,
rowid: rowid ?? this.rowid,
);
@@ -1069,6 +1105,9 @@ class BookmarksCompanion extends UpdateCompanion<Bookmark> {
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (created.present) {
map['created'] = Variable<int>(created.value);
}
if (deleted.present) {
map['deleted'] = Variable<bool>(deleted.value);
}
@@ -1083,6 +1122,7 @@ class BookmarksCompanion extends UpdateCompanion<Bookmark> {
return (StringBuffer('BookmarksCompanion(')
..write('resourceId: $resourceId, ')
..write('name: $name, ')
..write('created: $created, ')
..write('deleted: $deleted, ')
..write('rowid: $rowid')
..write(')'))
@@ -1114,13 +1154,14 @@ abstract class _$AppDatabase extends GeneratedDatabase {
Selectable<ListBookmarksResult> listBookmarks() {
return customSelect(
'SELECT resource_id, name FROM bookmarks WHERE deleted = 0',
'SELECT resource_id, name, created FROM bookmarks WHERE deleted = 0 ORDER BY created ASC',
variables: [],
readsFrom: {
bookmarks,
}).map((QueryRow row) => ListBookmarksResult(
resourceId: row.read<String>('resource_id'),
name: row.read<String>('name'),
created: row.read<int>('created'),
));
}
@@ -1282,6 +1323,26 @@ typedef $ResourcesUpdateCompanionBuilder = ResourcesCompanion Function({
Value<int> rowid,
});
final class $ResourcesReferences
extends BaseReferences<_$AppDatabase, Resources, Resource> {
$ResourcesReferences(super.$_db, super.$_table, super.$_typedResult);
static MultiTypedResultKey<Bookmarks, List<Bookmark>> _bookmarksRefsTable(
_$AppDatabase db) =>
MultiTypedResultKey.fromTable(db.bookmarks,
aliasName:
$_aliasNameGenerator(db.resources.id, db.bookmarks.resourceId));
$BookmarksProcessedTableManager get bookmarksRefs {
final manager = $BookmarksTableManager($_db, $_db.bookmarks)
.filter((f) => f.resourceId.id($_item.id));
final cache = $_typedResult.readTableOrNull(_bookmarksRefsTable($_db));
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: cache));
}
}
class $ResourcesFilterComposer extends Composer<_$AppDatabase, Resources> {
$ResourcesFilterComposer({
required super.$db,
@@ -1328,6 +1389,27 @@ class $ResourcesFilterComposer extends Composer<_$AppDatabase, Resources> {
ColumnFilters<DateTime> get lastRefresh => $composableBuilder(
column: $table.lastRefresh, builder: (column) => ColumnFilters(column));
Expression<bool> bookmarksRefs(
Expression<bool> Function($BookmarksFilterComposer f) f) {
final $BookmarksFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.bookmarks,
getReferencedColumn: (t) => t.resourceId,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$BookmarksFilterComposer(
$db: $db,
$table: $db.bookmarks,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return f(composer);
}
}
class $ResourcesOrderingComposer extends Composer<_$AppDatabase, Resources> {
@@ -1425,6 +1507,27 @@ class $ResourcesAnnotationComposer extends Composer<_$AppDatabase, Resources> {
GeneratedColumn<DateTime> get lastRefresh => $composableBuilder(
column: $table.lastRefresh, builder: (column) => column);
Expression<T> bookmarksRefs<T extends Object>(
Expression<T> Function($BookmarksAnnotationComposer a) f) {
final $BookmarksAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.bookmarks,
getReferencedColumn: (t) => t.resourceId,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$BookmarksAnnotationComposer(
$db: $db,
$table: $db.bookmarks,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return f(composer);
}
}
class $ResourcesTableManager extends RootTableManager<
@@ -1436,9 +1539,9 @@ class $ResourcesTableManager extends RootTableManager<
$ResourcesAnnotationComposer,
$ResourcesCreateCompanionBuilder,
$ResourcesUpdateCompanionBuilder,
(Resource, BaseReferences<_$AppDatabase, Resources, Resource>),
(Resource, $ResourcesReferences),
Resource,
PrefetchHooks Function()> {
PrefetchHooks Function({bool bookmarksRefs})> {
$ResourcesTableManager(_$AppDatabase db, Resources table)
: super(TableManagerState(
db: db,
@@ -1514,9 +1617,31 @@ class $ResourcesTableManager extends RootTableManager<
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.map((e) =>
(e.readTable(table), $ResourcesReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
prefetchHooksCallback: ({bookmarksRefs = false}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [if (bookmarksRefs) db.bookmarks],
addJoins: null,
getPrefetchedDataCallback: (items) async {
return [
if (bookmarksRefs)
await $_getPrefetchedData(
currentTable: table,
referencedTable:
$ResourcesReferences._bookmarksRefsTable(db),
managerFromTypedResult: (p0) =>
$ResourcesReferences(db, table, p0).bookmarksRefs,
referencedItemsForCurrentItem:
(item, referencedItems) => referencedItems
.where((e) => e.resourceId == item.id),
typedResults: items)
];
},
);
},
));
}
@@ -1529,22 +1654,43 @@ typedef $ResourcesProcessedTableManager = ProcessedTableManager<
$ResourcesAnnotationComposer,
$ResourcesCreateCompanionBuilder,
$ResourcesUpdateCompanionBuilder,
(Resource, BaseReferences<_$AppDatabase, Resources, Resource>),
(Resource, $ResourcesReferences),
Resource,
PrefetchHooks Function()>;
PrefetchHooks Function({bool bookmarksRefs})>;
typedef $BookmarksCreateCompanionBuilder = BookmarksCompanion Function({
required String resourceId,
required String name,
required int created,
required bool deleted,
Value<int> rowid,
});
typedef $BookmarksUpdateCompanionBuilder = BookmarksCompanion Function({
Value<String> resourceId,
Value<String> name,
Value<int> created,
Value<bool> deleted,
Value<int> rowid,
});
final class $BookmarksReferences
extends BaseReferences<_$AppDatabase, Bookmarks, Bookmark> {
$BookmarksReferences(super.$_db, super.$_table, super.$_typedResult);
static Resources _resourceIdTable(_$AppDatabase db) =>
db.resources.createAlias(
$_aliasNameGenerator(db.bookmarks.resourceId, db.resources.id));
$ResourcesProcessedTableManager? get resourceId {
if ($_item.resourceId == null) return null;
final manager = $ResourcesTableManager($_db, $_db.resources)
.filter((f) => f.id($_item.resourceId!));
final item = $_typedResult.readTableOrNull(_resourceIdTable($_db));
if (item == null) return manager;
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: [item]));
}
}
class $BookmarksFilterComposer extends Composer<_$AppDatabase, Bookmarks> {
$BookmarksFilterComposer({
required super.$db,
@@ -1553,14 +1699,34 @@ class $BookmarksFilterComposer extends Composer<_$AppDatabase, Bookmarks> {
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<String> get resourceId => $composableBuilder(
column: $table.resourceId, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnFilters(column));
ColumnFilters<int> get created => $composableBuilder(
column: $table.created, builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get deleted => $composableBuilder(
column: $table.deleted, builder: (column) => ColumnFilters(column));
$ResourcesFilterComposer get resourceId {
final $ResourcesFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.resourceId,
referencedTable: $db.resources,
getReferencedColumn: (t) => t.id,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$ResourcesFilterComposer(
$db: $db,
$table: $db.resources,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return composer;
}
}
class $BookmarksOrderingComposer extends Composer<_$AppDatabase, Bookmarks> {
@@ -1571,14 +1737,34 @@ class $BookmarksOrderingComposer extends Composer<_$AppDatabase, Bookmarks> {
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<String> get resourceId => $composableBuilder(
column: $table.resourceId, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get created => $composableBuilder(
column: $table.created, builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get deleted => $composableBuilder(
column: $table.deleted, builder: (column) => ColumnOrderings(column));
$ResourcesOrderingComposer get resourceId {
final $ResourcesOrderingComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.resourceId,
referencedTable: $db.resources,
getReferencedColumn: (t) => t.id,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$ResourcesOrderingComposer(
$db: $db,
$table: $db.resources,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return composer;
}
}
class $BookmarksAnnotationComposer extends Composer<_$AppDatabase, Bookmarks> {
@@ -1589,14 +1775,34 @@ class $BookmarksAnnotationComposer extends Composer<_$AppDatabase, Bookmarks> {
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<String> get resourceId => $composableBuilder(
column: $table.resourceId, builder: (column) => column);
GeneratedColumn<String> get name =>
$composableBuilder(column: $table.name, builder: (column) => column);
GeneratedColumn<int> get created =>
$composableBuilder(column: $table.created, builder: (column) => column);
GeneratedColumn<bool> get deleted =>
$composableBuilder(column: $table.deleted, builder: (column) => column);
$ResourcesAnnotationComposer get resourceId {
final $ResourcesAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.resourceId,
referencedTable: $db.resources,
getReferencedColumn: (t) => t.id,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$ResourcesAnnotationComposer(
$db: $db,
$table: $db.resources,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return composer;
}
}
class $BookmarksTableManager extends RootTableManager<
@@ -1608,9 +1814,9 @@ class $BookmarksTableManager extends RootTableManager<
$BookmarksAnnotationComposer,
$BookmarksCreateCompanionBuilder,
$BookmarksUpdateCompanionBuilder,
(Bookmark, BaseReferences<_$AppDatabase, Bookmarks, Bookmark>),
(Bookmark, $BookmarksReferences),
Bookmark,
PrefetchHooks Function()> {
PrefetchHooks Function({bool resourceId})> {
$BookmarksTableManager(_$AppDatabase db, Bookmarks table)
: super(TableManagerState(
db: db,
@@ -1624,31 +1830,69 @@ class $BookmarksTableManager extends RootTableManager<
updateCompanionCallback: ({
Value<String> resourceId = const Value.absent(),
Value<String> name = const Value.absent(),
Value<int> created = const Value.absent(),
Value<bool> deleted = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
BookmarksCompanion(
resourceId: resourceId,
name: name,
created: created,
deleted: deleted,
rowid: rowid,
),
createCompanionCallback: ({
required String resourceId,
required String name,
required int created,
required bool deleted,
Value<int> rowid = const Value.absent(),
}) =>
BookmarksCompanion.insert(
resourceId: resourceId,
name: name,
created: created,
deleted: deleted,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.map((e) =>
(e.readTable(table), $BookmarksReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
prefetchHooksCallback: ({resourceId = false}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [],
addJoins: <
T extends TableManagerState<
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic>>(state) {
if (resourceId) {
state = state.withJoin(
currentTable: table,
currentColumn: table.resourceId,
referencedTable: $BookmarksReferences._resourceIdTable(db),
referencedColumn:
$BookmarksReferences._resourceIdTable(db).id,
) as T;
}
return state;
},
getPrefetchedDataCallback: (items) async {
return [];
},
);
},
));
}
@@ -1661,9 +1905,9 @@ typedef $BookmarksProcessedTableManager = ProcessedTableManager<
$BookmarksAnnotationComposer,
$BookmarksCreateCompanionBuilder,
$BookmarksUpdateCompanionBuilder,
(Bookmark, BaseReferences<_$AppDatabase, Bookmarks, Bookmark>),
(Bookmark, $BookmarksReferences),
Bookmark,
PrefetchHooks Function()>;
PrefetchHooks Function({bool resourceId})>;
class $AppDatabaseManager {
final _$AppDatabase _db;
@@ -1691,8 +1935,10 @@ class ParentsResult {
class ListBookmarksResult {
final String resourceId;
final String name;
final int created;
ListBookmarksResult({
required this.resourceId,
required this.name,
required this.created,
});
}

View File

@@ -10,12 +10,10 @@ QueryExecutor openDatabase({
required Directory? tmpDir,
required String id,
}) {
return LazyDatabase(() async {
final file = _dbFile(storageDir, id);
sqlite3.tempDirectory = tmpDir!.path;
final file = _dbFile(storageDir, id);
sqlite3.tempDirectory = tmpDir!.path;
return NativeDatabase.createInBackground(file);
});
return NativeDatabase.createInBackground(file);
}
Future<void> deleteDatabase({

View File

@@ -1,25 +0,0 @@
CREATE TABLE IF NOT EXISTS "resources"(
"id" TEXT NOT NULL PRIMARY KEY,
"parent" TEXT REFERENCES resources (id),
"name" TEXT NOT NULL,
"dir" BOOLEAN NOT NULL CHECK ("dir" IN (0, 1)),
"created" DATETIME,
"modified" DATETIME,
"deleted" BOOLEAN NOT NULL DEFAULT 0 CHECK ("deleted" IN (0, 1)),
"content_size" INTEGER NOT NULL DEFAULT 0,
"content_sha256" TEXT NOT NULL DEFAULT '',
"content_type" TEXT NOT NULL DEFAULT '',
"permissions" TEXT,
"publinks" INTEGER NOT NULL DEFAULT 0,
"last_refresh" DATETIME
);
parents: WITH RECURSIVE parents(id, parent, name, permissions) AS (
SELECT id, parent, name, permissions
FROM resources
WHERE id = :id
UNION ALL
SELECT r.id, r.parent, r.name, r.permissions
FROM resources r
JOIN parents p
ON r.id = p.parent
) SELECT * from parents;

View File

@@ -1,7 +1,10 @@
import 'resources.drift';
CREATE TABLE IF NOT EXISTS bookmarks(
resource_id TEXT NOT NULL PRIMARY KEY REFERENCES resources(id),
name TEXT NOT NULL,
created INT NOT NULL,
deleted BOOLEAN NOT NULL CHECK (deleted IN (0, 1))
);
listBookmarks: SELECT resource_id, name FROM bookmarks WHERE deleted = 0;
listBookmarks: SELECT resource_id, name, created FROM bookmarks WHERE deleted = 0 ORDER BY created ASC;

View File

@@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS resources(
id TEXT NOT NULL PRIMARY KEY,
parent TEXT REFERENCES resources (id),
name TEXT NOT NULL,
dir BOOLEAN NOT NULL CHECK ("dir" IN (0, 1)),
created DATETIME,
modified DATETIME,
deleted BOOLEAN NOT NULL DEFAULT 0 CHECK ("deleted" IN (0, 1)),
content_size INTEGER NOT NULL DEFAULT 0,
content_sha256 TEXT NOT NULL DEFAULT '',
content_type TEXT NOT NULL DEFAULT '',
permissions TEXT,
publinks INTEGER NOT NULL DEFAULT 0,
last_refresh DATETIME
);
parents: WITH RECURSIVE parents(id, parent, name, permissions) AS (
SELECT id, parent, name, permissions
FROM resources
WHERE id = :id
UNION ALL
SELECT r.id, r.parent, r.name, r.permissions
FROM resources r
JOIN parents p
ON r.id = p.parent
) SELECT * from parents;

View File

@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS users(
username TEXT NOT NULL PRIMARY KEY,
display TEXT NOT NULL
);

View File

@@ -1,4 +0,0 @@
CREATE TABLE IF NOT EXISTS "users"(
"username" TEXT NOT NULL PRIMARY KEY,
"display" TEXT NOT NULL,
);

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
@@ -57,7 +58,11 @@ class PhylumAccount extends Account<PhylumAccount> {
// Set Authorization header
_accessToken = accessToken;
// Open the database to see if data was cleared
await db.bookmarks.count().get();
await resourceRepository.initialize();
await userRepository.initialize(db.cleared);
await bookmarkRepository.initialize(db.cleared);
void Function()? l;
l = apiClient.addResponseListener((request, errorResponse) {

View File

@@ -12,8 +12,23 @@ class BookmarkRepository {
BookmarkRepository({required this.account});
Stream<List<Bookmark>> watch() {
return account.db.bookmarks.select().watch();
Future<void> initialize(bool cleared) async {
if (cleared) {
await account.persist(keyLastBookmarkFetch, null);
}
refresh();
}
Selectable<ListBookmarksResult> list() {
return account.db.listBookmarks();
}
Future<int> create(Bookmark bookmark) {
return account.db.bookmarks.insertOne(bookmark, mode: InsertMode.insertOrReplace);
}
Future<int> delete(String resourceId) {
return account.db.bookmarks.deleteWhere((f) => f.resourceId.equals(resourceId));
}
Future<ApiResult> refresh() async {
@@ -21,13 +36,13 @@ class BookmarkRepository {
BookmarksRequest(since: account.getPersisted(keyLastBookmarkFetch) as int?),
callback: (request, response) async {
if (response is PhylumApiSuccessResponse) {
await parseBookmarksResponse(response.data);
await processBookmarksResponse(response.data);
}
},
);
}
Future parseBookmarksResponse(Map<String, dynamic> data) async {
Future processBookmarksResponse(Map<String, dynamic> data) async {
final db = account.db;
final bookmarks = (data["bookmarks"] as List).cast<Map>().map((u) => parseBookmarkObject(u.cast<String, dynamic>()));
await db.batch((batch) {
@@ -36,10 +51,17 @@ class BookmarkRepository {
account.persist(keyLastBookmarkFetch, data["until"]);
}
Future processSingleBookmarkResponse(Map<String, dynamic> data) async {
final db = account.db;
final bookmark = parseBookmarkObject((data as Map).cast<String, dynamic>());
return db.bookmarks.insertOnConflictUpdate(bookmark);
}
Bookmark parseBookmarkObject(Map<String, dynamic> data) {
return Bookmark(
resourceId: data['resource_id'],
name: data['name'],
created: data['created'],
deleted: data['deleted'],
);
}

View File

@@ -14,6 +14,13 @@ class UserRepository {
account.db.users.select().get().then((users) => _users = Map.unmodifiable(Map.fromIterable(users, key: (u) => u.username)));
}
Future<void> initialize(bool cleared) async {
if (cleared) {
// await account.persist(keyLastUserFetch, null);
}
refresh();
}
Future<ApiResult> refresh() async {
return account.apiClient.sendRequest(UsersListRequest(), callback: (request, response) async {
if (response is PhylumApiSuccessResponse) {

View File

@@ -38,8 +38,8 @@ class NavList extends StatelessWidget {
title: const Text('Home'),
onTap: () => Actions.maybeInvoke(context, const NavHomeIntent()),
),
StreamBuilder<List<Bookmark>>(
stream: context.read<PhylumAccount>().bookmarkRepository.watch(),
StreamBuilder<List<ListBookmarksResult>>(
stream: context.read<PhylumAccount>().bookmarkRepository.list().watch(),
initialData: const [],
builder: (context, snapshot) {
return Theme(
@@ -47,7 +47,8 @@ class NavList extends StatelessWidget {
child: ExpansionTile(
title: Text('Bookmarks'),
leading: Icon(Icons.bookmark),
children: snapshot.data!
initiallyExpanded: true,
children: (snapshot.data ?? const [])
.map((b) => ListTile(
title: Text(b.name),
leading: Icon(null),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:phylum/app_shortcuts.dart';
import 'package:phylum/libphylum/actions/action_resource_delete.dart';
import 'package:phylum/libphylum/actions/action_resource_move.dart';
import 'package:phylum/libphylum/actions/bookmark_add_action.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/common/responsive_dialog.dart';
@@ -130,6 +131,19 @@ void handleResourceOption(BuildContext context, Iterable<Resource> resources, Me
case MenuOption.publinks:
case MenuOption.download:
case MenuOption.bookmark:
assert(resources.length == 1);
final r = resources.first;
final name = await showInputDialog(
context,
autocorrect: false,
title: 'Add Bookmark',
labelText: 'Name',
preset: r.name,
);
if (name == null || !context.mounted) {
return;
}
account.addAction(BookmarkAddAction(r: r, name: name.isEmpty ? '' : name));
break;
case MenuOption.newFolder:
Actions.maybeInvoke(context, const NewFolderIntent());