682 lines
21 KiB
Dart
682 lines
21 KiB
Dart
// Copyright 2020 Ben Hills and the project contributors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:pinepods_mobile/core/extensions.dart';
|
|
import 'package:pinepods_mobile/entities/episode.dart';
|
|
import 'package:pinepods_mobile/entities/podcast.dart';
|
|
import 'package:pinepods_mobile/entities/queue.dart';
|
|
import 'package:pinepods_mobile/entities/transcript.dart';
|
|
import 'package:pinepods_mobile/repository/repository.dart';
|
|
import 'package:pinepods_mobile/repository/sembast/sembast_database_service.dart';
|
|
import 'package:pinepods_mobile/state/episode_state.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:rxdart/rxdart.dart';
|
|
import 'package:sembast/sembast.dart';
|
|
|
|
/// An implementation of [Repository] that is backed by
|
|
/// [Sembast](https://github.com/tekartik/sembast.dart/tree/master/sembast)
|
|
class SembastRepository extends Repository {
|
|
final log = Logger('SembastRepository');
|
|
|
|
final _podcastSubject = BehaviorSubject<Podcast>();
|
|
final _episodeSubject = BehaviorSubject<EpisodeState>();
|
|
|
|
final _podcastStore = intMapStoreFactory.store('podcast');
|
|
final _episodeStore = intMapStoreFactory.store('episode');
|
|
final _queueStore = intMapStoreFactory.store('queue');
|
|
final _transcriptStore = intMapStoreFactory.store('transcript');
|
|
|
|
final _queueGuids = <String>[];
|
|
|
|
late DatabaseService _databaseService;
|
|
|
|
Future<Database> get _db async => _databaseService.database;
|
|
|
|
SembastRepository({
|
|
bool cleanup = true,
|
|
String databaseName = 'pinepods.db',
|
|
}) {
|
|
_databaseService = DatabaseService(databaseName, version: 2, upgraderCallback: dbUpgrader);
|
|
|
|
if (cleanup) {
|
|
_cleanupEpisodes().then((value) {
|
|
log.fine('Orphan episodes cleanup complete');
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Saves the [Podcast] instance and associated [Episode]s. Podcasts are
|
|
/// only stored when we subscribe to them, so at the point we store a
|
|
/// new podcast we store the current [DateTime] to mark the
|
|
/// subscription date.
|
|
@override
|
|
Future<Podcast> savePodcast(Podcast podcast, {bool withEpisodes = true}) async {
|
|
log.fine('Saving podcast (${podcast.id ?? -1}) ${podcast.url}');
|
|
|
|
final finder = podcast.id == null
|
|
? Finder(filter: Filter.equals('guid', podcast.guid))
|
|
: Finder(filter: Filter.byKey(podcast.id));
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _podcastStore.findFirst(await _db, finder: finder);
|
|
|
|
podcast.lastUpdated = DateTime.now();
|
|
|
|
if (snapshot == null) {
|
|
podcast.subscribedDate = DateTime.now();
|
|
podcast.id = await _podcastStore.add(await _db, podcast.toMap());
|
|
} else {
|
|
await _podcastStore.update(await _db, podcast.toMap(), finder: finder);
|
|
}
|
|
|
|
if (withEpisodes) {
|
|
await _saveEpisodes(podcast.episodes);
|
|
}
|
|
|
|
_podcastSubject.add(podcast);
|
|
|
|
return podcast;
|
|
}
|
|
|
|
@override
|
|
Future<List<Podcast>> subscriptions() async {
|
|
// Custom sort order to ignore title case.
|
|
final titleSortOrder = SortOrder<String>.custom('title', (title1, title2) {
|
|
return title1.toLowerCase().compareTo(title2.toLowerCase());
|
|
});
|
|
|
|
final finder = Finder(sortOrders: [
|
|
titleSortOrder,
|
|
]);
|
|
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> subscriptionSnapshot = await _podcastStore.find(
|
|
await _db,
|
|
finder: finder,
|
|
);
|
|
|
|
final subs = subscriptionSnapshot.map((snapshot) {
|
|
final subscription = Podcast.fromMap(snapshot.key, snapshot.value);
|
|
|
|
return subscription;
|
|
}).toList();
|
|
|
|
return subs;
|
|
}
|
|
|
|
@override
|
|
Future<void> deletePodcast(Podcast podcast) async {
|
|
final db = await _db;
|
|
|
|
await db.transaction((txn) async {
|
|
final podcastFinder = Finder(filter: Filter.byKey(podcast.id));
|
|
final episodeFinder = Finder(filter: Filter.equals('pguid', podcast.guid));
|
|
|
|
await _podcastStore.delete(
|
|
txn,
|
|
finder: podcastFinder,
|
|
);
|
|
|
|
await _episodeStore.delete(
|
|
txn,
|
|
finder: episodeFinder,
|
|
);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<Podcast?> findPodcastById(num id) async {
|
|
final finder = Finder(filter: Filter.byKey(id));
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _podcastStore.findFirst(await _db, finder: finder);
|
|
|
|
if (snapshot != null) {
|
|
var p = Podcast.fromMap(snapshot.key, snapshot.value);
|
|
|
|
// Now attach all episodes for this podcast
|
|
p.episodes = await findEpisodesByPodcastGuid(
|
|
p.guid,
|
|
filter: p.filter,
|
|
sort: p.sort,
|
|
);
|
|
|
|
return p;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Future<Podcast?> findPodcastByGuid(String guid) async {
|
|
final finder = Finder(filter: Filter.equals('guid', guid));
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _podcastStore.findFirst(await _db, finder: finder);
|
|
|
|
if (snapshot != null) {
|
|
var p = Podcast.fromMap(snapshot.key, snapshot.value);
|
|
|
|
// Now attach all episodes for this podcast
|
|
p.episodes = await findEpisodesByPodcastGuid(
|
|
p.guid,
|
|
filter: p.filter,
|
|
sort: p.sort,
|
|
);
|
|
|
|
return p;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Future<List<Episode>> findAllEpisodes() async {
|
|
final finder = Finder(
|
|
sortOrders: [SortOrder('publicationDate', false)],
|
|
);
|
|
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> recordSnapshots =
|
|
await _episodeStore.find(await _db, finder: finder);
|
|
|
|
final results = recordSnapshots.map((snapshot) {
|
|
final episode = Episode.fromMap(snapshot.key, snapshot.value);
|
|
|
|
return episode;
|
|
}).toList();
|
|
|
|
return results;
|
|
}
|
|
|
|
@override
|
|
Future<Episode?> findEpisodeById(int? id) async {
|
|
final finder = Finder(filter: Filter.byKey(id));
|
|
final RecordSnapshot<int, Map<String, Object?>> snapshot =
|
|
(await _episodeStore.findFirst(await _db, finder: finder))!;
|
|
|
|
return await _loadEpisodeSnapshot(snapshot.key, snapshot.value);
|
|
}
|
|
|
|
@override
|
|
Future<Episode?> findEpisodeByGuid(String guid) async {
|
|
final finder = Finder(filter: Filter.equals('guid', guid));
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _episodeStore.findFirst(await _db, finder: finder);
|
|
|
|
if (snapshot == null) {
|
|
return null;
|
|
}
|
|
|
|
return await _loadEpisodeSnapshot(snapshot.key, snapshot.value);
|
|
}
|
|
|
|
// TODO: Remove nullable on pguid as this does not make sense.
|
|
@override
|
|
Future<List<Episode>> findEpisodesByPodcastGuid(
|
|
String? pguid, {
|
|
PodcastEpisodeFilter filter = PodcastEpisodeFilter.none,
|
|
PodcastEpisodeSort sort = PodcastEpisodeSort.none,
|
|
}) async {
|
|
var episodeFilter = Filter.equals('pguid', pguid);
|
|
var sortOrder = SortOrder('publicationDate', false);
|
|
|
|
// If we have an additional episode filter and/or sort, apply it.
|
|
episodeFilter = _applyEpisodeFilter(filter, episodeFilter, pguid);
|
|
sortOrder = _applyEpisodeSort(sort, sortOrder);
|
|
|
|
final finder = Finder(
|
|
filter: episodeFilter,
|
|
sortOrders: [sortOrder],
|
|
);
|
|
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> recordSnapshots =
|
|
await _episodeStore.find(await _db, finder: finder);
|
|
|
|
final results = recordSnapshots.map((snapshot) async {
|
|
return await _loadEpisodeSnapshot(snapshot.key, snapshot.value);
|
|
}).toList();
|
|
|
|
final episodeList = Future.wait(results);
|
|
|
|
return episodeList;
|
|
}
|
|
|
|
@override
|
|
Future<List<Episode>> findDownloadsByPodcastGuid(String pguid) async {
|
|
final finder = Finder(
|
|
filter: Filter.and([
|
|
Filter.equals('pguid', pguid),
|
|
Filter.equals('downloadPercentage', '100'),
|
|
]),
|
|
sortOrders: [SortOrder('publicationDate', false)],
|
|
);
|
|
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> recordSnapshots =
|
|
await _episodeStore.find(await _db, finder: finder);
|
|
|
|
final results = recordSnapshots.map((snapshot) {
|
|
final episode = Episode.fromMap(snapshot.key, snapshot.value);
|
|
|
|
return episode;
|
|
}).toList();
|
|
|
|
return results;
|
|
}
|
|
|
|
@override
|
|
Future<List<Episode>> findDownloads() async {
|
|
final finder =
|
|
Finder(filter: Filter.equals('downloadPercentage', '100'), sortOrders: [SortOrder('publicationDate', false)]);
|
|
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> recordSnapshots =
|
|
await _episodeStore.find(await _db, finder: finder);
|
|
|
|
final results = recordSnapshots.map((snapshot) {
|
|
final episode = Episode.fromMap(snapshot.key, snapshot.value);
|
|
|
|
return episode;
|
|
}).toList();
|
|
|
|
return results;
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteEpisode(Episode episode) async {
|
|
final finder = Finder(filter: Filter.byKey(episode.id));
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _episodeStore.findFirst(await _db, finder: finder);
|
|
|
|
if (snapshot == null) {
|
|
// Oops!
|
|
} else {
|
|
await _episodeStore.delete(await _db, finder: finder);
|
|
_episodeSubject.add(EpisodeDeleteState(episode));
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteEpisodes(List<Episode> episodes) async {
|
|
var d = await _db;
|
|
|
|
if (episodes.isNotEmpty) {
|
|
for (var chunk in episodes.chunk(100)) {
|
|
await d.transaction((txn) async {
|
|
var futures = <Future<int>>[];
|
|
|
|
for (var episode in chunk) {
|
|
final finder = Finder(filter: Filter.byKey(episode.id));
|
|
|
|
futures.add(_episodeStore.delete(txn, finder: finder));
|
|
}
|
|
|
|
if (futures.isNotEmpty) {
|
|
await Future.wait(futures);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<Episode> saveEpisode(Episode episode, [bool updateIfSame = false]) async {
|
|
var e = await _saveEpisode(episode, updateIfSame);
|
|
|
|
_episodeSubject.add(EpisodeUpdateState(e));
|
|
|
|
return e;
|
|
}
|
|
|
|
@override
|
|
Future<List<Episode>> saveEpisodes(List<Episode> episodes, [bool updateIfSame = false]) async {
|
|
final updatedEpisodes = <Episode>[];
|
|
|
|
for (var es in episodes) {
|
|
var e = await _saveEpisode(es, updateIfSame);
|
|
|
|
updatedEpisodes.add(e);
|
|
|
|
_episodeSubject.add(EpisodeUpdateState(e));
|
|
}
|
|
|
|
return updatedEpisodes;
|
|
}
|
|
|
|
@override
|
|
Future<List<Episode>> loadQueue() async {
|
|
var episodes = <Episode>[];
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot = await _queueStore.record(1).getSnapshot(await _db);
|
|
|
|
if (snapshot != null) {
|
|
var queue = Queue.fromMap(snapshot.key, snapshot.value);
|
|
|
|
var episodeFinder = Finder(filter: Filter.inList('guid', queue.guids));
|
|
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> recordSnapshots =
|
|
await _episodeStore.find(await _db, finder: episodeFinder);
|
|
|
|
episodes = recordSnapshots.map((snapshot) {
|
|
final episode = Episode.fromMap(snapshot.key, snapshot.value);
|
|
|
|
return episode;
|
|
}).toList();
|
|
}
|
|
|
|
return episodes;
|
|
}
|
|
|
|
@override
|
|
Future<void> saveQueue(List<Episode> episodes) async {
|
|
/// Check to see if we have any ad-hoc episodes and save them first
|
|
for (var e in episodes) {
|
|
if (e.pguid == null || e.pguid!.isEmpty) {
|
|
_saveEpisode(e, false);
|
|
}
|
|
}
|
|
|
|
var guids = episodes.map((e) => e.guid).toList();
|
|
|
|
/// Only bother saving if the queue has changed
|
|
if (!listEquals(guids, _queueGuids)) {
|
|
final queue = Queue(guids: guids);
|
|
|
|
await _queueStore.record(1).put(await _db, queue.toMap());
|
|
|
|
_queueGuids.clear();
|
|
_queueGuids.addAll(guids);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<Transcript?> findTranscriptById(int? id) async {
|
|
final finder = Finder(filter: Filter.byKey(id));
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _transcriptStore.findFirst(await _db, finder: finder);
|
|
|
|
return snapshot == null ? null : Transcript.fromMap(snapshot.key, snapshot.value);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteTranscriptById(int id) async {
|
|
final finder = Finder(filter: Filter.byKey(id));
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _transcriptStore.findFirst(await _db, finder: finder);
|
|
|
|
if (snapshot == null) {
|
|
// Oops!
|
|
} else {
|
|
await _transcriptStore.delete(await _db, finder: finder);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteTranscriptsById(List<int> id) async {
|
|
var d = await _db;
|
|
|
|
if (id.isNotEmpty) {
|
|
for (var chunk in id.chunk(100)) {
|
|
await d.transaction((txn) async {
|
|
var futures = <Future<int>>[];
|
|
|
|
for (var id in chunk) {
|
|
final finder = Finder(filter: Filter.byKey(id));
|
|
|
|
futures.add(_transcriptStore.delete(txn, finder: finder));
|
|
}
|
|
|
|
if (futures.isNotEmpty) {
|
|
await Future.wait(futures);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<Transcript> saveTranscript(Transcript transcript) async {
|
|
final finder = Finder(filter: Filter.byKey(transcript.id));
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _transcriptStore.findFirst(await _db, finder: finder);
|
|
|
|
transcript.lastUpdated = DateTime.now();
|
|
|
|
if (snapshot == null) {
|
|
transcript.id = await _transcriptStore.add(await _db, transcript.toMap());
|
|
} else {
|
|
await _transcriptStore.update(await _db, transcript.toMap(), finder: finder);
|
|
}
|
|
|
|
return transcript;
|
|
}
|
|
|
|
Future<void> _cleanupEpisodes() async {
|
|
final threshold = DateTime.now().subtract(const Duration(days: 60)).millisecondsSinceEpoch;
|
|
|
|
/// Find all streamed episodes over the threshold.
|
|
final filter = Filter.and([
|
|
Filter.equals('downloadState', 0),
|
|
Filter.lessThan('lastUpdated', threshold),
|
|
]);
|
|
|
|
final orphaned = <Episode>[];
|
|
final pguids = <String?>[];
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> episodes =
|
|
await _episodeStore.find(await _db, finder: Finder(filter: filter));
|
|
|
|
// First, find all podcasts
|
|
for (var podcast in await _podcastStore.find(await _db)) {
|
|
pguids.add(podcast.value['guid'] as String?);
|
|
}
|
|
|
|
for (var episode in episodes) {
|
|
final pguid = episode.value['pguid'] as String?;
|
|
final podcast = pguids.contains(pguid);
|
|
|
|
if (!podcast) {
|
|
orphaned.add(Episode.fromMap(episode.key, episode.value));
|
|
}
|
|
}
|
|
|
|
await deleteEpisodes(orphaned);
|
|
}
|
|
|
|
SortOrder<Object?> _applyEpisodeSort(PodcastEpisodeSort sort, SortOrder<Object?> sortOrder) {
|
|
switch (sort) {
|
|
case PodcastEpisodeSort.none:
|
|
case PodcastEpisodeSort.latestFirst:
|
|
sortOrder = SortOrder('publicationDate', false);
|
|
break;
|
|
case PodcastEpisodeSort.earliestFirst:
|
|
sortOrder = SortOrder('publicationDate', true);
|
|
break;
|
|
case PodcastEpisodeSort.alphabeticalDescending:
|
|
sortOrder = SortOrder<String>.custom('title', (title1, title2) {
|
|
return title2.toLowerCase().compareTo(title1.toLowerCase());
|
|
});
|
|
break;
|
|
case PodcastEpisodeSort.alphabeticalAscending:
|
|
sortOrder = SortOrder<String>.custom('title', (title1, title2) {
|
|
return title1.toLowerCase().compareTo(title2.toLowerCase());
|
|
});
|
|
break;
|
|
}
|
|
return sortOrder;
|
|
}
|
|
|
|
Filter _applyEpisodeFilter(PodcastEpisodeFilter filter, Filter episodeFilter, String? pguid) {
|
|
// If we have an additional episode filter, apply it.
|
|
switch (filter) {
|
|
case PodcastEpisodeFilter.none:
|
|
episodeFilter = Filter.equals('pguid', pguid);
|
|
break;
|
|
case PodcastEpisodeFilter.started:
|
|
episodeFilter = Filter.and([Filter.equals('pguid', pguid), Filter.notEquals('position', '0')]);
|
|
break;
|
|
case PodcastEpisodeFilter.played:
|
|
episodeFilter = Filter.and([Filter.equals('pguid', pguid), Filter.equals('played', 'true')]);
|
|
break;
|
|
case PodcastEpisodeFilter.notPlayed:
|
|
episodeFilter = Filter.and([Filter.equals('pguid', pguid), Filter.equals('played', 'false')]);
|
|
break;
|
|
}
|
|
return episodeFilter;
|
|
}
|
|
|
|
/// Saves a list of episodes to the repository. To improve performance we
|
|
/// split the episodes into chunks of 100 and save any that have been updated
|
|
/// in that chunk in a single transaction.
|
|
Future<void> _saveEpisodes(List<Episode?>? episodes) async {
|
|
var d = await _db;
|
|
var dateStamp = DateTime.now();
|
|
|
|
if (episodes != null && episodes.isNotEmpty) {
|
|
for (var chunk in episodes.chunk(100)) {
|
|
await d.transaction((txn) async {
|
|
var futures = <Future<int>>[];
|
|
|
|
for (var episode in chunk) {
|
|
episode!.lastUpdated = dateStamp;
|
|
|
|
if (episode.id == null) {
|
|
futures.add(_episodeStore.add(txn, episode.toMap()).then((id) => episode.id = id));
|
|
} else {
|
|
final finder = Finder(filter: Filter.byKey(episode.id));
|
|
|
|
var existingEpisode = await findEpisodeById(episode.id);
|
|
|
|
if (existingEpisode == null || existingEpisode != episode) {
|
|
futures.add(_episodeStore.update(txn, episode.toMap(), finder: finder));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (futures.isNotEmpty) {
|
|
await Future.wait(futures);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<Episode> _saveEpisode(Episode episode, bool updateIfSame) async {
|
|
final finder = Finder(filter: Filter.byKey(episode.id));
|
|
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _episodeStore.findFirst(await _db, finder: finder);
|
|
|
|
if (snapshot == null) {
|
|
episode.lastUpdated = DateTime.now();
|
|
episode.id = await _episodeStore.add(await _db, episode.toMap());
|
|
} else {
|
|
var e = Episode.fromMap(episode.id, snapshot.value);
|
|
episode.lastUpdated = DateTime.now();
|
|
|
|
if (updateIfSame || episode != e) {
|
|
await _episodeStore.update(await _db, episode.toMap(), finder: finder);
|
|
}
|
|
}
|
|
|
|
return episode;
|
|
}
|
|
|
|
@override
|
|
Future<Episode?> findEpisodeByTaskId(String taskId) async {
|
|
final finder = Finder(filter: Filter.equals('downloadTaskId', taskId));
|
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
|
await _episodeStore.findFirst(await _db, finder: finder);
|
|
|
|
if (snapshot != null) {
|
|
return await _loadEpisodeSnapshot(snapshot.key, snapshot.value);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<Episode> _loadEpisodeSnapshot(int key, Map<String, Object?> snapshot) async {
|
|
var episode = Episode.fromMap(key, snapshot);
|
|
|
|
if (episode.transcriptId! > 0) {
|
|
episode.transcript = await findTranscriptById(episode.transcriptId);
|
|
}
|
|
|
|
return episode;
|
|
}
|
|
|
|
@override
|
|
Future<void> close() async {
|
|
final d = await _db;
|
|
|
|
await d.close();
|
|
}
|
|
|
|
Future<void> dbUpgrader(Database db, int oldVersion, int newVersion) async {
|
|
if (oldVersion == 1) {
|
|
await _upgradeV2(db);
|
|
}
|
|
}
|
|
|
|
/// In v1 we allowed http requests, where as now we force to https. As we currently use the
|
|
/// URL as the GUID we need to upgrade any followed podcasts that have a http base to https.
|
|
/// We use the passed [Database] rather than _db to prevent deadlocking, hence the direct
|
|
/// update to data within this routine rather than using the existing find/update methods.
|
|
Future<void> _upgradeV2(Database db) async {
|
|
List<RecordSnapshot<int, Map<String, Object?>>> data = await _podcastStore.find(db);
|
|
final podcasts = data.map((e) => Podcast.fromMap(e.key, e.value)).toList();
|
|
|
|
log.info('Upgrading Sembast store to V2');
|
|
|
|
for (var podcast in podcasts) {
|
|
if (podcast.guid!.startsWith('http:')) {
|
|
final idFinder = Finder(filter: Filter.byKey(podcast.id));
|
|
final guid = podcast.guid!.replaceFirst('http:', 'https:');
|
|
final episodeFinder = Finder(
|
|
filter: Filter.equals('pguid', podcast.guid),
|
|
);
|
|
|
|
log.fine('Upgrading GUID ${podcast.guid} - to $guid');
|
|
|
|
var upgradedPodcast = Podcast(
|
|
id: podcast.id,
|
|
guid: guid,
|
|
url: podcast.url,
|
|
link: podcast.link,
|
|
title: podcast.title,
|
|
description: podcast.description,
|
|
imageUrl: podcast.imageUrl,
|
|
thumbImageUrl: podcast.thumbImageUrl,
|
|
copyright: podcast.copyright,
|
|
funding: podcast.funding,
|
|
persons: podcast.persons,
|
|
lastUpdated: DateTime.now(),
|
|
);
|
|
|
|
final List<RecordSnapshot<int, Map<String, Object?>>> episodeData =
|
|
await _episodeStore.find(db, finder: episodeFinder);
|
|
final episodes = episodeData.map((e) => Episode.fromMap(e.key, e.value)).toList();
|
|
|
|
// Now upgrade episodes
|
|
for (var e in episodes) {
|
|
e.pguid = guid;
|
|
log.fine('Updating episode guid for ${e.title} from ${e.pguid} to $guid');
|
|
|
|
final epf = Finder(filter: Filter.byKey(e.id));
|
|
await _episodeStore.update(db, e.toMap(), finder: epf);
|
|
}
|
|
|
|
upgradedPodcast.episodes = episodes;
|
|
await _podcastStore.update(db, upgradedPodcast.toMap(), finder: idFinder);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Stream<EpisodeState> get episodeListener => _episodeSubject.stream;
|
|
|
|
@override
|
|
Stream<Podcast> get podcastListener => _podcastSubject.stream;
|
|
}
|