chore(tmdb): use mutex to avoid double tmdb requests

This commit is contained in:
WebStreamr 2025-09-08 08:31:44 +00:00
parent 3b8481ed3f
commit e1b5e7bce1
No known key found for this signature in database
2 changed files with 72 additions and 41 deletions

View file

@ -8,33 +8,40 @@ const ctx = createTestContext();
describe('getTmdbIdFromImdbId', () => {
test('series', async () => {
const tmdbId1 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt2085059', 2, 4));
expect(tmdbId1).toBeInstanceOf(TmdbId);
expect(tmdbId1).toHaveProperty('id', 42009);
expect(tmdbId1).toHaveProperty('season', 2);
expect(tmdbId1).toHaveProperty('episode', 4);
const results = await Promise.all([
getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt2085059', 2, 4)),
getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt2085059', 2, 4)),
]);
expect(results[0]).toBeInstanceOf(TmdbId);
expect(results[0]).toHaveProperty('id', 42009);
expect(results[0]).toHaveProperty('season', 2);
expect(results[0]).toHaveProperty('episode', 4);
// from cache
const tmdbId2 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt2085059', 2, 4));
expect(tmdbId2).toBeInstanceOf(TmdbId);
expect(tmdbId2).toHaveProperty('id', 42009);
expect(tmdbId2).toHaveProperty('season', 2);
expect(tmdbId2).toHaveProperty('episode', 4);
const tmdbId = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt2085059', 2, 4));
expect(tmdbId).toBeInstanceOf(TmdbId);
expect(tmdbId).toHaveProperty('id', 42009);
expect(tmdbId).toHaveProperty('season', 2);
expect(tmdbId).toHaveProperty('episode', 4);
});
test('movie', async () => {
const tmdbId1 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt29141112', undefined, undefined));
expect(tmdbId1).toBeInstanceOf(TmdbId);
expect(tmdbId1).toHaveProperty('id', 931944);
expect(tmdbId1).toHaveProperty('season', undefined);
expect(tmdbId1).toHaveProperty('episode', undefined);
const results = await Promise.all([
getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt29141112', undefined, undefined)),
getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt29141112', undefined, undefined)),
]);
expect(results[0]).toBeInstanceOf(TmdbId);
expect(results[0]).toHaveProperty('id', 931944);
expect(results[0]).toHaveProperty('season', undefined);
expect(results[0]).toHaveProperty('episode', undefined);
// from cache
const tmdbId2 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt29141112', undefined, undefined));
expect(tmdbId2).toBeInstanceOf(TmdbId);
expect(tmdbId2).toHaveProperty('id', 931944);
expect(tmdbId2).toHaveProperty('season', undefined);
expect(tmdbId2).toHaveProperty('episode', undefined);
const tmdbId = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt29141112', undefined, undefined));
expect(tmdbId).toBeInstanceOf(TmdbId);
expect(tmdbId).toHaveProperty('id', 931944);
expect(tmdbId).toHaveProperty('season', undefined);
expect(tmdbId).toHaveProperty('episode', undefined);
});
test('throws with invalid imdb id', async () => {
@ -44,33 +51,41 @@ describe('getTmdbIdFromImdbId', () => {
describe('getImdbIdFromTmdbId', () => {
test('series', async () => {
const imdbId1 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(42009, 2, 4));
expect(imdbId1).toBeInstanceOf(ImdbId);
expect(imdbId1).toHaveProperty('id', 'tt2085059');
expect(imdbId1).toHaveProperty('season', 2);
expect(imdbId1).toHaveProperty('episode', 4);
const results = await Promise.all([
getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(42009, 2, 4)),
getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(42009, 2, 4)),
]);
expect(results[0]).toBeInstanceOf(ImdbId);
expect(results[0]).toHaveProperty('id', 'tt2085059');
expect(results[0]).toHaveProperty('season', 2);
expect(results[0]).toHaveProperty('episode', 4);
// from cache
const imdbId2 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(42009, 2, 4));
expect(imdbId2).toBeInstanceOf(ImdbId);
expect(imdbId2).toHaveProperty('id', 'tt2085059');
expect(imdbId2).toHaveProperty('season', 2);
expect(imdbId2).toHaveProperty('episode', 4);
const imdbId = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(42009, 2, 4));
expect(imdbId).toBeInstanceOf(ImdbId);
expect(imdbId).toHaveProperty('id', 'tt2085059');
expect(imdbId).toHaveProperty('season', 2);
expect(imdbId).toHaveProperty('episode', 4);
});
test('movie', async () => {
const imdbId1 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(931944, undefined, undefined));
expect(imdbId1).toBeInstanceOf(ImdbId);
expect(imdbId1).toHaveProperty('id', 'tt29141112');
expect(imdbId1).toHaveProperty('season', undefined);
expect(imdbId1).toHaveProperty('episode', undefined);
const results = await Promise.all([
getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(931944, undefined, undefined)),
getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(931944, undefined, undefined)),
]);
expect(results[0]).toBeInstanceOf(ImdbId);
expect(results[0]).toHaveProperty('id', 'tt29141112');
expect(results[0]).toHaveProperty('season', undefined);
expect(results[0]).toHaveProperty('episode', undefined);
// from cache
const imdbId2 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(931944, undefined, undefined));
expect(imdbId2).toBeInstanceOf(ImdbId);
expect(imdbId2).toHaveProperty('id', 'tt29141112');
expect(imdbId2).toHaveProperty('season', undefined);
expect(imdbId2).toHaveProperty('episode', undefined);
const imdbId = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(931944, undefined, undefined));
expect(imdbId).toBeInstanceOf(ImdbId);
expect(imdbId).toHaveProperty('id', 'tt29141112');
expect(imdbId).toHaveProperty('season', undefined);
expect(imdbId).toHaveProperty('episode', undefined);
});
test('throws with invalid tmdb id', async () => {

View file

@ -1,3 +1,4 @@
import { Mutex } from 'async-mutex';
import { NotFoundError } from '../error';
import { Context } from '../types';
import { envGet } from './env';
@ -27,6 +28,7 @@ interface TvDetailsResponsePartial {
name: string;
}
const mutexes = new Map<string, Mutex>();
const tmdbFetch = async (ctx: Context, fetcher: Fetcher, path: string, searchParams?: Record<string, string | undefined>): Promise<unknown> => {
const config: CustomRequestInit = {
headers: {
@ -44,7 +46,21 @@ const tmdbFetch = async (ctx: Context, fetcher: Fetcher, path: string, searchPar
}
});
return JSON.parse(await fetcher.text(ctx, url, config));
let mutex = mutexes.get(url.href);
if (!mutex) {
mutex = new Mutex();
mutexes.set(url.href, mutex);
}
const data = await mutex.runExclusive(async () => {
return JSON.parse(await fetcher.text(ctx, url, config));
});
if (!mutex.isLocked()) {
mutexes.delete(url.href);
}
return data;
};
const imdbTmdbMap = new Map<string, number>();