Path: blob/master/src/packages/backend/get-listing.ts
1447 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Server directory listing through the HTTP server and Websocket API.78{files:[..., {size:?,name:?,mtime:?,isdir:?}]}910where mtime is integer SECONDS since epoch, size is in bytes, and isdir11is only there if true.1213Obviously we should probably use POST instead of GET, due to the14result being a function of time... but POST is so complicated.15Use ?random= or ?time= if you're worried about cacheing.16Browser client code only uses this through the websocket anyways.17*/1819import { reuseInFlight } from "@cocalc/util/reuse-in-flight";20import type { Dirent, Stats } from "node:fs";21import { lstat, opendir, readdir, readlink, stat } from "node:fs/promises";22import { getLogger } from "./logger";23import { DirectoryListingEntry } from "@cocalc/util/types";24import { join } from "path";2526const logger = getLogger("backend:directory-listing");2728// SMC_LOCAL_HUB_HOME is used for developing cocalc inside cocalc...29const HOME = process.env.SMC_LOCAL_HUB_HOME ?? process.env.HOME ?? "";3031const getListing = reuseInFlight(32async (33path: string, // assumed in home directory!34hidden: boolean = false,35{ home = HOME, limit }: { home?: string; limit?: number } = {},36): Promise<DirectoryListingEntry[]> => {37const dir = join(home, path);38logger.debug(dir);39const files: DirectoryListingEntry[] = [];40let file: Dirent;41for await (file of await opendir(dir)) {42if (limit && files.length >= limit) {43break;44}45if (!hidden && file.name[0] === ".") {46continue;47}48let entry: DirectoryListingEntry;49try {50// I don't actually know if file.name can fail to be JSON-able with node.js -- is there51// even a string in Node.js that cannot be dumped to JSON? With python52// this definitely was a problem, but I can't find the examples now. Users53// sometimes create "insane" file names via bugs in C programs...54JSON.stringify(file.name);55entry = { name: file.name };56} catch (err) {57entry = { name: "????", error: "Cannot display bad binary filename. " };58}5960try {61let stats: Stats;62if (file.isSymbolicLink()) {63// Optimization: don't explicitly set issymlink if it is false64entry.issymlink = true;65}66if (entry.issymlink) {67// at least right now we only use this symlink stuff to display68// information to the user in a listing, and nothing else.69try {70entry.link_target = await readlink(dir + "/" + entry.name);71} catch (err) {72// If we don't know the link target for some reason; just ignore this.73}74}75try {76stats = await stat(dir + "/" + entry.name);77} catch (err) {78// don't have access to target of link (or it is a broken link).79stats = await lstat(dir + "/" + entry.name);80}81entry.mtime = stats.mtime.valueOf() / 1000;82if (stats.isDirectory()) {83entry.isdir = true;84const v = await readdir(dir + "/" + entry.name);85if (hidden) {86entry.size = v.length;87} else {88// only count non-hidden files89entry.size = 0;90for (const x of v) {91if (x[0] != ".") {92entry.size += 1;93}94}95}96} else {97entry.size = stats.size;98}99} catch (err) {100entry.error = `${entry.error ? entry.error : ""}${err}`;101}102files.push(entry);103}104return files;105},106);107108export default getListing;109110111