Skip to content

Commit 63e7f66

Browse files
committed
#94 Downloads failing immediately correctly update UI
Add a Lock to DownloadService. This ensures that download status updates are not processed while a download start/stop is requested (they are processed right after).
1 parent d9e121a commit 63e7f66

7 files changed

Lines changed: 85 additions & 56 deletions

File tree

lib/bloc/podcast/episode_bloc.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:async';
77
import 'package:anytime/bloc/bloc.dart';
88
import 'package:anytime/entities/episode.dart';
99
import 'package:anytime/services/audio/audio_player_service.dart';
10+
import 'package:anytime/services/download/download_service.dart';
1011
import 'package:anytime/services/podcast/podcast_service.dart';
1112
import 'package:anytime/state/bloc_state.dart';
1213
import 'package:logging/logging.dart';
@@ -17,6 +18,7 @@ import 'package:rxdart/rxdart.dart';
1718
class EpisodeBloc extends Bloc {
1819
final log = Logger('EpisodeBloc');
1920
final PodcastService podcastService;
21+
final DownloadService downloadService;
2022
final AudioPlayerService audioPlayerService;
2123

2224
/// Add to sink to fetch list of current downloaded episodes.
@@ -43,6 +45,7 @@ class EpisodeBloc extends Bloc {
4345
EpisodeBloc({
4446
required this.podcastService,
4547
required this.audioPlayerService,
48+
required this.downloadService,
4649
}) {
4750
_init();
4851
}
@@ -65,7 +68,7 @@ class EpisodeBloc extends Bloc {
6568
await audioPlayerService.stop();
6669
}
6770

68-
await podcastService.deleteDownload(episode!);
71+
await downloadService.deleteDownload(episode!);
6972

7073
fetchDownloads(true);
7174
});

lib/services/download/download_service.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:anytime/entities/episode.dart';
66

