Path: blob/master/src/packages/frontend/editors/archive/actions.ts
1691 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { webapp_client } from "@cocalc/frontend/webapp-client";6import {7filename_extension,8filename_extension_notilde,9keys,10path_split,11split,12} from "@cocalc/util/misc";13import { Actions, redux_name } from "../../app-framework";14import { register_file_editor } from "../../project-file";15import { Archive } from "./component";16import { COMMANDS, DOUBLE_EXT } from "./misc";1718function init_redux(path: string, redux, project_id: string): string {19const name = redux_name(project_id, path);20if (redux.getActions(name) != null) {21return name;22}23redux.createStore(name);24const actions = redux.createActions(name, ArchiveActions);25actions.setArchiveContents(project_id, path);26return name;27}2829function remove_redux(path: string, redux, project_id: string): string {30const name = redux_name(project_id, path);31redux.removeActions(name);32redux.removeStore(name);33return name;34}3536interface State {37contents?: string;38type?: string;39loading?: boolean;40command?: string;41error?: string;42extract_output?: string;43}4445export class ArchiveActions extends Actions<State> {46private project_id: string;47private path: string;4849parse_file_type(file_info: string): string | undefined {50if (file_info.indexOf("Zip archive data") !== -1) {51return "zip";52} else if (file_info.indexOf("tar archive") !== -1) {53return "tar";54} else if (file_info.indexOf("gzip compressed data") !== -1) {55return "gz";56} else if (file_info.indexOf("bzip2 compressed data") !== -1) {57return "bzip2";58} else if (file_info.indexOf("lzip compressed data") !== -1) {59return "lzip";60} else if (file_info.indexOf("XZ compressed data") !== -1) {61return "xz";62}63return undefined;64}6566setUnsupported(ext: string | undefined): void {67this.setState({68error: "unsupported",69contents: "",70type: ext,71});72}7374/**75* Extract the extension, and check if there is a tilde.76*/77private extractExtension(pathReal: string): string | null {78const path = pathReal.toLowerCase(); // convert to lowercase for case-insensitive matching79const ext0 = filename_extension_notilde(path);80const ext = filename_extension(path);81if (ext0 !== ext) {82this.setState({83error: "Rename the archive file to not end in a tilde.",84});85return null;86}87// there are "double extension" with a dot, like "tar.bz2"88for (const ext of DOUBLE_EXT) {89if (path.endsWith(`.${ext}`)) {90return ext;91}92}93return ext;94}9596private exec = async (opts) => {97const { project_id, path } = this;98const compute_server_id =99(await webapp_client.project_client.getServerIdForPath({100project_id,101path,102})) ?? 0;103return await webapp_client.exec({104filesystem: true,105compute_server_id,106project_id,107...opts,108});109};110111async setArchiveContents(project_id: string, path: string): Promise<void> {112this.project_id = project_id;113this.path = path;114const ext = this.extractExtension(path);115if (ext === null) return;116117if (COMMANDS[ext]?.list == null) {118this.setUnsupported(ext);119return;120}121122const { command, args } = COMMANDS[ext].list;123124try {125const output = await this.exec({126command,127args: args.concat([path]),128err_on_exit: true,129});130this.setState({131error: undefined,132contents: output?.stdout,133type: ext,134});135} catch (err) {136this.setState({137error: err?.toString(),138contents: undefined,139type: ext,140});141}142}143144async extractArchiveFiles(145type: string | undefined,146contents: string | undefined,147): Promise<void> {148if (type == null || COMMANDS[type]?.extract == null) {149this.setUnsupported(type);150return;151}152let post_args;153let { command, args } = COMMANDS[type].extract;154const path_parts = path_split(this.path);155let extra_args: string[] = (post_args = []);156let output: any = undefined;157let base;158let error: string | undefined = undefined;159this.setState({ loading: true });160try {161if (contents == null) {162throw Error("Archive not loaded yet");163} else if (type === "zip") {164// special case for zip files: if heuristically it looks like not everything is contained165// in a subdirectory with name the zip file, then create that subdirectory.166base = path_parts.tail.slice(0, path_parts.tail.length - 4);167if (contents.indexOf(base + "/") === -1) {168extra_args = ["-d", base];169}170} else if (["tar", "tar.gz", "tar.bz2"].includes(type)) {171// special case for tar files: if heuristically it looks like not everything is contained172// in a subdirectory with name the tar file, then create that subdirectory.173const i = path_parts.tail.lastIndexOf(".t"); // hopefully that's good enough.174base = path_parts.tail.slice(0, i);175if (contents.indexOf(base + "/") === -1) {176post_args = ["-C", base];177await this.exec({178path: path_parts.head,179command: "mkdir",180args: ["-p", base],181error_on_exit: true,182});183}184}185args = args186.concat(extra_args != null ? extra_args : [])187.concat([path_parts.tail])188.concat(post_args);189const args_str = args190.map((x) => (x.indexOf(" ") !== -1 ? `'${x}'` : x))191.join(" ");192const cmd = `cd \"${path_parts.head}\" ; ${command} ${args_str}`; // ONLY for info purposes -- not actually run!193this.setState({ command: cmd });194output = await this.exec({195path: path_parts.head,196command,197args,198err_on_exit: true,199timeout: 120,200});201} catch (err) {202error = err.toString();203}204205this.setState({206error,207extract_output: output?.stdout,208loading: false,209});210}211}212213// TODO: change ext below to use keys(COMMANDS). We don't now, since there are a214// ton of extensions that should open in the archive editor, but aren't implemented215// yet and we don't want to open those in codemirror -- see https://github.com/sagemathinc/cocalc/issues/1720216// NOTE: One you implement one of these (so it is in commands), be217// sure to delete it from the list below.218const TODO_TYPES = split("z lz lzma tbz tb2 taz tz tlz txz");219register_file_editor({220ext: keys(COMMANDS).concat(TODO_TYPES),221icon: "file-archive",222init: init_redux,223remove: remove_redux,224component: Archive,225});226227228