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