Schema 11: Playlists.cloudId fuer die einseitige Sicherung
This commit is contained in:
@@ -72,6 +72,10 @@ class Playlists extends Table {
|
||||
IntColumn get updatedAtMs => integer()();
|
||||
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
|
||||
|
||||
/// ID derselben Playlist am Melo-Server, sobald sie einmal gesichert
|
||||
/// wurde. `null` heißt: nur auf diesem Gerät.
|
||||
TextColumn get cloudId => text().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
@@ -148,7 +152,7 @@ class MeloDb extends _$MeloDb {
|
||||
MeloDb([QueryExecutor? executor]) : super(executor ?? _open());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 10;
|
||||
int get schemaVersion => 11;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -190,6 +194,9 @@ class MeloDb extends _$MeloDb {
|
||||
if (from < 10) {
|
||||
await m.createTable(downloads);
|
||||
}
|
||||
if (from < 11) {
|
||||
await m.addColumn(playlists, playlists.cloudId);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -284,6 +291,26 @@ class MeloDb extends _$MeloDb {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Playlist?> playlistById(String id) =>
|
||||
(select(playlists)..where((p) => p.id.equals(id))).getSingleOrNull();
|
||||
|
||||
Future<void> setPlaylistCloudId(String id, String cloudId) async {
|
||||
await (update(playlists)..where((p) => p.id.equals(id)))
|
||||
.write(PlaylistsCompanion(cloudId: Value(cloudId)));
|
||||
}
|
||||
|
||||
/// Wie viele Playlisten es hier gibt — **inklusive Grabsteinen**.
|
||||
///
|
||||
/// Grundlage der Wiederherstellung: nur eine wirklich leere Tabelle gilt
|
||||
/// als Neuinstallation. Wer alle Playlisten selbst gelöscht hat, soll sie
|
||||
/// nicht vom Server zurückbekommen.
|
||||
Future<int> countPlaylists() async {
|
||||
final zaehler = playlists.id.count();
|
||||
final zeile = await (selectOnly(playlists)..addColumns([zaehler]))
|
||||
.getSingle();
|
||||
return zeile.read(zaehler) ?? 0;
|
||||
}
|
||||
|
||||
// === PlaylistSongs ===
|
||||
Stream<List<Song>> watchPlaylistSongs(String playlistId) {
|
||||
final query = select(songs).join([
|
||||
|
||||
@@ -1322,6 +1322,17 @@ class $PlaylistsTable extends Playlists
|
||||
),
|
||||
defaultValue: const Constant(false),
|
||||
);
|
||||
static const VerificationMeta _cloudIdMeta = const VerificationMeta(
|
||||
'cloudId',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<String> cloudId = GeneratedColumn<String>(
|
||||
'cloud_id',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
@@ -1330,6 +1341,7 @@ class $PlaylistsTable extends Playlists
|
||||
createdAtMs,
|
||||
updatedAtMs,
|
||||
deleted,
|
||||
cloudId,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@@ -1393,6 +1405,12 @@ class $PlaylistsTable extends Playlists
|
||||
deleted.isAcceptableOrUnknown(data['deleted']!, _deletedMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('cloud_id')) {
|
||||
context.handle(
|
||||
_cloudIdMeta,
|
||||
cloudId.isAcceptableOrUnknown(data['cloud_id']!, _cloudIdMeta),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -1426,6 +1444,10 @@ class $PlaylistsTable extends Playlists
|
||||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}deleted'],
|
||||
)!,
|
||||
cloudId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}cloud_id'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1442,6 +1464,10 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
final int createdAtMs;
|
||||
final int updatedAtMs;
|
||||
final bool deleted;
|
||||
|
||||
/// ID derselben Playlist am Melo-Server, sobald sie einmal gesichert
|
||||
/// wurde. `null` heißt: nur auf diesem Gerät.
|
||||
final String? cloudId;
|
||||
const Playlist({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -1449,6 +1475,7 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
required this.createdAtMs,
|
||||
required this.updatedAtMs,
|
||||
required this.deleted,
|
||||
this.cloudId,
|
||||
});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
@@ -1461,6 +1488,9 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
map['created_at_ms'] = Variable<int>(createdAtMs);
|
||||
map['updated_at_ms'] = Variable<int>(updatedAtMs);
|
||||
map['deleted'] = Variable<bool>(deleted);
|
||||
if (!nullToAbsent || cloudId != null) {
|
||||
map['cloud_id'] = Variable<String>(cloudId);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -1474,6 +1504,9 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
createdAtMs: Value(createdAtMs),
|
||||
updatedAtMs: Value(updatedAtMs),
|
||||
deleted: Value(deleted),
|
||||
cloudId: cloudId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(cloudId),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1489,6 +1522,7 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
createdAtMs: serializer.fromJson<int>(json['createdAtMs']),
|
||||
updatedAtMs: serializer.fromJson<int>(json['updatedAtMs']),
|
||||
deleted: serializer.fromJson<bool>(json['deleted']),
|
||||
cloudId: serializer.fromJson<String?>(json['cloudId']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
@@ -1501,6 +1535,7 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
'createdAtMs': serializer.toJson<int>(createdAtMs),
|
||||
'updatedAtMs': serializer.toJson<int>(updatedAtMs),
|
||||
'deleted': serializer.toJson<bool>(deleted),
|
||||
'cloudId': serializer.toJson<String?>(cloudId),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1511,6 +1546,7 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
int? createdAtMs,
|
||||
int? updatedAtMs,
|
||||
bool? deleted,
|
||||
Value<String?> cloudId = const Value.absent(),
|
||||
}) => Playlist(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
@@ -1518,6 +1554,7 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
createdAtMs: createdAtMs ?? this.createdAtMs,
|
||||
updatedAtMs: updatedAtMs ?? this.updatedAtMs,
|
||||
deleted: deleted ?? this.deleted,
|
||||
cloudId: cloudId.present ? cloudId.value : this.cloudId,
|
||||
);
|
||||
Playlist copyWithCompanion(PlaylistsCompanion data) {
|
||||
return Playlist(
|
||||
@@ -1533,6 +1570,7 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
? data.updatedAtMs.value
|
||||
: this.updatedAtMs,
|
||||
deleted: data.deleted.present ? data.deleted.value : this.deleted,
|
||||
cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1544,14 +1582,22 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
..write('description: $description, ')
|
||||
..write('createdAtMs: $createdAtMs, ')
|
||||
..write('updatedAtMs: $updatedAtMs, ')
|
||||
..write('deleted: $deleted')
|
||||
..write('deleted: $deleted, ')
|
||||
..write('cloudId: $cloudId')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(id, name, description, createdAtMs, updatedAtMs, deleted);
|
||||
int get hashCode => Object.hash(
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
createdAtMs,
|
||||
updatedAtMs,
|
||||
deleted,
|
||||
cloudId,
|
||||
);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@@ -1561,7 +1607,8 @@ class Playlist extends DataClass implements Insertable<Playlist> {
|
||||
other.description == this.description &&
|
||||
other.createdAtMs == this.createdAtMs &&
|
||||
other.updatedAtMs == this.updatedAtMs &&
|
||||
other.deleted == this.deleted);
|
||||
other.deleted == this.deleted &&
|
||||
other.cloudId == this.cloudId);
|
||||
}
|
||||
|
||||
class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
@@ -1571,6 +1618,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
final Value<int> createdAtMs;
|
||||
final Value<int> updatedAtMs;
|
||||
final Value<bool> deleted;
|
||||
final Value<String?> cloudId;
|
||||
final Value<int> rowid;
|
||||
const PlaylistsCompanion({
|
||||
this.id = const Value.absent(),
|
||||
@@ -1579,6 +1627,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
this.createdAtMs = const Value.absent(),
|
||||
this.updatedAtMs = const Value.absent(),
|
||||
this.deleted = const Value.absent(),
|
||||
this.cloudId = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
PlaylistsCompanion.insert({
|
||||
@@ -1588,6 +1637,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
required int createdAtMs,
|
||||
required int updatedAtMs,
|
||||
this.deleted = const Value.absent(),
|
||||
this.cloudId = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
name = Value(name),
|
||||
@@ -1600,6 +1650,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
Expression<int>? createdAtMs,
|
||||
Expression<int>? updatedAtMs,
|
||||
Expression<bool>? deleted,
|
||||
Expression<String>? cloudId,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
@@ -1609,6 +1660,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
if (createdAtMs != null) 'created_at_ms': createdAtMs,
|
||||
if (updatedAtMs != null) 'updated_at_ms': updatedAtMs,
|
||||
if (deleted != null) 'deleted': deleted,
|
||||
if (cloudId != null) 'cloud_id': cloudId,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
@@ -1620,6 +1672,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
Value<int>? createdAtMs,
|
||||
Value<int>? updatedAtMs,
|
||||
Value<bool>? deleted,
|
||||
Value<String?>? cloudId,
|
||||
Value<int>? rowid,
|
||||
}) {
|
||||
return PlaylistsCompanion(
|
||||
@@ -1629,6 +1682,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
createdAtMs: createdAtMs ?? this.createdAtMs,
|
||||
updatedAtMs: updatedAtMs ?? this.updatedAtMs,
|
||||
deleted: deleted ?? this.deleted,
|
||||
cloudId: cloudId ?? this.cloudId,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
@@ -1654,6 +1708,9 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
if (deleted.present) {
|
||||
map['deleted'] = Variable<bool>(deleted.value);
|
||||
}
|
||||
if (cloudId.present) {
|
||||
map['cloud_id'] = Variable<String>(cloudId.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
@@ -1669,6 +1726,7 @@ class PlaylistsCompanion extends UpdateCompanion<Playlist> {
|
||||
..write('createdAtMs: $createdAtMs, ')
|
||||
..write('updatedAtMs: $updatedAtMs, ')
|
||||
..write('deleted: $deleted, ')
|
||||
..write('cloudId: $cloudId, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
@@ -4216,6 +4274,7 @@ typedef $$PlaylistsTableCreateCompanionBuilder =
|
||||
required int createdAtMs,
|
||||
required int updatedAtMs,
|
||||
Value<bool> deleted,
|
||||
Value<String?> cloudId,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $$PlaylistsTableUpdateCompanionBuilder =
|
||||
@@ -4226,6 +4285,7 @@ typedef $$PlaylistsTableUpdateCompanionBuilder =
|
||||
Value<int> createdAtMs,
|
||||
Value<int> updatedAtMs,
|
||||
Value<bool> deleted,
|
||||
Value<String?> cloudId,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
@@ -4291,6 +4351,11 @@ class $$PlaylistsTableFilterComposer
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get cloudId => $composableBuilder(
|
||||
column: $table.cloudId,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
Expression<bool> playlistSongsRefs(
|
||||
Expression<bool> Function($$PlaylistSongsTableFilterComposer f) f,
|
||||
) {
|
||||
@@ -4355,6 +4420,11 @@ class $$PlaylistsTableOrderingComposer
|
||||
column: $table.deleted,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get cloudId => $composableBuilder(
|
||||
column: $table.cloudId,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$PlaylistsTableAnnotationComposer
|
||||
@@ -4390,6 +4460,9 @@ class $$PlaylistsTableAnnotationComposer
|
||||
GeneratedColumn<bool> get deleted =>
|
||||
$composableBuilder(column: $table.deleted, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get cloudId =>
|
||||
$composableBuilder(column: $table.cloudId, builder: (column) => column);
|
||||
|
||||
Expression<T> playlistSongsRefs<T extends Object>(
|
||||
Expression<T> Function($$PlaylistSongsTableAnnotationComposer a) f,
|
||||
) {
|
||||
@@ -4450,6 +4523,7 @@ class $$PlaylistsTableTableManager
|
||||
Value<int> createdAtMs = const Value.absent(),
|
||||
Value<int> updatedAtMs = const Value.absent(),
|
||||
Value<bool> deleted = const Value.absent(),
|
||||
Value<String?> cloudId = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) => PlaylistsCompanion(
|
||||
id: id,
|
||||
@@ -4458,6 +4532,7 @@ class $$PlaylistsTableTableManager
|
||||
createdAtMs: createdAtMs,
|
||||
updatedAtMs: updatedAtMs,
|
||||
deleted: deleted,
|
||||
cloudId: cloudId,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback:
|
||||
@@ -4468,6 +4543,7 @@ class $$PlaylistsTableTableManager
|
||||
required int createdAtMs,
|
||||
required int updatedAtMs,
|
||||
Value<bool> deleted = const Value.absent(),
|
||||
Value<String?> cloudId = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) => PlaylistsCompanion.insert(
|
||||
id: id,
|
||||
@@ -4476,6 +4552,7 @@ class $$PlaylistsTableTableManager
|
||||
createdAtMs: createdAtMs,
|
||||
updatedAtMs: updatedAtMs,
|
||||
deleted: deleted,
|
||||
cloudId: cloudId,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:drift/drift.dart' show Migrator;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:melo/library/database.dart';
|
||||
|
||||
void main() {
|
||||
test('Bestandsdaten überleben die neue Spalte', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
|
||||
// Den Stand von Schema 10 nachbauen: Tabelle ohne cloud_id, mit Daten.
|
||||
await db.customStatement('DROP TABLE playlist_songs');
|
||||
await db.customStatement('DROP TABLE playlists');
|
||||
await db.customStatement(
|
||||
'CREATE TABLE playlists ('
|
||||
'id TEXT NOT NULL, '
|
||||
'name TEXT NOT NULL, '
|
||||
'description TEXT NULL, '
|
||||
'created_at_ms INTEGER NOT NULL, '
|
||||
'updated_at_ms INTEGER NOT NULL, '
|
||||
'deleted INTEGER NOT NULL DEFAULT 0, '
|
||||
'PRIMARY KEY (id))',
|
||||
);
|
||||
await db.customStatement(
|
||||
"INSERT INTO playlists (id, name, created_at_ms, updated_at_ms) "
|
||||
"VALUES ('alt-1', 'Road Trip', 0, 0)",
|
||||
);
|
||||
|
||||
await Migrator(db).addColumn(db.playlists, db.playlists.cloudId);
|
||||
|
||||
final rows = await db.select(db.playlists).get();
|
||||
expect(rows.single.name, 'Road Trip');
|
||||
expect(rows.single.cloudId, isNull);
|
||||
});
|
||||
|
||||
test('setPlaylistCloudId merkt sich die Server-ID', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
|
||||
final id = await db.createPlaylist('Mix');
|
||||
await db.setPlaylistCloudId(id, '42');
|
||||
|
||||
expect((await db.playlistById(id))!.cloudId, '42');
|
||||
});
|
||||
|
||||
test('countPlaylists zählt auch Grabsteine', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
|
||||
final id = await db.createPlaylist('Mix');
|
||||
await db.deletePlaylist(id);
|
||||
|
||||
// Sonst hielte die Wiederherstellung ein Gerät, auf dem der Nutzer alle
|
||||
// Playlisten gelöscht hat, für eine Neuinstallation.
|
||||
expect(await db.countPlaylists(), 1);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user