Skip to content

Commit 2b0b5c4

Browse files
authored
Fix caching of Youtube API pagination + filter duplicate videoIds. (#8472)
1 parent 7065813 commit 2b0b5c4

File tree

3 files changed

+58
-51
lines changed

3 files changed

+58
-51
lines changed

app/lib/service/youtube/backend.dart

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

123123
try {
124-
final videos = <PkgOfWeekVideo>[];
125124
String? nextPageToken;
126-
for (var check = true; check && videos.length < 50;) {
127-
final rs = await cache.youtubePlaylistItems().get(
125+
126+
final videos = <PkgOfWeekVideo>[];
127+
final videoIds = <String>{};
128+
while (videos.length < 50) {
129+
// get page from cache or from Youtube API
130+
final rs = await cache.youtubePlaylistItems(nextPageToken ?? '').get(
128131
() async => await youtube.playlistItems.list(
129132
['snippet', 'contentDetails'],
130133
playlistId: powPlaylistId,
131134
pageToken: nextPageToken,
132135
),
133136
);
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);
137+
138+
// process playlist items
139+
for (final i in rs!.items!) {
140+
try {
141+
final videoId = i.contentDetails?.videoId;
142+
if (videoId == null || videoIds.contains(videoId)) {
143+
continue;
144+
}
145+
final thumbnails = i.snippet?.thumbnails;
146+
if (thumbnails == null) {
147+
continue;
148+
}
149+
final thumbnail = thumbnails.high ??
150+
thumbnails.default_ ??
151+
thumbnails.maxres ??
152+
thumbnails.standard ??
153+
thumbnails.medium;
154+
final thumbnailUrl = thumbnail?.url;
155+
if (thumbnailUrl == null || thumbnailUrl.isEmpty) {
156+
continue;
165157
}
166-
return null;
167-
},
168-
).nonNulls);
169-
// next page
158+
videoIds.add(videoId);
159+
videos.add(PkgOfWeekVideo(
160+
videoId: videoId,
161+
title: i.snippet?.title ?? '',
162+
description:
163+
(i.snippet?.description ?? '').trim().split('\n').first,
164+
thumbnailUrl: thumbnailUrl,
165+
));
166+
} catch (e, st) {
167+
// this item will be skipped, the rest of the list may be displayed
168+
_logger.pubNoticeShout(
169+
'youtube', 'Processing Youtube PlaylistItem failed.', e, st);
170+
}
171+
}
172+
173+
// advance to next page token
170174
nextPageToken = rs.nextPageToken;
171-
check = nextPageToken != null && nextPageToken.isNotEmpty;
175+
if (nextPageToken == null) {
176+
break;
177+
}
172178
}
173179
return videos;
174180
} 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)