diff --git a/.gitignore b/.gitignore index 0784e61..262499b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist .cache +cache diff --git a/src/tam/sources/tam.js b/src/tam/sources/tam.js index 6b93146..dbc2c9e 100644 --- a/src/tam/sources/tam.js +++ b/src/tam/sources/tam.js @@ -1,5 +1,7 @@ const csv = require("csv-parse"); const axios = require("axios"); +const path = require("path"); +const fs = require("fs").promises; const { snakeToCamelCase, unzipFile } = require("../../util"); /** @@ -17,15 +19,65 @@ const { snakeToCamelCase, unzipFile } = require("../../util"); * @property {string} directionId Route identifier inside the line. * @property {string} departureTime Theoretical time at which the * vehicle will depart the stop (HH:MM:SS format). - * @property {string} isTheorical (sic) Whether the arrival time is only - * a theoretical information. + * @property {string} isTheorical (sic) True if this is only the planned + * passing time, false if this is real-time information. * @property {string} delaySec Number of seconds before the vehicle arrives - * at the station. + * at the station (only if isTheorical is false). * @property {string} destArCode Unique network identifier for the final - * stop of this trip. + * stop of this trip (only if isTheorical is false). */ +/** + * Wrap a passing-fetching function to use a filesystem-based cache. + * + * @param {function} func Fetching function to wrap. + * @param {string} cachePath Path to the file to use as a cache (will be + * overwritten, may be non-existing). + * @return {function} Wrapped function. + */ +const makeCached = (func, cachePath) => { + return async function *() { + try { + const cacheRaw = await fs.readFile(cachePath, {encoding: "utf8"}); + const cache = JSON.parse(cacheRaw); + + if (Date.now() < cache.timing.nextUpdate) { + yield cache.timing; + + for (const passing of cache.passings) { + yield passing; + } + + return; + } + } catch (err) { + // Ignore missing cache file + if (err.code !== 'ENOENT') { + throw err; + } + } + + const passings = func(); + const newCache = { + timing: (await passings.next()).value, + passings: [], + }; + + yield newCache.timing; + + for await (const passing of passings) { + newCache.passings.push(passing); + yield passing; + } + + fs.writeFile(cachePath, JSON.stringify(newCache)); + }; +}; + +const cacheDir = path.join(__dirname, "..", "..", "..", "cache"); + const realtimeEndpoint = "http://data.montpellier3m.fr/node/10732/download"; +const realtimeCachePath = path.join(cacheDir, "realtime.json"); /** * Fetch real time passings of vehicles across the network. @@ -54,9 +106,10 @@ const fetchRealtime = async function *() { } }; -exports.fetchRealtime = fetchRealtime; +exports.fetchRealtime = makeCached(fetchRealtime, realtimeCachePath); const theoreticalEndpoint = "http://data.montpellier3m.fr/node/10731/download"; +const theoreticalCachePath = path.join(cacheDir, "theoretical.json"); /** * Fetch theoretical passings for the current day across the network. @@ -101,4 +154,4 @@ const fetchTheoretical = async function *() { } }; -exports.fetchTheoretical = fetchTheoretical; +exports.fetchTheoretical = makeCached(fetchTheoretical, theoreticalCachePath);