nrss/lib/caching.ts

111 lines
3.0 KiB
TypeScript

import { nrkRadio } from "./nrk/nrk.ts";
import { Series, storage } from "./storage.ts";
import * as datetime from "$std/datetime/mod.ts";
import { Episode } from "./storage.ts";
const SYNC_INTERVAL_HOURS = 1;
async function initialFetch(options: { id: string }): Promise<Series | null> {
const series = await nrkRadio.getSeries(options.id);
if (!series) {
return null;
}
const stored = storage.writeSeries(series);
if (!stored) {
console.error(`Failed to store series ${options.id}`);
return null;
}
return series;
}
type UpdatedSeries = {
lastFetch: Date;
episodes: Episode[];
} & Series;
async function updateFetch(existingSeries: Series): Promise<UpdatedSeries | Series | null> {
const series = await nrkRadio.getSeries(existingSeries.id);
if (!series) {
return null;
}
const newEpisodes = series.episodes.filter((episode) => {
return !existingSeries.episodes.find((serieEpisode) => serieEpisode.id === episode.id);
});
if (newEpisodes.length === 0) {
return existingSeries;
}
/**
* Since we don't control the API,
* we should not make assumptions about the order,
* but rather sort the episode to what we want.
*/
const episodesSortedDescending = [...newEpisodes, ...existingSeries.episodes]
.sort((a, b) => a.date.getTime() > b.date.getTime() ? -1 : 1);
const updated = {
...existingSeries,
lastFetch: new Date(),
episodes: episodesSortedDescending,
};
const updateSuccessful = await storage.writeSeries(updated);
if (!updateSuccessful) {
console.log(`Failed to update series ${existingSeries.id}`);
return null;
}
return updated;
}
function getTimeSinceLastFetch(inputDate: Date, againstDate = new Date()): number | null {
return datetime.difference(inputDate, againstDate, { units: ["hours"] }).hours ?? null;
}
function isSeriesFromStorageNew(
seriesFromStorage: Series,
syncInterval = SYNC_INTERVAL_HOURS,
timeSinceLastFetch = getTimeSinceLastFetch(seriesFromStorage.lastFetchedAt),
) {
return !Number.isNaN(timeSinceLastFetch) &&
seriesFromStorage !== null &&
timeSinceLastFetch !== null &&
timeSinceLastFetch !== undefined &&
timeSinceLastFetch <= syncInterval;
}
async function getSeries(options: { id: string }): Promise<Series | null> {
const seriesFromStorage = await storage.readSeries(options);
/**
* We don't have the feed in storage,
* and we need to fetch it for the first time.
*/
if (seriesFromStorage === null) {
// TODO: schedule a job to fetch the entire backlog https://github.com/olaven/NRSS/issues/24
return await initialFetch(options);
}
// we have the feed in storage and it's not too old
if (isSeriesFromStorageNew(seriesFromStorage)) {
return seriesFromStorage;
}
/**
* We have the feed in storage, but it's too old
* and needs to be refreshed.
*/
const updatedSeries = await updateFetch(seriesFromStorage);
return updatedSeries;
}
export const caching = {
getSeries,
};
export const forTestingOnly = {
getTimeSinceLastFetch,
isSeriesFromStorageNew,
};