/**
 * Contains Cache, which is used to store temporary expiring data
 * (usually directly from the API), and the factory for DataWriter
 * which is how Tasks primarily save their results to disk.
 */ import * as fs from 'fs-extra' ; import path from 'path' ; import type { MPEModule } from '../../types/mpe' ; import { Aliases , Module , ModuleCode , ModuleCondensed , ModuleInformation , RawLesson , Semester , SemesterData , } from '../../types/modules' ; import { Venue , VenueInfo } from '../../types/venues' ; import { Cache , Persist } from '../../types/persist' ; import config from '../../config' ; import { CacheExpiredError } from '../../utils/errors' ; const defaultExpiry = 24 * 60 ; const writeOptions = { spaces : process . env . NODE_ENV === 'production' ? 0 : 2 , } ; const getFileRoot = ( academicYear ) => path . join ( config . dataPath , academicYear . replace ( '/' , '-' ) ) ; /**
 * Create a cache factory for BaseTask
 */ export function getCacheFactory ( academicYear ) { const cacheRoot = path . join ( getFileRoot ( academicYear ) , 'cache' ) ; return function getCache < T > ( key , expiryInMin = defaultExpiry ) : Cache < T > { const filepath = path . join ( cacheRoot , `${ key }.json` ) ; return { path : filepath , write : ( o ) => fs . outputJSON ( filepath , o , writeOptions ) , read : async ( ) => { const [ data , stat ] = await Promise . all ( [ fs . readJSON ( filepath ) , fs . stat ( filepath ) ] ) ; if ( Date . now ( ) - stat . mtimeMs > expiryInMin * 60 * 1000 ) { throw new CacheExpiredError ( 'Cache expired' , filepath , stat . mtimeMs ) ; } return data ; } , } ; } ; } /**
 * Create an object that maps each of the file output from the scraper.
 * Uses a factory so that objects are not shared. Since the Tasks only see
 * a function to send output to, this can be replaced with any persistence
 * mechanism in the future, eg. a database
 */ export function getFileSystemWriter ( academicYear ) { const yearRoot = getFileRoot ( academicYear ) ; return { moduleList : ( data ) => fs . outputJSON ( path . join ( yearRoot , 'moduleList.json' ) , data , writeOptions ) , moduleInfo : ( data ) => fs . outputJSON ( path . join ( yearRoot , 'moduleInfo.json' ) , data , writeOptions ) , mpeModules : ( data ) => fs . outputJSON ( path . join ( yearRoot , 'mpeModules.json' ) , data , writeOptions ) , moduleInformation : ( data ) => fs . outputJSON ( path . join ( yearRoot , 'moduleInformation.json' ) , data , writeOptions ) , moduleAliases : ( data ) => fs . outputJSON ( path . join ( yearRoot , 'aliases.json' ) , data , writeOptions ) , facultyDepartments : ( data ) => fs . outputJSON ( path . join ( yearRoot , 'facultyDepartments.json' ) , data , writeOptions ) , module : ( moduleCode , data ) => fs . outputJSON ( path . join ( yearRoot , 'modules' , `${ moduleCode }.json` ) , data , writeOptions ) , timetable : ( semester , moduleCode , data ) => fs . outputJSON ( path . join ( yearRoot , 'semesters' , String ( semester ) , moduleCode , 'timetable.json' ) , data , writeOptions ) , semesterData : ( semester , moduleCode , data ) => fs . outputJSON ( path . join ( yearRoot , 'semesters' , String ( semester ) , moduleCode , 'semesterData.json' ) , data , writeOptions ) , getModuleCodes : async ( ) => { let files ; try { files = await fs . readdir ( path . join ( yearRoot , 'modules' ) ) ; } catch ( e ) { if ( e . code === 'ENOENT' ) { files = [ ] ; } else { throw e ; } } return files . map ( ( filename ) => path . basename ( filename , '.json' ) ) ; } , deleteModule : async ( moduleCode ) => { await Promise . all ( [ fs . remove ( path . join ( yearRoot , 'modules' , `${ moduleCode }.json` ) ) , fs . remove ( path . join ( yearRoot , 'semesters' , '1' , moduleCode ) ) , fs . remove ( path . join ( yearRoot , 'semesters' , '2' , moduleCode ) ) , fs . remove ( path . join ( yearRoot , 'semesters' , '3' , moduleCode ) ) , fs . remove ( path . join ( yearRoot , 'semesters' , '4' , moduleCode ) ) , ] ) ; } , venueList : ( semester , data ) => fs . outputJSON ( path . join ( yearRoot , 'semesters' , String ( semester ) , 'venues.json' ) , data , writeOptions ) , venueInformation : ( semester , data ) => fs . outputJSON ( path . join ( yearRoot , 'semesters' , String ( semester ) , 'venueInformation.json' ) , data , writeOptions ) , } ; }