refactor: extract shared mixins and helpers, drop dead abstractions
Introduces shared seams for paginated views, D-pad reorder, media control routing, async singletons and the device method channel, then points the open-coded copies at them. Also removes unused models and duplicated provider/server plumbing, folds the twice-implemented artifact store in the server, and factors the repeated Flutter toolchain prologue in CI into a composite action.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:plezy/database/app_database.dart';
|
||||
import 'package:plezy/media/ids.dart';
|
||||
|
||||
/// Seeds `downloaded_media` rows at an arbitrary [status] so tests can start
|
||||
/// from completed, downloading, or paused state.
|
||||
///
|
||||
/// Production never writes rows this way — it goes through
|
||||
/// `insertQueuedDownload`, which only ever admits `queued` rows and guards on
|
||||
/// the existing status. This fixture deliberately keeps neither restriction,
|
||||
/// which is why it lives in `test/` instead of `lib/`.
|
||||
extension DownloadFixtures on AppDatabase {
|
||||
Future<void> insertDownload({
|
||||
required ServerId serverId,
|
||||
String? clientScopeId,
|
||||
required String ratingKey,
|
||||
required String globalKey,
|
||||
required String type,
|
||||
String? parentRatingKey,
|
||||
String? grandparentRatingKey,
|
||||
required int status,
|
||||
int mediaIndex = 0,
|
||||
String? mediaSourceId,
|
||||
}) async {
|
||||
await customUpdate(
|
||||
'''
|
||||
INSERT INTO downloaded_media (
|
||||
server_id,
|
||||
client_scope_id,
|
||||
rating_key,
|
||||
global_key,
|
||||
type,
|
||||
parent_rating_key,
|
||||
grandparent_rating_key,
|
||||
status,
|
||||
media_index,
|
||||
media_source_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(global_key) DO UPDATE SET
|
||||
server_id = excluded.server_id,
|
||||
client_scope_id = excluded.client_scope_id,
|
||||
rating_key = excluded.rating_key,
|
||||
type = excluded.type,
|
||||
parent_rating_key = excluded.parent_rating_key,
|
||||
grandparent_rating_key = excluded.grandparent_rating_key,
|
||||
status = excluded.status,
|
||||
progress = 0,
|
||||
total_bytes = NULL,
|
||||
downloaded_bytes = 0,
|
||||
error_message = NULL,
|
||||
retry_count = 0,
|
||||
media_index = excluded.media_index,
|
||||
media_source_id = excluded.media_source_id
|
||||
''',
|
||||
variables: [
|
||||
Variable<String>(serverId),
|
||||
Variable<String>(clientScopeId),
|
||||
Variable<String>(ratingKey),
|
||||
Variable<String>(globalKey),
|
||||
Variable<String>(type),
|
||||
Variable<String>(parentRatingKey),
|
||||
Variable<String>(grandparentRatingKey),
|
||||
Variable<int>(status),
|
||||
Variable<int>(mediaIndex),
|
||||
Variable<String>(mediaSourceId),
|
||||
],
|
||||
updates: {downloadedMedia},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import 'package:drift/drift.dart' hide isNull, isNotNull;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/database/app_database.dart';
|
||||
import 'package:plezy/database/download_operations.dart';
|
||||
import 'package:plezy/media/ids.dart';
|
||||
import 'package:plezy/models/download_models.dart';
|
||||
|
||||
import 'download_fixtures.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase db;
|
||||
|
||||
setUp(() {
|
||||
db = AppDatabase.forTesting(NativeDatabase.memory());
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
group('insertDownload', () {
|
||||
test('inserts a movie row with defaults', () async {
|
||||
await db.insertDownload(
|
||||
serverId: ServerId('srv'),
|
||||
ratingKey: '100',
|
||||
globalKey: 'srv:100',
|
||||
type: 'movie',
|
||||
status: DownloadStatus.queued.index,
|
||||
);
|
||||
|
||||
final rows = await db.select(db.downloadedMedia).get();
|
||||
expect(rows, hasLength(1));
|
||||
final r = rows.first;
|
||||
expect(r.serverId, 'srv');
|
||||
expect(r.ratingKey, '100');
|
||||
expect(r.globalKey, 'srv:100');
|
||||
expect(r.type, 'movie');
|
||||
expect(r.status, DownloadStatus.queued.index);
|
||||
expect(r.parentRatingKey, isNull);
|
||||
expect(r.grandparentRatingKey, isNull);
|
||||
expect(r.mediaIndex, 0);
|
||||
});
|
||||
|
||||
test('inserts an episode with parent and grandparent keys', () async {
|
||||
await db.insertDownload(
|
||||
serverId: ServerId('srv'),
|
||||
ratingKey: 'ep1',
|
||||
globalKey: 'srv:ep1',
|
||||
type: 'episode',
|
||||
parentRatingKey: 'season1',
|
||||
grandparentRatingKey: 'show1',
|
||||
status: DownloadStatus.queued.index,
|
||||
mediaIndex: 7,
|
||||
);
|
||||
|
||||
final row = (await db.select(db.downloadedMedia).get()).single;
|
||||
expect(row.parentRatingKey, 'season1');
|
||||
expect(row.grandparentRatingKey, 'show1');
|
||||
expect(row.mediaIndex, 7);
|
||||
});
|
||||
|
||||
test('atomically updates metadata and attempt state while preserving the row and physical fields', () async {
|
||||
await db.insertDownload(
|
||||
serverId: ServerId('srv'),
|
||||
clientScopeId: 'scope-old',
|
||||
ratingKey: '100',
|
||||
globalKey: 'srv:100',
|
||||
type: 'movie',
|
||||
status: DownloadStatus.queued.index,
|
||||
mediaIndex: 1,
|
||||
mediaSourceId: 'source-old',
|
||||
);
|
||||
final original = (await db.getDownloadedMedia('srv:100'))!;
|
||||
await (db.update(db.downloadedMedia)..where((row) => row.globalKey.equals('srv:100'))).write(
|
||||
const DownloadedMediaCompanion(
|
||||
progress: Value(50),
|
||||
downloadedBytes: Value(500),
|
||||
totalBytes: Value(1000),
|
||||
videoFilePath: Value('downloads/video.mkv'),
|
||||
safRootUri: Value('content://downloads'),
|
||||
thumbPath: Value('downloads/thumb.jpg'),
|
||||
downloadedAt: Value(1234),
|
||||
errorMessage: Value('old error'),
|
||||
retryCount: Value(2),
|
||||
bgTaskId: Value('current-task'),
|
||||
),
|
||||
);
|
||||
|
||||
await db.insertDownload(
|
||||
serverId: ServerId('srv-new'),
|
||||
clientScopeId: 'scope-new',
|
||||
ratingKey: '100-new',
|
||||
globalKey: 'srv:100',
|
||||
type: 'episode',
|
||||
parentRatingKey: 'season-new',
|
||||
grandparentRatingKey: 'show-new',
|
||||
status: DownloadStatus.failed.index,
|
||||
mediaIndex: 3,
|
||||
mediaSourceId: 'source-new',
|
||||
);
|
||||
|
||||
final row = (await db.select(db.downloadedMedia).get()).single;
|
||||
expect(row.id, original.id);
|
||||
expect(row.serverId, 'srv-new');
|
||||
expect(row.clientScopeId, 'scope-new');
|
||||
expect(row.ratingKey, '100-new');
|
||||
expect(row.type, 'episode');
|
||||
expect(row.parentRatingKey, 'season-new');
|
||||
expect(row.grandparentRatingKey, 'show-new');
|
||||
expect(row.status, DownloadStatus.failed.index);
|
||||
expect(row.mediaIndex, 3);
|
||||
expect(row.mediaSourceId, 'source-new');
|
||||
expect(row.progress, 0);
|
||||
expect(row.downloadedBytes, 0);
|
||||
expect(row.totalBytes, isNull);
|
||||
expect(row.errorMessage, isNull);
|
||||
expect(row.retryCount, 0);
|
||||
expect(row.videoFilePath, 'downloads/video.mkv');
|
||||
expect(row.safRootUri, 'content://downloads');
|
||||
expect(row.thumbPath, 'downloads/thumb.jpg');
|
||||
expect(row.downloadedAt, 1234);
|
||||
expect(row.bgTaskId, 'current-task');
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user