UNPKG

contact

Version:

private, one-to-one or many-to-many command line chat

116 lines (105 loc) 3.41 kB
import { promises as fs, createReadStream, createWriteStream } from 'node:fs' import os from 'node:os' import path from 'node:path' import { performance } from 'node:perf_hooks' import Transmission from 'contact-model/transmission' import { streamReadText } from 'stream-read-all' import { Readable } from 'node:stream' const configDir = path.join(os.homedir(), '.contact') const roomsDir = path.join(configDir, 'rooms') const historyPath = path.join(configDir, 'history') class Storage { existingPaths = new Set() async init () { try { await fs.mkdir(configDir) } catch (err) { if (err.code !== 'EEXIST') { throw err } } } async writeHistory (history) { history = history.filter(h => h.startsWith('/')) await fs.writeFile(historyPath, history.join('\n')) } async readHistory () { try { const history = await fs.readFile(historyPath, { encoding: 'utf8' }) return history.split('\n') } catch (err) { if (err.code === 'ENOENT') { return [] } else { throw err } } } async createDir (dirPath) { if (!this.existingPaths.has(dirPath)) { await fs.mkdir(dirPath, { recursive: true }) this.existingPaths.add(dirPath) } } async writeUnread (t) { const roomId = t.headers['room-id'] if (roomId) { const promises = [] const roomDirPath = path.join(roomsDir, roomId) await this.createDir(roomDirPath) const fileId = performance.now() * 1e6 const headerFilePath = path.join(roomDirPath, `${fileId}.json`) promises.push(fs.writeFile(headerFilePath, JSON.stringify(t.headers))) if (Number(t.headers['content-length']) > 0) { const bodyFilePath = path.join(roomDirPath, `${fileId}`) const prom = new Promise((resolve, reject) => { const stream = t.readable.pipe(createWriteStream(bodyFilePath)) stream.on('close', resolve) stream.on('error', reject) }) promises.push(prom) } return Promise.all(promises) } else { throw new Error('Room transmissions only') } } async io (fn) { try { const result = await fn() return result } catch (err) { if (err.code === 'ENOENT') { return null } else { throw new Error('I/O failed', { cause: err }) } } } async readUnread (roomId) { const roomDirPath = path.join(roomsDir, roomId) const roomDirFiles = await this.io(() => fs.readdir(roomDirPath)) if (roomDirFiles === null) { return [] } const headerFiles = roomDirFiles.sort().filter(f => /\.json/.test(f)) const transmissions = [] for (const file of headerFiles) { const headersPath = path.join(roomsDir, roomId, file) const headers = JSON.parse(await fs.readFile(headersPath)) await fs.rm(headersPath) const readablePath = path.join(roomsDir, roomId, path.basename(file, '.json')) const stat = await this.io(() => fs.stat(readablePath)) if (stat === null) { transmissions.push(new Transmission(headers)) } else { const readable = createReadStream(readablePath) const msg = await streamReadText(readable) transmissions.push(new Transmission(headers, Readable.from([msg]))) await fs.rm(readablePath) } } return transmissions } } export default Storage