'use strict'; const fsp = require('fs-promise'); const path = require('path'); const debug = require('debug')('psitransfer:db'); const config = require('../config'); module.exports = class DB { constructor(uploadDir, store) { this.initialized = false; this.db = {}; this.expireTimers = {}; this.uploadDir = uploadDir; this.store = store; // delete expired files const gc = () => { let sid,f,expires; for (sid of Object.keys(this.db)) { for (f of this.db[sid]) { // expire on maxAge expires = (+f.metadata.createdAt) + (config.maxAge * 1000) - Date.now(); // respect one-time downloads if(expires > 0 && Number.isInteger(+f.metadata.retention)) { expires = (+f.metadata.createdAt) + (+f.metadata.retention * 1000) - Date.now(); } if(expires <= 0) { debug(`Expired ${sid}++${f.key}`); this.remove(sid, f.key).catch(e => console.error(e)); } } } }; setInterval(gc, 60*1000); } init() { if(this.initialized) return; this.initialized = true; try { this._sync(); } catch(e) { this.initialized = false; e.message = `db initialization failed with error ${e.message}`; throw e; } } /** * @private */ _sync() { fsp.ensureDirSync(this.uploadDir); fsp.readdirSync(this.uploadDir).forEach((sid) => { this._import(sid); }); } /** * @private */ _import(sid) { const p = path.resolve(this.uploadDir, sid); const stat = fsp.statSync(p); if(!stat.isDirectory()) return; fsp.readdirSync(p).forEach(async (key) => { if(path.extname(key) !== '') { return Promise.resolve(); } try { let info = await this.store.info(`${sid}++${key}`); this.add(sid, key, info); } catch(e) { console.error(e); } }); } add(sid, key, data) { if(!this.initialized) throw new Error('DB not initialized_'); if(!this.db[sid]) this.db[sid] = []; data.key = key; const old = this.db[sid].findIndex(i => i.key === key); if(old !== -1) { this.db[sid].splice(old, 1, data); debug(`Updated ${sid}++${key}`); } else { this.db[sid].push(data); debug(`Added ${sid}++${key}`); } } async remove(sid, key) { if(!this.initialized) throw new Error('DB not initialized'); debug(`Remove ${sid}++${key}`); await this.store.del(sid + '++' + key); const i = this.db[sid].findIndex(item => item.key === key); this.get(sid).splice(i, 1); if(this.get(sid).length === 0) { delete this.db[sid]; await fsp.rmdir(path.resolve(this.uploadDir, sid)); } } async updateLastDownload(sid, key) { debug(`Update last download ${sid}++${key}`); const data = this.get(sid).find(item => item.key === key); if(!data) return; data.metadata.lastDownload = Date.now(); await this.store.update(`${sid}++${key}`, data); } get(sid) { return this.db[sid]; } };