mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-05 19:21:23 -06:00
[client] Add bookmarks
This commit is contained in:
5
client/lib/libphylum/actions/bookmark_action.dart
Normal file
5
client/lib/libphylum/actions/bookmark_action.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
import 'package:phylum/libphylum/phylum_api_types.dart';
|
||||
|
||||
mixin BookmarkAction on PhylumAction {
|
||||
String get resourceId;
|
||||
}
|
||||
93
client/lib/libphylum/actions/bookmark_add_action.dart
Normal file
93
client/lib/libphylum/actions/bookmark_add_action.dart
Normal 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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
26
client/lib/libphylum/db/sql/resources.drift
Normal file
26
client/lib/libphylum/db/sql/resources.drift
Normal 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;
|
||||
4
client/lib/libphylum/db/sql/users.drift
Normal file
4
client/lib/libphylum/db/sql/users.drift
Normal file
@@ -0,0 +1,4 @@
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
username TEXT NOT NULL PRIMARY KEY,
|
||||
display TEXT NOT NULL
|
||||
);
|
||||
@@ -1,4 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS "users"(
|
||||
"username" TEXT NOT NULL PRIMARY KEY,
|
||||
"display" TEXT NOT NULL,
|
||||
);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user