"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.frontEndApis = void 0;
exports.notifyClient = notifyClient;
exports.moveFiles = moveFiles;
exports.requestedRename = requestedRename;
const apiMiddleware_1 = require("./apiMiddleware");
const api_get_file_list_1 = require("./api.get_file_list");
const api_auth_1 = require("./api.auth");
const events_1 = __importDefault(require("./events"));
const util_files_1 = require("./util-files");
const const_1 = require("./const");
const vfs_1 = require("./vfs");
const fs_1 = __importDefault(require("fs"));
const promises_1 = require("fs/promises");
const path_1 = require("path");
const upload_1 = require("./upload");
const misc_1 = require("./misc");
const config_1 = require("./config");
const comments_1 = require("./comments");
const SendList_1 = require("./SendList");
const adminApis_1 = require("./adminApis");
const lodash_1 = __importDefault(require("lodash"));
const partialFolderSize = {};
const showUploader = (0, config_1.defineConfig)(misc_1.CFG.show_uploader, misc_1.WHO_ADMIN);
exports.frontEndApis = {
    get_file_list: api_get_file_list_1.get_file_list,
    ...api_auth_1.authApis,
    get_notifications({ channel }, ctx) {
        (0, misc_1.apiAssertTypes)({ string: { channel } });
        const list = new SendList_1.SendListReadable();
        list.ready(); // on chrome109 EventSource doesn't emit 'open' until something is sent
        return list.events(ctx, {
            [NOTIFICATION_PREFIX + channel](name, data) {
                list.custom(name, data);
            }
        }, { warnAfter: 10_000 }); // we may have many clients on the same channel (eg: chat), and we don't want to be spammed with console warnings
    },
    async get_file_details({ uris }, ctx) {
        if (!Array.isArray(uris) || typeof uris[0] !== 'string')
            return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad uris');
        const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx);
        return {
            details: (0, vfs_1.simpleWhoToError)(showUploader.get(), ctx) ? [] // return early because at the moment we only have the uploader
                : await Promise.all(uris.map(async (uri) => {
                    if (typeof uri !== 'string')
                        return false; // false means error
                    const node = await (0, vfs_1.urlToNode)(uri, ctx);
                    if (!node || !(0, vfs_1.hasPermission)(node, 'can_see', ctx))
                        return false;
                    let upload = node.source && await (0, upload_1.getUploadMeta)(node.source).catch(() => undefined);
                    if (!upload)
                        return;
                    if (!isAdmin)
                        upload = lodash_1.default.omit(upload, 'ip');
                    return { upload };
                }))
        };
    },
    async create_folder({ uri, name }, ctx) {
        (0, misc_1.apiAssertTypes)({ string: { uri, name } });
        try {
            ctx.logExtra(null, { name, target: (0, misc_1.pathDecode)(uri) });
        }
        catch {
            return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
        }
        if (!name || !(0, util_files_1.isValidFileName)(name))
            return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
        const parentNode = await (0, vfs_1.urlToNode)(uri, ctx);
        if (!parentNode)
            return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'parent not found');
        const err = (0, vfs_1.statusCodeForMissingPerm)(parentNode, 'can_upload', ctx);
        if (err)
            return new apiMiddleware_1.ApiError(err);
        try {
            await (0, promises_1.mkdir)((0, path_1.join)(parentNode.source, name));
            return {};
        }
        catch (e) {
            return new apiMiddleware_1.ApiError(e.code === 'EEXIST' ? const_1.HTTP_CONFLICT : const_1.HTTP_BAD_REQUEST, e);
        }
    },
    // dest is not encoded
    async rename({ uri, dest }, ctx) {
        (0, misc_1.apiAssertTypes)({ string: { uri, dest } });
        try {
            ctx.logExtra(null, { target: (0, misc_1.pathDecode)(uri), destination: dest });
        }
        catch {
            return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
        }
        const node = await (0, vfs_1.urlToNode)(uri, ctx);
        if (!node)
            throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
        if ((0, vfs_1.isRoot)(node) || !(0, util_files_1.isValidFileName)(dest))
            return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
        await requestedRename(node, dest, ctx);
        return {};
    },
    async move_files({ uri_from, uri_to }, ctx) {
        return moveFiles(uri_from, uri_to, ctx);
    },
    async copy_files({ uri_from, uri_to }, ctx) {
        return moveFiles(uri_from, uri_to, ctx, (srcNode, dest) => // override behavior
         (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_read', ctx)
            || (0, promises_1.copyFile)(srcNode.source, dest, fs_1.default.constants.COPYFILE_EXCL | fs_1.default.constants.COPYFILE_FICLONE) // .source is checked by moveFiles
                .catch(e => e.code || String(e)));
    },
    async comment({ uri, comment }, ctx) {
        (0, misc_1.apiAssertTypes)({ string: { uri, comment } });
        try {
            ctx.logExtra(null, { target: (0, misc_1.pathDecode)(uri) });
        }
        catch {
            return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
        }
        const node = await (0, vfs_1.urlToNode)(uri, ctx);
        if (!node)
            return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
        if (!(0, vfs_1.hasPermission)(node, 'can_upload', ctx))
            return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
        if (!node.source)
            return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY);
        await (0, comments_1.setCommentFor)(node.source, comment);
        return {};
    },
    async get_folder_size_partial({ id }) {
        (0, misc_1.apiAssertTypes)({ string: { id } });
        return partialFolderSize[id] || new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
    },
    async get_folder_size({ uri, id }, ctx) {
        (0, misc_1.apiAssertTypes)({ string: { uri } });
        const folder = await (0, vfs_1.urlToNode)(uri, ctx);
        if (!folder)
            return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
        if (!(0, vfs_1.nodeIsFolder)(folder))
            return new apiMiddleware_1.ApiError(const_1.HTTP_METHOD_NOT_ALLOWED);
        if ((0, vfs_1.statusCodeForMissingPerm)(folder, 'can_list', ctx))
            return new apiMiddleware_1.ApiError(ctx.status);
        let bytes = 0;
        let files = 0;
        let folders = 0;
        for await (const n of (0, vfs_1.walkNode)(folder, { ctx, depth: Infinity })) {
            if (n.isFolder)
                folders++;
            else {
                bytes += await (0, vfs_1.nodeStats)(n).then(x => x?.size || 0, () => 0);
                files++;
            }
            partialFolderSize[id] = { bytes, files, folders };
        }
        return (0, misc_1.popKey)(partialFolderSize, id) || { bytes, files, folders };
    },
};
function notifyClient(channel, name, data) {
    if (typeof channel !== 'string')
        channel = String(channel.query.notifications);
    events_1.default.emit(NOTIFICATION_PREFIX + channel, name, data);
}
const NOTIFICATION_PREFIX = 'notificationChannel:';
async function moveFiles(uri_from, uri_to, ctx, override) {
    (0, misc_1.apiAssertTypes)({ array: { uri_from }, string: { uri_to } });
    try {
        ctx.logExtra(null, { target: uri_from.map(misc_1.pathDecode), destination: (0, misc_1.pathDecode)(uri_to) });
    }
    catch {
        return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
    }
    const destNode = await (0, vfs_1.urlToNode)(uri_to, ctx);
    const err = !destNode ? const_1.HTTP_NOT_FOUND
        : !(0, vfs_1.nodeIsFolder)(destNode) ? const_1.HTTP_METHOD_NOT_ALLOWED
            : (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_upload', ctx);
    if (err)
        return new apiMiddleware_1.ApiError(err);
    return {
        errors: await Promise.all(uri_from.map(async (from1) => {
            if (typeof from1 !== 'string')
                return const_1.HTTP_BAD_REQUEST;
            const srcNode = await (0, vfs_1.urlToNode)(from1, ctx);
            const src = srcNode?.source;
            if (!src)
                return const_1.HTTP_NOT_FOUND;
            const destName = (0, path_1.basename)(src);
            const destChild = await (0, vfs_1.urlToNode)(destName, ctx, destNode);
            if (destChild && (0, vfs_1.statusCodeForMissingPerm)(destChild, 'can_delete', ctx))
                return ctx.status;
            const dest = (0, path_1.join)(destNode.source, destName);
            if (lodash_1.default.isFunction(override))
                return override?.(srcNode, dest);
            return (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_delete', ctx)
                || (0, promises_1.rename)(src, dest).catch(async (e) => {
                    if (e.code !== 'EXDEV')
                        throw e; // exdev = different drive
                    await (0, promises_1.copyFile)(src, dest);
                    await (0, promises_1.unlink)(src);
                }).then(() => (0, misc_1.moveStoredFileAttrs)(src, dest))
                    .catch(e => e.code || String(e));
        }))
    };
}
async function requestedRename(node, newName, ctx) {
    if (!node)
        throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
    if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
        return new apiMiddleware_1.ApiError(ctx.status);
    try {
        if (node.name) // virtual name = virtual rename
            node.name = newName;
        else {
            if (!node.source)
                throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY);
            const destNode = await (0, vfs_1.urlToNode)((0, misc_1.pathEncode)(newName), ctx, node.parent);
            if (destNode && (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_delete', ctx)) // if destination exists, you need delete permission
                return new apiMiddleware_1.ApiError(ctx.status);
            try {
                const destSource = (0, path_1.join)((0, path_1.dirname)(node.source), newName);
                await (0, promises_1.rename)(node.source, destSource);
                await (0, misc_1.moveStoredFileAttrs)(node.source, destSource);
                (0, comments_1.getCommentFor)(node.source).then(c => {
                    if (!c)
                        return;
                    void (0, comments_1.setCommentFor)(node.source, '');
                    void (0, comments_1.setCommentFor)(destSource, c);
                });
                return {};
            }
            catch (e) {
                return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e);
            }
        }
        return;
    }
    catch (e) {
        throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e);
    }
}
