"use strict";
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.get_file_list = void 0;
exports.paramsToFilter = paramsToFilter;
const vfs_1 = require("./vfs");
const apiMiddleware_1 = require("./apiMiddleware");
const plugins_1 = require("./plugins");
const misc_1 = require("./misc");
const const_1 = require("./const");
const comments_1 = require("./comments");
const path_1 = require("path");
const connections_1 = require("./connections");
const adminApis_1 = require("./adminApis");
const upload_1 = require("./upload");
const SendList_1 = require("./SendList");
const events_1 = __importDefault(require("./events"));
function paramsToFilter({ search, wild, searchComment, fileMask }) {
    search = String(search || '').toLocaleLowerCase();
    searchComment = String(searchComment || '').toLocaleLowerCase();
    return {
        depth: search || searchComment ? Infinity : 0,
        filterName: search > '' && (wild === 'no' ? (s) => s.includes(search) : (0, misc_1.pattern2filter)(search)),
        fileMask: fileMask > '' && (0, misc_1.pattern2filter)(fileMask),
        filterComment: searchComment > '' && (wild === 'no' ? (s) => s.includes(searchComment) : (0, misc_1.pattern2filter)(searchComment))
    };
}
const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFiles, admin, ...rest }, ctx) => {
    (0, misc_1.apiAssertTypes)({ string: { uri } });
    const node = await (0, vfs_1.urlToNode)(uri, ctx);
    const list = ctx.get('accept') === 'text/event-stream' ? new SendList_1.SendListReadable() : undefined;
    if (!node)
        return fail(const_1.HTTP_NOT_FOUND);
    admin &&= (0, adminApis_1.ctxAdminAccess)(ctx); // validate 'admin' flag
    if (await (0, vfs_1.getDefaultFile)(node, ctx) || !(0, vfs_1.nodeIsFolder)(node)) // for files without permission, the frontend is sent, and the location is the file itself
        // so, we first check if you have a permission problem, to tell frontend to show login, otherwise we fall back to method_not_allowed, as it's proper for files.
        return fail(!admin && (0, vfs_1.statusCodeForMissingPerm)(node, 'can_read', ctx) ? undefined : const_1.HTTP_METHOD_NOT_ALLOWED);
    if (!admin && (0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx))
        return fail();
    offset = Number(offset);
    limit = Number(limit);
    const { filterName, filterComment, fileMask, depth } = paramsToFilter(rest);
    const walker = (0, vfs_1.walkNode)(node, { ctx: admin ? undefined : ctx, onlyFolders, onlyFiles, depth });
    const onDirEntryHandlers = (0, plugins_1.mapPlugins)((plug, id) => plug.onDirEntry && { id, cb: plug.onDirEntry });
    const can_upload = admin || (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
    const can_delete = admin || (0, vfs_1.hasPermission)(node, 'can_delete', ctx);
    const fakeChild = await (0, vfs_1.applyParentToChild)({ source: 'dummy-file', original: undefined }, node); // used to check permission; simple but can produce false results; 'original' to simulate a non-vfs node
    const can_delete_children = admin || (0, vfs_1.hasPermission)(fakeChild, 'can_delete', ctx);
    const can_archive = admin || (0, vfs_1.hasPermission)(node, 'can_archive', ctx);
    const can_comment = can_upload && (0, comments_1.areCommentsEnabled)();
    const can_overwrite = can_upload && (can_delete_children || !upload_1.dontOverwriteUploading.get());
    const comment = node.comment ?? await (0, comments_1.getCommentFor)(node.source);
    const props = { can_archive, can_upload, can_delete, can_delete_children, can_overwrite, can_comment, comment, accept: node.accept, icon: getNodeIcon(node) };
    ctx.state.browsing = uri.replace(/\/{2,}/g, '/');
    (0, connections_1.updateConnectionForCtx)(ctx);
    if (!list)
        return { ...props, list: await (0, misc_1.asyncGeneratorToArray)(produceEntries()) };
    setTimeout(async () => {
        list.props(props);
        for await (const entry of produceEntries())
            list.add(entry);
        list.close();
    });
    return list;
    function fail(code = ctx.status) {
        if (!list)
            return new apiMiddleware_1.ApiError(code);
        list.error(code, true);
        return list;
    }
    async function* produceEntries() {
        for await (const sub of walker) {
            let name = (0, vfs_1.getNodeName)(sub);
            name = (0, path_1.basename)(name) || name; // on Windows, basename('C:') === ''
            if (filterName && !filterName(name) || fileMask && !(0, vfs_1.nodeIsFolder)(sub) && !fileMask(name)
                || filterComment && !filterComment(await (0, comments_1.getCommentFor)(sub.source) || ''))
                continue;
            const entry = await nodeToDirEntry(ctx, sub);
            if (!entry)
                continue;
            const cbParams = { entry, ctx, listUri: uri, node: sub };
            try {
                const res = await Promise.all(onDirEntryHandlers.map(({ id, cb }) => Promise.resolve().then(() => cb(cbParams)).catch(error => { throw { id, error }; })));
                if (res.some(x => x === false))
                    continue;
            }
            catch (e) {
                console.warn(`Plugin ${e?.id || '?'} is causing problems on onDirEntry:`, e?.error ?? e);
            }
            if ((await events_1.default.emitAsync('dirEntry', cbParams))?.isDefaultPrevented())
                continue;
            if (offset) {
                --offset;
                continue;
            }
            if (c === 'no' && entry.c) // allow excluding c for smaller payload
                entry.c = undefined;
            yield entry;
            if (limit && !--limit)
                break;
        }
    }
    function getNodeIcon(node) {
        return node.icon?.includes('.') || node.icon; // send just true when the icon is specific and must be retrieved by the frontend with ?get=icon, otherwise is a SYS_ICONS
    }
    async function nodeToDirEntry(ctx, node) {
        const { source, url } = node;
        const name = (0, vfs_1.getNodeName)(node);
        const isFolder = (0, vfs_1.nodeIsFolder)(node);
        try {
            const [web, comment, st] = await Promise.all([
                (0, vfs_1.getDefaultFile)(node, ctx).then(x => x ? true : undefined),
                node.comment ?? (0, comments_1.getCommentFor)(source),
                (0, vfs_1.nodeStats)(node).catch(e => {
                    if (!isFolder || !node.children?.length) // folders with virtual children, keep them
                        throw e;
                })
            ]);
            // permissions of entries are sent as a difference with permissions of parent
            const pl = node.can_list === misc_1.WHO_NO_ONE ? 'l'
                : !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
                    : '';
            // no download here, but maybe inside?
            const pr = node.can_read === misc_1.WHO_NO_ONE && !(isFolder && filesInsideCould()) ? 'r'
                : !(0, vfs_1.hasPermission)(node, 'can_read', ctx) ? 'R'
                    : '';
            // for delete, the diff is based on can_delete_children instead of can_delete, because it will produce less data
            const pd = Boolean(can_delete_children) === (0, vfs_1.hasPermission)(node, 'can_delete', ctx) ? '' : can_delete_children ? 'd' : 'D';
            const pa = Boolean(can_archive) === (0, vfs_1.hasPermission)(node, 'can_archive', ctx) ? '' : can_archive ? 'a' : 'A';
            const pu = !isFolder || Boolean(can_upload) === (0, vfs_1.hasPermission)(node, 'can_upload', ctx) ? '' : can_upload ? 'u' : 'U';
            return {
                n: name + (isFolder ? '/' : ''),
                c: st?.birthtime,
                m: !st || Math.abs(st.mtimeMs - st.birthtimeMs) < 1000 ? undefined : st.mtime,
                s: isFolder ? undefined : st?.size,
                p: (pr + pl + pd + pa + pu) || undefined,
                url,
                target: node.target,
                order: node.order,
                comment,
                icon: getNodeIcon(node),
                web,
            };
        }
        catch {
            return null;
        }
        function filesInsideCould(n = node) {
            return (0, vfs_1.masksCouldGivePermission)(n.masks, 'can_read')
                || n.children?.some(c => c.can_read || filesInsideCould(c)); // we count on the boolean-compliant nature of the permission type here
        }
    }
};
exports.get_file_list = get_file_list;
