feat(train): the marked playlists downloaded by a WorkManager job, the app open or not (ADR 0030, D52)
The app reads the playlists as before and hands the missing tracks to MusicWorker.kt, which runs every 6 hours and once whenever the list changes, on unmetered networks with the battery and storage not low. MusicFetcher.kt downloads each with the token from JellyfinTokens, works out its tempo and appends a line to fetched.txt, which the app takes in as it opens and as each track comes. The app's own download is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
6bde74f0ba
commit
409a2519f9
1 changed files with 830 additions and 207 deletions
|
|
@ -1,4 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:halkyon_train_flutter/src/feature/music/data/music_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/music/data/music_store.dart';
|
||||
|
|
@ -124,20 +126,67 @@ class FakeJellyfin({
|
|||
});
|
||||
}
|
||||
|
||||
/// A [MusicPlatform] answering [tempos] by the file's name.
|
||||
class FakeMusicPlatform() implements MusicPlatform {
|
||||
/// A [MusicPlatform] whose music job runs when a test says ([run]), as
|
||||
/// `MusicFetcher.kt` does: each track from [jellyfin] into the folder, a
|
||||
/// line each in `fetched.txt`, its tempo from [tempos] by its ID.
|
||||
class FakeMusicPlatform([final FakeJellyfin? jellyfin])
|
||||
implements MusicPlatform {
|
||||
bool unmeteredNow = true;
|
||||
final tempos = <String, double>{'t1': 128, 't2': 172.5};
|
||||
final analysed = <String>[];
|
||||
|
||||
/// The job as last scheduled; null where none is.
|
||||
MusicJob? job;
|
||||
final _fetched = StreamController<void>.broadcast();
|
||||
|
||||
@override
|
||||
Future<bool> unmetered() async => unmeteredNow;
|
||||
|
||||
@override
|
||||
Future<double?> tempo(String path) async {
|
||||
final name = path.split('/').last;
|
||||
analysed.add(name);
|
||||
return tempos[name];
|
||||
Future<void> schedule(MusicJob? job) async => this.job = job;
|
||||
|
||||
@override
|
||||
Stream<void> get fetched => _fetched.stream;
|
||||
|
||||
/// Runs the job as scheduled, with the token [token].
|
||||
Future<void> run(String token) async {
|
||||
final job = this.job;
|
||||
if (job == null || !unmeteredNow) {
|
||||
return;
|
||||
}
|
||||
final lines = File('${job.folder}/fetched.txt');
|
||||
final done = {
|
||||
if (lines.existsSync())
|
||||
for (final line in lines.readAsLinesSync())
|
||||
(jsonDecode(line) as Map<String, Object?>)['id'],
|
||||
};
|
||||
for (final track in job.tracks) {
|
||||
if (done.contains(track.id)) {
|
||||
continue;
|
||||
}
|
||||
final response = await jellyfin!.client.get(
|
||||
Uri.parse('${job.address}${track.path}'),
|
||||
headers: {'authorization': '${job.client}, Token="$token"'},
|
||||
);
|
||||
if (response.statusCode >= 300) {
|
||||
continue;
|
||||
}
|
||||
final file = File('${job.folder}/${track.id}')
|
||||
..parent.createSync(recursive: true)
|
||||
..writeAsBytesSync(response.bodyBytes);
|
||||
if (track.analyse) {
|
||||
analysed.add(track.id);
|
||||
}
|
||||
lines.writeAsStringSync(
|
||||
'${jsonEncode({
|
||||
'id': track.id,
|
||||
'bytes': file.lengthSync(),
|
||||
if (track.analyse) ...{'tempo': tempos[track.id], 'analysed': true},
|
||||
})}\n',
|
||||
mode: FileMode.append,
|
||||
);
|
||||
_fetched.add(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue