const fs = require("fs"); const path = require("path"); const Koa = require("koa"); const koaPug = require("koa-pug"); const koaRouter = require("koa-router"); const koaRange = require("koa-range"); //const koaServe = require("koa-static"); const FileType = require("file-type"); const config = require("./config"); const util = require("./util"); const { type } = require("os"); const { ENOENT, ECONNRESET } = require("constants"); const sizeUnits = [ "B", "KiB", "MiB", "GiB" ]; function makeCurrentPathField(path) { let current = ""; if (path.endsWith("/")) path = path.slice(1, path.length-1); const path_parts = path.split("/"); current = `root`; for (i = 0; i < path_parts.length; ++i) { let lnk = ""; for (j = 0; j < i+1; ++j) { lnk += `/${path_parts[j]}`; } current += `/${path_parts[i]}`; } return current; } function convertSize(size) { let i = 0; for (; size > 1024; size /= 1024) { ++i; } return [i > 0 ? size.toFixed(2) : size, sizeUnits[i]]; } function fileType(name) { if (name.endsWith("txt") || name.endsWith("md")) return "text/plain"; return "octet-stream"; } async function getDirectoryList(dir_path, base_url) { let dirs = []; let files = []; let d = await fs.promises.opendir(dir_path); for await (const dirent of d) { const stat = fs.lstatSync(path.join(dir_path, dirent.name)); const [s, u] = convertSize(stat.size); if (stat.isDirectory()) dirs.push({ name: dirent.name, link: `${base_url}${encodeURIComponent(dirent.name)}/`, datetime: util.datetime(stat.mtime, util.formats.file_date), size: "DIR", size_unit: '' }); else files.push({ name: dirent.name, link: `${base_url}${encodeURIComponent(dirent.name)}`, datetime: util.datetime(stat.mtime, util.formats.file_date), size: +s, size_unit: u }); } const sort_by_name = (a, b) => { if (a.name > b.name) return 1; else if (a.name < b.name) return -1; return 0; } dirs.sort(sort_by_name); files.sort(sort_by_name); return dirs.concat(files); } function setRoutes(router) { router.get('/(.*)?', async (ctx, next) => { if (ctx.originalUrl.startsWith("/assets") || ctx.originalUrl.startsWith("/shared")) { await next(); return; } const file_path = path.join(config.files.file_path, decodeURI(ctx.originalUrl)); let stat = fs.lstatSync(file_path); if (stat.isDirectory()) { await ctx.render('index', { title: "/ Files", description: "File share.", host: util.getBaseHost(ctx.header.host), current_path: makeCurrentPathField(decodeURI(ctx.originalUrl)), items: await getDirectoryList(file_path, ctx.originalUrl) }); } else { let f = await FileType.fromStream(fs.createReadStream(file_path)); if (f === undefined) ctx.type = fileType(file_path); else ctx.type = f.mime; ctx.set("Content-Length", stat.size); const stream = fs.createReadStream(file_path); // let ch = 0; // stream.on("end", () => { }); // stream.on("data", (chunk) => { ch += chunk.length; }); ctx.body = stream; } }); } module.exports = () => { const app = new Koa(); const pug = new koaPug({ viewPath: path.join(__dirname, "views", "files"), locals: { moment: date => util.datetime(date, util.formats.file_date) }, app: app }); const radio_router = koaRouter(); setRoutes(radio_router); app.proxy = true; app .use(koaRange) .use(radio_router.routes()) // .use(koaServe(path.join("static", "shared"))) // .use(koaServe(path.join("static", "files"))) .on("error", err => { if (!err.code == ECONNRESET || !err.code == ENOENT) console.log(err.code); }) .listen(config.files.port, config.files.host); return app; }