Rearchitect import (#6141)

This commit is contained in:
Tom Moor
2023-11-13 20:15:38 -05:00
committed by GitHub
parent 2143269bcd
commit 1898a34418
10 changed files with 302 additions and 244 deletions

View File

@@ -0,0 +1,67 @@
import path from "path";
import fs from "fs-extra";
import { deserializeFilename } from "./fs";
export type FileTreeNode = {
/** The title, extracted from the file name */
title: string;
/** The file name including extension */
name: string;
/** Full path to the file within the zip file */
path: string;
/** Any nested children */
children: FileTreeNode[];
};
export default class ImportHelper {
/**
* Collects the files and folders for a directory filePath.
*/
public static async toFileTree(
filePath: string,
currentDepth = 0
): Promise<FileTreeNode | null> {
const name = path.basename(filePath);
const title = deserializeFilename(path.parse(path.basename(name)).name);
const item = {
path: filePath,
name,
title,
children: [] as FileTreeNode[],
};
let stats;
if ([".DS_Store", "__MACOSX"].includes(name)) {
return null;
}
try {
stats = await fs.stat(filePath);
} catch (e) {
return null;
}
if (stats.isFile()) {
return item;
}
if (stats.isDirectory()) {
const dirData = await fs.readdir(filePath);
if (dirData === null) {
return null;
}
item.children = (
await Promise.all(
dirData.map((child) =>
this.toFileTree(path.join(filePath, child), currentDepth + 1)
)
)
).filter(Boolean) as FileTreeNode[];
} else {
return null;
}
return item;
}
}

View File

@@ -1,24 +1,9 @@
import fs from "fs";
import path from "path";
import JSZip from "jszip";
import find from "lodash/find";
import tmp from "tmp";
import { bytesToHumanReadable } from "@shared/utils/files";
import { ValidationError } from "@server/errors";
import Logger from "@server/logging/Logger";
import { trace } from "@server/logging/tracing";
import { deserializeFilename } from "./fs";
export type FileTreeNode = {
/** The title, extracted from the file name */
title: string;
/** The file name including extension */
name: string;
/** Full path to the file within the zip file */
path: string;
/** Any nested children */
children: FileTreeNode[];
};
@trace()
export default class ZipHelper {
@@ -32,62 +17,6 @@ export default class ZipHelper {
},
};
/**
* Converts the flat structure returned by JSZIP into a nested file structure
* for easier processing.
*
* @param zip The JSZip instance
* @param maxFiles The maximum number of files to unzip (Prevent zip bombs)
*/
public static toFileTree(
zip: JSZip,
/** The maximum number of files to unzip */
maxFiles = 10000
) {
const paths = ZipHelper.getPathsInZip(zip, maxFiles);
const tree: FileTreeNode[] = [];
paths.forEach(function (filePath) {
if (filePath.startsWith("/__MACOSX")) {
return;
}
const pathParts = filePath.split("/");
// Remove first blank element from the parts array.
pathParts.shift();
let currentLevel = tree; // initialize currentLevel to root
pathParts.forEach(function (name) {
// check to see if the path already exists.
const existingPath = find(currentLevel, {
name,
});
if (existingPath) {
// The path to this item was already in the tree, so don't add again.
// Set the current level to this path's children
currentLevel = existingPath.children;
} else if (name.endsWith(".DS_Store") || !name) {
return;
} else {
const newPart = {
name,
path: filePath.replace(/^\//, ""),
title: deserializeFilename(path.parse(path.basename(name)).name),
children: [],
};
currentLevel.push(newPart);
currentLevel = newPart.children;
}
});
});
return tree;
}
/**
* Write a zip file to a temporary disk location
*
@@ -158,34 +87,4 @@ export default class ZipHelper {
);
});
}
/**
* Gets a list of file paths contained within the ZIP file, accounting for
* differences between OS.
*
* @param zip The JSZip instance
* @param maxFiles The maximum number of files to unzip (Prevent zip bombs)
*/
private static getPathsInZip(zip: JSZip, maxFiles = 10000) {
let fileCount = 0;
const paths: string[] = [];
Object.keys(zip.files).forEach((p) => {
if (++fileCount > maxFiles) {
throw ValidationError("Too many files in zip");
}
const filePath = `/${p}`;
// "zip.files" for ZIPs created on Windows does not return paths for
// directories, so we must add them manually if missing.
const dir = filePath.slice(0, filePath.lastIndexOf("/") + 1);
if (dir.length > 1 && !paths.includes(dir)) {
paths.push(dir);
}
paths.push(filePath);
});
return paths;
}
}