77
abstract class DownloadService {
88
Future<bool> downloadEpisode(Episode episode);
9+
Future<void> deleteDownload(Episode episode);
910

1011
Future<Episode?> findEpisodeByTaskId(String taskId);
1112

lib/services/download/mobile_download_service.dart

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ import 'package:anytime/entities/episode.dart';
1111
import 'package:anytime/repository/repository.dart';
1212
import 'package:anytime/services/download/download_manager.dart';
1313
import 'package:anytime/services/download/download_service.dart';
14+
import 'package:anytime/services/settings/settings_service.dart';
1415
import 'package:collection/collection.dart' show IterableExtension;
16+
import 'package:flutter_downloader/flutter_downloader.dart';
1517
import 'package:logging/logging.dart';
1618
import 'package:mp3_info/mp3_info.dart';
1719
import 'package:rxdart/rxdart.dart';
20+
import 'package:synchronized/synchronized.dart';
1821

1922
/// An implementation of a [DownloadService] that handles downloading
2023
/// of episodes on mobile.
@@ -23,18 +26,33 @@ class MobileDownloadService extends DownloadService {
2326

2427
final log = Logger('MobileDownloadService');
2528
final Repository repository;
29+
final SettingsService settingsService;
2630
final DownloadManager downloadManager;
27-
28-
MobileDownloadService({required this.repository, required this.downloadManager}) {
29-
downloadManager.downloadProgress.pipe(downloadProgress);
30-
downloadProgress.listen((progress) {
31-
_updateDownloadProgress(progress);
32-
});
31+
late final StreamSubscription _downloadProgressSubscription;
32+
33+
/// Lock ensures we wait for task creation and local save
34+
/// before handling subsequent [Download update events].
35+
final _downloadProgressLock = Lock();
36+
37+
MobileDownloadService({
38+
required this.repository,
39+
required this.downloadManager,
40+
required this.settingsService,
41+
}) {
42+
_downloadProgressSubscription = downloadManager.downloadProgress.listen(
43+
(event) async => await _downloadProgressLock.synchronized(
44+
() {
45+
downloadProgress.add(event);
46+
_updateDownloadProgress(event);
47+
},
48+
),
49+
);
3350
}
3451

3552
@override
3653
void dispose() {
3754
downloadManager.dispose();
55+
_downloadProgressSubscription.cancel();
3856
}
3957

4058
@override
@@ -91,16 +109,22 @@ class MobileDownloadService extends DownloadService {
91109
/// the URL before calling download and ensure it is https.
92110
var url = await resolveUrl(episode.contentUrl!, forceHttps: true);
93111

94-
final taskId = await downloadManager.enqueueTask(url, downloadPath, filename);
112+
await _downloadProgressLock.synchronized(() async {
113+
final taskId = await downloadManager.enqueueTask(
114+
url,
115+
downloadPath,
116+
filename!,
117+
);
95118

96-
// Update the episode with download data
97-
episode.filepath = episodePath;
98-
episode.filename = filename;
99-
episode.downloadTaskId = taskId;
100-
episode.downloadState = DownloadState.downloading;
101-
episode.downloadPercentage = 0;
119+
// Update the episode with download data
120+
episode.filepath = episodePath;
121+
episode.filename = filename;
122+
episode.downloadTaskId = taskId;
123+
episode.downloadState = DownloadState.downloading;
124+
episode.downloadPercentage = 0;
102125

103-
await repository.saveEpisode(episode);
126+
await repository.saveEpisode(episode);
127+
});
104128

105129
return Future.value(true);
106130
}
@@ -109,6 +133,41 @@ class MobileDownloadService extends DownloadService {
109133
return Future.value(false);
110134
}
111135

136+
@override
137+
Future<void> deleteDownload(Episode episode) async => _downloadProgressLock.synchronized(() async {
138+
// If this episode is currently downloading, cancel the download first.
139+
if (episode.downloadState == DownloadState.downloaded) {
140+
if (settingsService.markDeletedEpisodesAsPlayed) {
141+
episode.played = true;
142+
}
143+
} else if (episode.downloadState == DownloadState.downloading && episode.downloadPercentage! < 100) {
144+
await FlutterDownloader.cancel(taskId: episode.downloadTaskId!);
145+
}
146+
147+
episode.downloadTaskId = null;
148+
episode.downloadPercentage = 0;
149+
episode.position = 0;
150+
episode.downloadState = DownloadState.none;
151+
152+
if (episode.transcriptId != null && episode.transcriptId! > 0) {
153+
await repository.deleteTranscriptById(episode.transcriptId!);
154+
}
155+
156+
await repository.saveEpisode(episode);
157+
158+
if (await hasStoragePermission()) {
159+
final f = File.fromUri(Uri.file(await resolvePath(episode)));
160+
161+
log.fine('Deleting file ${f.path}');
162+
163+
if (await f.exists()) {
164+
f.delete();
165+
}
166+
}
167+
168+
return;
169+
});
170+
112171
@override
113172
Future<Episode?> findEpisodeByTaskId(String taskId) {
114173
return repository.findEpisodeByTaskId(taskId);

lib/services/podcast/mobile_podcast_service.dart

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'dart:io';
88
import 'package:anytime/api/podcast/podcast_api.dart';
99
import 'package:anytime/core/utils.dart';
1010
import 'package:anytime/entities/chapter.dart';
11-
import 'package:anytime/entities/downloadable.dart';
1211
import 'package:anytime/entities/episode.dart';
1312
import 'package:anytime/entities/funding.dart';
1413
import 'package:anytime/entities/person.dart';
@@ -22,7 +21,6 @@ import 'package:anytime/state/episode_state.dart';
2221
import 'package:collection/collection.dart' show IterableExtension;
2322
import 'package:flutter/foundation.dart';
2423
import 'package:flutter/material.dart';
25-
import 'package:flutter_downloader/flutter_downloader.dart';
2624
import 'package:intl/intl.dart';
2725
import 'package:logging/logging.dart';
2826
import 'package:path/path.dart';
@@ -478,41 +476,6 @@ class MobilePodcastService extends PodcastService {
478476
return repository.findAllEpisodes();
479477
}
480478

481-
@override
482-
Future<void> deleteDownload(Episode episode) async {
483-
// If this episode is currently downloading, cancel the download first.
484-
if (episode.downloadState == DownloadState.downloaded) {
485-
if (settingsService.markDeletedEpisodesAsPlayed) {
486-
episode.played = true;
487-
}
488-
} else if (episode.downloadState == DownloadState.downloading && episode.downloadPercentage! < 100) {
489-
await FlutterDownloader.cancel(taskId: episode.downloadTaskId!);
490-
}
491-
492-
episode.downloadTaskId = null;
493-
episode.downloadPercentage = 0;
494-
episode.position = 0;
495-
episode.downloadState = DownloadState.none;
496-
497-
if (episode.transcriptId != null && episode.transcriptId! > 0) {
498-
await repository.deleteTranscriptById(episode.transcriptId!);
499-
}
500-
501-
await repository.saveEpisode(episode);
502-
503-
if (await hasStoragePermission()) {
504-
final f = File.fromUri(Uri.file(await resolvePath(episode)));
505-
506-
log.fine('Deleting file ${f.path}');
507-
508-
if (await f.exists()) {
509-
f.delete();
510-
}
511-
}
512-
513-
return;
514-
}
515-
516479
@override
517480
Future<void> toggleEpisodePlayed(Episode episode) async {
518481
episode.played = !episode.played;

lib/services/podcast/podcast_service.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,6 @@ abstract class PodcastService {
198198

199199
Future<Transcript> loadTranscriptByUrl({required TranscriptUrl transcriptUrl});
200200

201-
Future<void> deleteDownload(Episode episode);
202-
203201
Future<void> toggleEpisodePlayed(Episode episode);
204202

205203
Future<List<Podcast>> subscriptions();

lib/ui/anytime_podcast_app.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class AnytimePodcastApp extends StatefulWidget {
7878
downloadService = MobileDownloadService(
7979
repository: repository,
8080
downloadManager: MobileDownloaderManager(),
81+
settingsService: mobileSettingsService,
8182
);
8283

8384
podcastService = MobilePodcastService(
@@ -149,8 +150,11 @@ class AnytimePodcastAppState extends State<AnytimePodcastApp> {
149150
dispose: (_, value) => value.dispose(),
150151
),
151152
Provider<EpisodeBloc>(
152-
create: (_) =>
153-
EpisodeBloc(podcastService: widget.podcastService!, audioPlayerService: widget.audioPlayerService),
153+
create: (_) => EpisodeBloc(
154+
podcastService: widget.podcastService!,
155+
audioPlayerService: widget.audioPlayerService,
156+
downloadService: widget.downloadService,
157+
),
154158
dispose: (_, value) => value.dispose(),
155159
),
156160
Provider<PodcastBloc>(

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ dependencies:
5353
flutter_localizations:
5454
sdk: flutter
5555
collection: ^1.17.1
56+
synchronized: ^3.1.0
5657

5758
dev_dependencies:
5859
intl_generator: ^0.4.1

0 commit comments

Comments
 (0)