CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/lib/share/get-contents.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import pathToFiles from "./path-to-files";
7
import { promises as fs } from "fs";
8
import { join } from "path";
9
import { sortBy } from "lodash";
10
import { hasSpecialViewer } from "@cocalc/frontend/file-extensions";
11
import { getExtension } from "./util";
12
13
const MB: number = 1000000;
14
15
const LIMITS = {
16
listing: 3000, // directory listing is truncated after this many files
17
ipynb: 7 * MB,
18
sagews: 5 * MB,
19
whiteboard: 3 * MB,
20
slides: 3 * MB,
21
other: 1 * MB,
22
// no special viewer
23
generic: 2 * MB,
24
};
25
26
// also used for proxied content -- see https://github.com/sagemathinc/cocalc/issues/8020
27
export function getSizeLimit(path: string): number {
28
const ext = getExtension(path);
29
if (hasSpecialViewer(ext)) {
30
return LIMITS[ext] ?? LIMITS.other;
31
}
32
return LIMITS.generic;
33
}
34
35
export interface FileInfo {
36
name: string;
37
error?: Error;
38
isdir?: boolean;
39
size?: number;
40
mtime?: number;
41
url?: string; // if given and click on this file, goes here. Can be used to make path canonical and is used for navigating github repos (say).
42
}
43
44
export interface PathContents {
45
isdir?: boolean;
46
listing?: FileInfo[];
47
content?: string;
48
size?: number;
49
mtime?: number;
50
truncated?: string;
51
}
52
53
export default async function getContents(
54
project_id: string,
55
path: string,
56
): Promise<PathContents> {
57
const fsPath = pathToFiles(project_id, path);
58
const obj: PathContents = {};
59
60
// use lstat instead of stat so it works on symlinks too
61
const stats = await fs.lstat(fsPath);
62
obj.isdir = stats.isDirectory();
63
obj.mtime = stats.mtime.valueOf();
64
if (obj.isdir) {
65
// get listing
66
const { listing, truncated } = await getDirectoryListing(fsPath);
67
obj.listing = listing;
68
if (truncated) {
69
obj.truncated = truncated;
70
}
71
} else {
72
// get actual file content
73
if (stats.size >= getSizeLimit(fsPath)) {
74
obj.truncated = "File too big to be displayed; download it instead.";
75
} else {
76
obj.content = (await fs.readFile(fsPath)).toString();
77
}
78
obj.size = stats.size;
79
}
80
return obj;
81
}
82
83
async function getDirectoryListing(
84
path: string,
85
): Promise<{ listing: FileInfo[]; truncated?: string }> {
86
const listing: FileInfo[] = [];
87
let truncated: string | undefined = undefined;
88
for (const name of await fs.readdir(path)) {
89
if (name.startsWith(".")) {
90
// We never grab hidden files. This is a public share server after all.
91
continue;
92
}
93
const obj: FileInfo = { name };
94
// use lstat instead of stat so it works on symlinks too
95
try {
96
const stats = await fs.lstat(join(path, name));
97
if (stats.isDirectory()) {
98
obj.isdir = true;
99
// For a directory, we define "size" to be the number of items
100
// in the directory.
101
obj.size = (await fs.readdir(join(path, name))).length;
102
} else {
103
obj.size = stats.size;
104
}
105
obj.mtime = stats.mtime.valueOf();
106
} catch (err) {
107
obj.error = err;
108
}
109
listing.push(obj);
110
if (listing.length >= LIMITS.listing) {
111
truncated = `Too many files -- only showing ${LIMITS.listing} of them.`;
112
break;
113
}
114
}
115
return { listing: sortBy(listing, ["name"]), truncated };
116
}
117
118