Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Events Review #237

Merged
merged 10 commits into from
May 6, 2024
3 changes: 2 additions & 1 deletion lib/api/api_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ class DevHttpOverrides extends HttpOverrides {
/// See also:
/// * <https://github.com/bluecherrydvr/unity/discussions/42>
/// * [compute], used to compute data in another thread
static void configureCertificates() {
static Future<void> configureCertificates() async {
ServersProvider.instance = ServersProvider.dump();
SettingsProvider.ensureInitialized();
HttpOverrides.global = DevHttpOverrides();
}

Expand Down
135 changes: 93 additions & 42 deletions lib/providers/downloads_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:bluecherry_client/providers/app_provider_interface.dart';
import 'package:bluecherry_client/providers/home_provider.dart';
import 'package:bluecherry_client/providers/settings_provider.dart';
import 'package:bluecherry_client/utils/constants.dart';
import 'package:bluecherry_client/utils/extensions.dart';
import 'package:bluecherry_client/utils/logging.dart';
import 'package:bluecherry_client/utils/storage.dart';
import 'package:dio/dio.dart';
Expand Down Expand Up @@ -107,10 +108,13 @@ class DownloadsManager extends UnityProvider {
if (dirs?.isNotEmpty ?? false) dir = dirs!.first;
}

if (dir == null) {
final downloadsDir = await getDownloadsDirectory();
if (downloadsDir != null) {
dir = Directory(path.join(downloadsDir.path, 'Bluecherry Client'));
// This method is only available on macOS
if (Platform.isMacOS) {
if (dir == null) {
final downloadsDir = await getDownloadsDirectory();
if (downloadsDir != null) {
dir = Directory(path.join(downloadsDir.path, 'Bluecherry Client'));
}
}
}

Expand All @@ -130,7 +134,7 @@ class DownloadsManager extends UnityProvider {
}

/// All the downloaded events
List<DownloadedEvent> downloadedEvents = [];
Set<DownloadedEvent> downloadedEvents = {};

/// The events that are downloading
Map<Event, (DownloadProgress progress, String filePath)> downloading = {};
Expand All @@ -145,6 +149,11 @@ class DownloadsManager extends UnityProvider {
downloadsCompleter = null;
}

downloading.removeWhere((key, value) {
final progress = value.$1;
return progress == 1.0;
});

// // setProgressBar is only available on Windows and macOS
// if (isDesktopPlatform && !Platform.isLinux) {
// if (downloading.isEmpty) {
Expand All @@ -162,7 +171,22 @@ class DownloadsManager extends UnityProvider {
});

await tryReadStorage(
() => super.initializeStorage(downloads, kStorageDownloads));
() => super.initializeStorage(downloads, kStorageDownloads),
);

for (final de in downloadedEvents) {
doesEventFileExist(de.event.id).then((exist) async {
if (!exist) {
debugPrint(
'Event file does not exist: ${de.event.id}. Redownloading');
final downloadPath = await _downloadEventFile(
de.event,
path.dirname(de.downloadPath),
);
downloadedEvents.add(de.copyWith(downloadPath: downloadPath));
}
});
}
}

@override
Expand All @@ -184,13 +208,13 @@ class DownloadsManager extends UnityProvider {
final data = await tryReadStorage(() => downloads.read());

downloadedEvents = data[kStorageDownloads] == null
? []
? <DownloadedEvent>{}
: ((await compute(jsonDecode, data[kStorageDownloads] as String) ?? [])
as List)
.cast<Map>()
.map<DownloadedEvent>((item) {
return DownloadedEvent.fromJson(item.cast<String, dynamic>());
}).toList();
}).toSet();

super.restore(notifyListeners: notifyListeners);
}
Expand All @@ -205,6 +229,11 @@ class DownloadsManager extends UnityProvider {
return downloadedEvents.any((de) => de.event.id == eventId);
}

