import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios' import type { ProcessedData, RawData } from '../types/dataProcessor' import { DataProcessor } from './dataProcessor' import { Collection } from '@freearhey/core' import doFetch from '@ntlab/sfetch' import { isNode } from '../utils' import { BlocklistRecord, Category, Channel, City, Country, Feed, Guide, Language, Logo, Region, Stream, Subdivision, Timezone } from '../models' const files = [ 'blocklist', 'categories', 'channels', 'cities', 'countries', 'feeds', 'guides', 'languages', 'logos', 'regions', 'streams', 'subdivisions', 'timezones' ] export class DataManager { #rawData: RawData #processedData?: ProcessedData #client: AxiosInstance #baseUrl: string #dataDir: string #dataProcessor: DataProcessor constructor(config?: { dataDir?: string }) { const baseURL = 'https://iptv-org.github.io/api' this.#client = axios.create({ baseURL }) this.#baseUrl = baseURL this.#dataDir = config && config.dataDir ? config.dataDir : './temp/data' this.#dataProcessor = new DataProcessor() this.#rawData = {} } async downloadToDisk(config?: { request?: AxiosRequestConfig }) { const params = config ? config.request : undefined const requests = files.map(basename => this.downloadFileToDisk(basename, params)) await Promise.allSettled(requests).catch(console.error) } async downloadFileToDisk(basename: string, requestConfig?: AxiosRequestConfig) { try { if (!isNode()) throw new Error('Node.js is required to save files to the disk.') requestConfig = { ...requestConfig, ...{ responseType: 'stream' } } const fs = await import('node:fs') const path = await import('node:path') const filename = `${basename}.json` const targetPath = path.resolve(this.#dataDir, filename) const targetDir = path.dirname(targetPath) const response = await this.#client.get(filename, requestConfig) await fs.promises.mkdir(targetDir, { recursive: true }) const writer = fs.createWriteStream(targetPath) response.data.pipe(writer) return new Promise((resolve, reject) => { writer.on('finish', () => { resolve(true) }) writer.on('error', err => { reject(err) }) }) } catch (error) { throw error } } async loadFromDisk() { for (const basename of files) { this.#rawData[basename] = await this.loadFileFromDisk(basename) } } async loadFileFromDisk(basename: string): Promise { if (!isNode()) throw new Error('Node.js is required to load files from the disk.') const fs = await import('node:fs') const path = await import('node:path') try { const filepath = path.resolve(this.#dataDir, `${basename}.json`) const content = fs.readFileSync(filepath, 'utf8') return JSON.parse(content) } catch (err) { console.error(err) return [] } } async downloadToMemory(config?: { request?: AxiosRequestConfig }) { const params = config ? config.request : undefined const queue = files.map(basename => { return { basename, url: `${this.#baseUrl}/${basename}.json`, params } }) await doFetch(queue, (req, data) => { this.#rawData[req.basename] = data }).catch(console.error) } loadFromMemory() { // placeholder } processData() { this.#processedData = this.#dataProcessor.process(this.#rawData) } getProcessedData(): ProcessedData { return ( this.#processedData || { blocklist: new Collection(), categories: new Collection(), channels: new Collection(), cities: new Collection(), countries: new Collection(), feeds: new Collection(), guides: new Collection(), languages: new Collection(), logos: new Collection(), regions: new Collection(), streams: new Collection(), subdivisions: new Collection(), timezones: new Collection() } ) } getRawData(): RawData { return this.#rawData } setRawData(rawData: RawData) { this.#rawData = rawData } }