Skip to content

Commit d53d3bd

Browse files
committed
Fix caching of Youtube API pagination + filter duplicate videoIds.
1 parent 3acb30b commit d53d3bd

File tree

3 files changed

+64
-53
lines changed

3 files changed

+64
-53
lines changed

app/lib/service/youtube/backend.dart

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -121,54 +121,64 @@ class _PkgOfWeekVideoFetcher {
121121
final youtube = YouTubeApi(apiClient);
122122

123123
try {
124+
final pageTokensVisited = <String>{};
125+
String nextPageToken = '';
126+
124127
final videos = <PkgOfWeekVideo>[];
125-
String? nextPageToken;
126-
for (var check = true; check && videos.length < 50;) {
127-
final rs = await cache.youtubePlaylistItems().get(
128+
final videoIds = <String>{};
129+
while (videos.length < 50) {
130+
// check visited status
131+
if (pageTokensVisited.contains(nextPageToken)) {
132+
break;
133+
}
134+
pageTokensVisited.add(nextPageToken);
135+
136+
// get page from cache or from Youtube API
137+
final rs = await cache.youtubePlaylistItems(nextPageToken).get(
128138
() async => await youtube.playlistItems.list(
129139
['snippet', 'contentDetails'],
130140
playlistId: powPlaylistId,
131-
pageToken: nextPageToken,
141+
pageToken: nextPageToken.isEmpty ? null : nextPageToken,
132142
),
133143
);
134-
videos.addAll(rs!.items!.map(
135-
(i) {
136-
try {
137-
final videoId = i.contentDetails?.videoId;
138-
if (videoId == null) {
139-
return null;
140-
}
141-
final thumbnails = i.snippet?.thumbnails;
142-
if (thumbnails == null) {
143-
return null;
144-
}
145-
final thumbnail = thumbnails.high ??
146-
thumbnails.default_ ??
147-
thumbnails.maxres ??
148-
thumbnails.standard ??
149-
thumbnails.medium;
150-
final thumbnailUrl = thumbnail?.url;
151-
if (thumbnailUrl == null || thumbnailUrl.isEmpty) {
152-
return null;
153-
}
154-
return PkgOfWeekVideo(
155-
videoId: videoId,
156-
title: i.snippet?.title ?? '',
157-
description:
158-
(i.snippet?.description ?? '').trim().split('\n').first,
159-
thumbnailUrl: thumbnailUrl,
160-
);
161-
} catch (e, st) {
162-
// this item will be skipped, the rest of the list may be displayed
163-
_logger.pubNoticeShout(
164-
'youtube', 'Processing Youtube PlaylistItem failed.', e, st);
144+
145+
// process playlist items
146+
for (final i in rs!.items!) {
147+
try {
148+
final videoId = i.contentDetails?.videoId;
149+
if (videoId == null || videoIds.contains(videoId)) {
150+
continue;
151+
}
152+
final thumbnails = i.snippet?.thumbnails;
153+
if (thumbnails == null) {
154+
continue;
155+
}
156+
final thumbnail = thumbnails.high ??
157+
thumbnails.default_ ??
158+
thumbnails.maxres ??
159+
thumbnails.standard ??
160+
thumbnails.medium;
161+
final thumbnailUrl = thumbnail?.url;
162+
if (thumbnailUrl == null || thumbnailUrl.isEmpty) {
163+
continue;
165164
}
166-
return null;
167-
},
168-
).nonNulls);
169-
// next page
170-
nextPageToken = rs.nextPageToken;
171-
check = nextPageToken != null && nextPageToken.isNotEmpty;
165+
videoIds.add(videoId);
166+
videos.add(PkgOfWeekVideo(
167+
videoId: videoId,
168+
title: i.snippet?.title ?? '',
169+
description:
170+
(i.snippet?.description ?? '').trim().split('\n').first,
171+
thumbnailUrl: thumbnailUrl,
172+
));
173+
} catch (e, st) {
174+
// this item will be skipped, the rest of the list may be displayed
175+
_logger.pubNoticeShout(
176+
'youtube', 'Processing Youtube PlaylistItem failed.', e, st);
177+
}
178+
}
179+
180+
// advance to next page token
181+
nextPageToken = rs.nextPageToken ?? '';
172182
}
173183
return videos;
174184
} finally {

app/lib/shared/redis_cache.dart

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -457,16 +457,17 @@ class CachePatterns {
457457
ResolvedDocUrlVersion.fromJson(v as Map<String, dynamic>),
458458
))['$package-$version'];
459459

460-
Entry<PlaylistItemListResponse> youtubePlaylistItems() => _cache
461-
.withPrefix('youtube/playlist-item-list-response/')
462-
.withTTL(Duration(hours: 6))
463-
.withCodec(utf8)
464-
.withCodec(json)
465-
.withCodec(wrapAsCodec(
466-
encode: (PlaylistItemListResponse v) => v.toJson(),
467-
decode: (v) =>
468-
PlaylistItemListResponse.fromJson(v as Map<String, dynamic>),
469-
))[''];
460+
Entry<PlaylistItemListResponse> youtubePlaylistItems(String pageToken) =>
461+
_cache
462+
.withPrefix('youtube/playlist-item-list-response/')
463+
.withTTL(Duration(hours: 6))
464+
.withCodec(utf8)
465+
.withCodec(json)
466+
.withCodec(wrapAsCodec(
467+
encode: (PlaylistItemListResponse v) => v.toJson(),
468+
decode: (v) =>
469+
PlaylistItemListResponse.fromJson(v as Map<String, dynamic>),
470+
))[pageToken];
470471
}
471472

472473
/// The active cache.

app/test/service/youtube/backend_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ void main() {
4040
});
4141

4242
test('selectRandomVideos', () {
43-
final random = Random(123);
4443
final items = <int>[0, 1, 2, 3, 4, 5, 6, 7, 9, 10];
4544

4645
for (var i = 0; i < 1000; i++) {
47-
final selected = selectRandomVideos(random, items, 4);
46+
final selected = selectRandomVideos(Random(i), items, 4);
4847

48+
expect(selected, hasLength(4));
4949
expect(selected.first, 0);
5050
expect(selected[1], greaterThan(0));
5151
expect(selected[1], lessThan(4));

0 commit comments

Comments
 (0)