Future<bool> doesEventFileExist(int eventId) async {
final downloadPath = getDownloadedPathForEvent(eventId);
return File(downloadPath).exists();
}

String getDownloadedPathForEvent(int eventId) {
assert(isEventDownloaded(eventId));
return downloadedEvents
Expand All @@ -222,16 +251,6 @@ class DownloadsManager extends UnityProvider {
assert(event.mediaURL != null, 'There must be an url to be downloaded');
if (event.mediaURL == null) return; // safe for release

final home = HomeProvider.instance
..loading(UnityLoadingReason.downloadEvent);

if (downloadsCompleter == null || downloadsCompleter!.isCompleted) {
downloadsCompleter = Completer();
}

downloading[event] = (0.0, '');
notifyListeners();

final dir = await () async {
final settings = SettingsProvider.instance;

Expand All @@ -249,10 +268,42 @@ class DownloadsManager extends UnityProvider {
}
return settings.kDownloadsDirectory.value;
}();
final downloadPath = await _downloadEventFile(event, dir);

downloading.remove(event);
downloadedEvents
..removeWhere((downloadedEvent) => downloadedEvent.event.id == event.id)
..add(DownloadedEvent(event: event, downloadPath: downloadPath));

if (downloading.isEmpty) {
downloadsCompleter?.complete();
}

await save();
}

/// Downloads the given [event] and returns the path of the downloaded file
Future<String> _downloadEventFile(Event event, String dir) async {
if (downloading.entries.any((de) => de.key.id == event.id)) {
return downloading.entries
.firstWhere((de) => de.key.id == event.id)
.value
.$2;
}
debugPrint('Downloading event: $event to $dir');
final home = HomeProvider.instance
..loading(UnityLoadingReason.downloadEvent);

if (downloadsCompleter == null || downloadsCompleter!.isCompleted) {
downloadsCompleter = Completer();
}

downloading[event] = (0.0, '');
notifyListeners();

final fileName =
'event_${event.id}_${event.deviceID}_${event.server.name}.mp4';
final downloadPath = path.join(dir, fileName);

await Dio().downloadUri(
event.mediaURL!,
downloadPath,
Expand All @@ -267,18 +318,9 @@ class DownloadsManager extends UnityProvider {
},
);

downloading.remove(event);
downloadedEvents.add(DownloadedEvent(
event: event,
downloadPath: downloadPath,
));

if (downloading.isEmpty) {
downloadsCompleter?.complete();
}

home.notLoading(UnityLoadingReason.downloadEvent);
await save();

return downloadPath;
}

/// Deletes any downloaded events at the given [downloadPath]
Expand All @@ -291,19 +333,28 @@ class DownloadsManager extends UnityProvider {
if (await file.exists()) await file.delete();
}

void cancelEventDownload(Event target) {
final event = downloading.keys.firstWhereOrNull((downloadingEvent) {
return downloadingEvent.id == target.id;
});

try {
final file = File(downloading[event]!.$2);
if (file.existsSync()) file.deleteSync();
} catch (e, s) {
debugPrint('Failed to delete file: $e');
writeLogToFile(
'Failed to delete file during cancelDownloading: $e, $s',
);
}
downloading.remove(event);
notifyListeners();
}

/// Cancels the ongoing downloads and deletes the downloaded files.
Future<void> cancelDownloading() async {
for (final event in downloading.keys.toList()) {
try {
final file = File(downloading[event]!.$2);
if (file.existsSync()) file.deleteSync();
} catch (e, s) {
debugPrint('Failed to delete file: $e');
writeLogToFile(
'Failed to delete file during cancelDownloading: $e, $s',
);
}
downloading.remove(event);
Future<void> cancelDownloading([String? downloadPath]) async {
for (final event in downloading.keys) {
cancelEventDownload(event);
}

downloadsCompleter?.complete();
Expand Down
7 changes: 1 addition & 6 deletions lib/providers/home_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,7 @@ enum UnityLoadingReason {
}

class HomeProvider extends ChangeNotifier {
static late HomeProvider _instance;

HomeProvider() {
_instance = this;
}

static final _instance = HomeProvider();
static HomeProvider get instance => _instance;

UnityTab tab = ServersProvider.instance.hasServers
Expand Down
10 changes: 8 additions & 2 deletions lib/providers/settings_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,14 @@ class SettingsProvider extends UnityProvider {
);
final kDownloadsDirectory = _SettingsOption(
def: '',
getDefault: () async =>
(await DownloadsManager.kDefaultDownloadsDirectory).path,
getDefault: () async {
try {
return (await DownloadsManager.kDefaultDownloadsDirectory).path;
} catch (e) {
debugPrint('Error getting default downloads directory: $e');
return '';
}
},
key: 'downloads.directory',
);

Expand Down
33 changes: 21 additions & 12 deletions lib/screens/downloads/downloads_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class DownloadsManagerScreen extends StatelessWidget {
SliverList.builder(
itemCount: downloads.downloadedEvents.length,
itemBuilder: (context, index) {
final de = downloads.downloadedEvents[index];
final de = downloads.downloadedEvents.elementAt(index);

return DownloadTile(
key: ValueKey(de.event.id),
Expand Down Expand Up @@ -299,17 +299,26 @@ class _DownloadTileState extends State<DownloadTile> {
]),
),
),
TextButton.icon(
onPressed: isDownloaded
? () {
context
.read<DownloadsManager>()
.delete(widget.downloadPath!);
}
: null,
icon: const Icon(Icons.delete, size: 20.0),
label: Text(loc.delete),
),
if (isDownloaded)
TextButton.icon(
onPressed: () {
context
.read<DownloadsManager>()
.delete(widget.downloadPath!);
},
icon: const Icon(Icons.delete, size: 20.0),
label: Text(loc.delete),
)
else
TextButton.icon(
onPressed: () {
context
.read<DownloadsManager>()
.cancelEventDownload(widget.event);
},
icon: const Icon(Icons.cancel, size: 20.0),
label: Text(loc.cancel),
),
if (isDesktop)
TextButton.icon(
onPressed: isDownloaded
Expand Down
4 changes: 3 additions & 1 deletion lib/screens/events_browser/events_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ class EventsScreenState<T extends StatefulWidget> extends State<T> {
eventsProvider.endTime == null) {
return loc.today;
} else if (DateUtils.isSameDay(
eventsProvider.startTime, eventsProvider.endTime)) {
eventsProvider.startTime,
eventsProvider.endTime,
)) {
return formatter.format(eventsProvider.startTime!);
} else {
return loc.fromToDate(
Expand Down
8 changes: 7 additions & 1 deletion lib/screens/events_browser/filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import 'package:bluecherry_client/providers/events_provider.dart';
import 'package:bluecherry_client/providers/server_provider.dart';
import 'package:bluecherry_client/providers/settings_provider.dart';
import 'package:bluecherry_client/utils/extensions.dart';
import 'package:bluecherry_client/widgets/misc.dart';
import 'package:bluecherry_client/widgets/search.dart';
Expand Down Expand Up @@ -72,6 +73,7 @@ class _EventsDevicesPickerState extends State<EventsDevicesPicker> {
Widget build(BuildContext context) {
final serversProvider = context.watch<ServersProvider>();
final eventsProvider = context.watch<EventsProvider>();
final settings = context.watch<SettingsProvider>();

return SingleChildScrollView(
child: TreeView(
Expand Down Expand Up @@ -128,7 +130,11 @@ class _EventsDevicesPickerState extends State<EventsDevicesPicker> {
return <TreeNode>[];
} else {
return server.devices
.sorted(searchQuery: widget.searchQuery)
.toSet()
.sorted(
searchQuery: widget.searchQuery,
onlyEnabled: !settings.kListOfflineDevices.value,
)
.map((device) {
final enabled = isOffline
? false
Expand Down
Loading
Loading