Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/project/read_write_files.ts
Views: 687
/*1* decaffeinate suggestions:2* DS102: Remove unnecessary code created because of implicit returns3* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md4*/5//########################################################################6// This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.7// License: MS-RSL – see LICENSE.md for details8//########################################################################910import { CoCalcSocket } from "@cocalc/backend/tcp/enable-messaging-protocol";11import { execFile } from "node:child_process";12import { constants, Stats } from "node:fs";13import {14access,15readFile as readFileAsync,16stat as statAsync,17unlink,18writeFile,19} from "node:fs/promises";20import * as temp from "temp";2122import ensureContainingDirectoryExists from "@cocalc/backend/misc/ensure-containing-directory-exists";23import { abspath, uuidsha1 } from "@cocalc/backend/misc_node";24import * as message from "@cocalc/util/message";25import { path_split } from "@cocalc/util/misc";26import { check_file_size } from "./common";2728import { getLogger } from "@cocalc/backend/logger";29const winston = getLogger("read-write-files");3031//##############################################32// Read and write individual files33//##############################################3435// Read a file located in the given project. This will result in an36// error if the readFile function fails, e.g., if the file doesn't37// exist or the project is not open. We then send the resulting file38// over the socket as a blob message.39//40// Directories get sent as a ".tar.bz2" file.41// TODO: should support -- 'tar', 'tar.bz2', 'tar.gz', 'zip', '7z'. and mesg.archive option!!!42//43export async function read_file_from_project(socket: CoCalcSocket, mesg) {44const dbg = (...m) =>45winston.debug(`read_file_from_project(path='${mesg.path}'): `, ...m);46dbg("called");47let data: Buffer | undefined = undefined;48let path = abspath(mesg.path);49let is_dir: boolean | undefined = undefined;50let id: string | undefined = undefined;51let target: string | undefined = undefined;52let archive = undefined;53let stats: Stats | undefined = undefined;5455try {56//dbg("Determine whether the path '#{path}' is a directory or file.")57stats = await statAsync(path);58is_dir = stats.isDirectory();5960// make sure the file isn't too large61const size_check = check_file_size(stats.size);62if (size_check) {63throw new Error(size_check);64}6566// tar jcf a directory67if (is_dir) {68if (mesg.archive !== "tar.bz2") {69throw new Error(70"The only supported directory archive format is tar.bz2"71);72}73target = temp.path({ suffix: "." + mesg.archive });74//dbg("'#{path}' is a directory, so archive it to '#{target}', change path, and read that file")75archive = mesg.archive;76if (path[path.length - 1] === "/") {77// common nuisance with paths to directories78path = path.slice(0, path.length - 1);79}80const split = path_split(path);81// TODO same patterns also in project.ts82const args = [83"--exclude=.sagemathcloud*",84"--exclude=.forever",85"--exclude=.node*",86"--exclude=.npm",87"--exclude=.sage",88"-jcf",89target as string,90split.tail,91];92//dbg("tar #{args.join(' ')}")93await new Promise<void>((resolve, reject) => {94execFile(95"tar",96args,97{ cwd: split.head },98function (err, stdout, stderr) {99if (err) {100winston.debug(101`Issue creating tarball: ${err}, ${stdout}, ${stderr}`102);103return reject(err);104} else {105return resolve();106}107}108);109});110} else {111//Nothing to do, it is a file.112target = path;113}114if (!target) {115throw Error("bug -- target must be set");116}117118//dbg("Read the file into memory.")119data = await readFileAsync(target);120121// get SHA1 of contents122if (data == null) {123throw new Error("data is null");124}125id = uuidsha1(data);126//dbg("sha1 hash = '#{id}'")127128//dbg("send the file as a blob back to the hub.")129socket.write_mesg(130"json",131message.file_read_from_project({132id: mesg.id,133data_uuid: id,134archive,135})136);137138socket.write_mesg("blob", {139uuid: id,140blob: data,141ttlSeconds: mesg.ttlSeconds, // TODO does ttlSeconds work?142});143} catch (err) {144if (err && err !== "file already known") {145socket.write_mesg(146"json",147message.error({ id: mesg.id, error: `${err}` })148);149}150}151152// in any case, clean up the temporary archive153if (is_dir && target) {154try {155await access(target, constants.F_OK);156//dbg("It was a directory, so remove the temporary archive '#{path}'.")157await unlink(target);158} catch (err) {159winston.debug(`Error removing temporary archive '${target}': ${err}`);160}161}162}163164export function write_file_to_project(socket: CoCalcSocket, mesg) {165const dbg = (...m) =>166winston.debug(`write_file_to_project(path='${mesg.path}'): `, ...m);167dbg("called");168169const { data_uuid } = mesg;170const path = abspath(mesg.path);171172// Listen for the blob containing the actual content that we will write.173const write_file = async function (type, value) {174if (type === "blob" && value.uuid === data_uuid) {175socket.removeListener("mesg", write_file);176try {177await ensureContainingDirectoryExists(path);178await writeFile(path, value.blob);179socket.write_mesg(180"json",181message.file_written_to_project({ id: mesg.id })182);183} catch (err) {184socket.write_mesg("json", message.error({ id: mesg.id, error: err }));185}186}187};188socket.on("mesg", write_file);189}190191192