replace libarchive.js with 7z-wasm and fix utf-8 issue

This commit is contained in:
Salem Yaslem 2023-06-03 05:27:08 +03:00
parent 5f4329d8f9
commit e644375b75
9 changed files with 164 additions and 74 deletions

56
app.vue
View File

@ -1,13 +1,10 @@
<script setup lang="ts">
import { Archive } from 'libarchive.js/main.js';
import { CompressedFile } from 'libarchive.js/src/compressed-file';
import { getElementInfo } from "moveable";
import { VueSelecto } from "vue3-selecto";
import { useDisplay } from 'vuetify/lib/framework.mjs';
import { HistoryManager } from './composables/history-manager';
import { FilesManager } from './composables/files-manager';
Archive.init({
workerUrl: '/worker-bundle.js',
});
let display = useDisplay();
let drawer = ref(!display.mdAndDown.value);
@ -18,31 +15,16 @@ let filesList = ref<any>([]);
let selectedItem = useSelectedItem();
let filesGridList = ref<any>([])
let selectedList = ref<any>([]);
let history = new HistorySwitcher(selectedItem);
let history = new HistoryManager(selectedItem, filesList);
let fileManager = new FilesManager(filesList);
watchEffect(async () => {
if (files.value?.[0]) {
loadingModel.value = true;
filesList.value = [];
const archive = await Archive.open(files.value[0]);
fileManager.loadArchive(files.value?.[0]);
let extractedFiles = await archive.getFilesObject();
let getContent = (fileList: any, path = ''): any => {
return Object.keys(fileList).map(file => ({
name: file,
path: `${path}/${file}${!(fileList[file] instanceof File || fileList[file] instanceof CompressedFile) ? '/' : ''}`,
isFolder: !(fileList[file] instanceof File || fileList[file] instanceof CompressedFile),
content: !(fileList[file] instanceof File || fileList[file] instanceof CompressedFile) && fileList[file] ? getContent(fileList[file], `${path}/${file}`)?.sort((a: any, b: any) => {
return b.isFolder - a.isFolder
}) : fileList[file]
}))
}
filesList.value = getContent(extractedFiles)?.sort((a: any, b: any) => {
return b.isFolder - a.isFolder
});
loadingModel.value = false;
}
})
@ -71,33 +53,9 @@ onUnmounted(() => {
})
})
function getFile(path: string, innerList = undefined): any {
if (path == "/") {
return {
content: filesList.value
};
}
for (const file of (innerList || filesList.value)) {
if (file.path == path) {
return file;
}
if (file.isFolder && path.includes(file.path)) {
let recursiveFile = getFile(path, file.content);
if (recursiveFile) {
return recursiveFile;
}
}
}
return undefined;
}
watchEffect(() => {
filesGridList.value = getFile(selectedItem.value)?.content || [];
filesGridList.value = fileManager.getFile(selectedItem.value)?.content || [];
selectedList.value = [];
for (const selectedElement of document.querySelectorAll(".selectable.selected")) {
selectedElement.classList.remove("selected");
}
@ -188,7 +146,7 @@ function stepUp(path: string) {
</v-col>
</v-row>
</v-toolbar>
<template v-if="selectedItem.endsWith('/')">
<template v-if="fileManager.getFile(selectedItem)?.isFolder || false">
<v-container>
<v-list :selected="[selectedItem]">
<v-row no-gutters>

View File

@ -0,0 +1,131 @@
// class to load 7zip wasm module
// and extract files from archive
import SevenZip, { SevenZipModule } from "7z-wasm";
export class FilesManager {
sevenZip?: SevenZipModule;
consoleOutputBuffer: string[] = [];
path: Ref<string> = useSelectedItem();
constructor(private filesList: Ref<any[]>) {
this.init();
}
async init() {
this.sevenZip = await SevenZip({
wasmBinary: await fetch("/7zz.wasm").then((res) => res.arrayBuffer()),
print: (text) => {
if (text.lastIndexOf("\b")) {
text = text.substring(text.lastIndexOf("\b") + 1);
}
this.consoleOutputBuffer.push(text);
},
});
}
execute(commands: string[]) {
if (!this.sevenZip) return;
this.consoleOutputBuffer = [];
this.sevenZip.callMain(commands);
return this.consoleOutputBuffer;
}
async loadArchive(file: File) {
if (!this.sevenZip) return;
const archiveName = file.name;
const stream = this.sevenZip.FS.open(archiveName, "w+");
let archiveData = new Uint8Array(await file.arrayBuffer());
this.sevenZip.FS.write(stream, archiveData, 0, archiveData.byteLength);
this.sevenZip.FS.close(stream);
// 7zip get files list
let filesString = this.execute(["l", "-ba", archiveName]);
// parse files list
let unorganizedFiles = filesString!.map((fileString) => {
let file: RegExpMatchArray = /\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+(?<type>[AD.]+)\s+(?<size>\d+)\s+(?<compressed>\d+)\s+(?<path>.+)[\n\r]{0,}/.exec(fileString)!;
let isFolder = file.groups!.type.indexOf("D") > -1 ? true : false;
return {
name: file.groups!.path.lastIndexOf('/') > -1 ? file.groups!.path.substring(file.groups!.path.lastIndexOf('/') + 1) : file.groups!.path,
path: `/${file.groups!.path}`,
isFolder: isFolder ? true : false,
content: isFolder ? [] as any[] : undefined,
}
});
// sort unorganized files by depth
unorganizedFiles = unorganizedFiles.sort((a, b) => {
if (a.path.split("/").length > b.path.split("/").length) return -1;
if (a.path.split("/").length < b.path.split("/").length) return 1;
return 0;
});
// sort files and folder inside each folder
for (let file of unorganizedFiles) {
// get parent folder file
let parentFolderFile = unorganizedFiles.find(_file => _file.path == file.path.substring(0, file.path.lastIndexOf("/")));
if (!parentFolderFile) continue;
// add file to parent folder content
parentFolderFile.content!.push(file);
}
// remove folders from root
console.log(unorganizedFiles);
let files = unorganizedFiles.filter(file => file.path.split("/").length <= 2);
console.log(files);
this.filesList.value = files;
return files;
}
// wait until console output is empty
async waitConsoleOutput() {
if (!this.sevenZip) return;
let breakLoop = false;
let lastLength = this.consoleOutputBuffer.length;
while (!breakLoop) {
await new Promise((resolve) => {
if (lastLength == this.consoleOutputBuffer.length) {
return;
}
lastLength = this.consoleOutputBuffer.length;
setTimeout(resolve, 10)
});
}
return;
}
getFile(path: string, innerList = undefined): any {
if (path == "/") {
return {
content: this.filesList.value,
isFolder: true,
};
}
for (const file of (innerList || this.filesList.value)) {
if (file.path == path) {
return file;
}
if (file.isFolder && path.includes(file.path)) {
let recursiveFile = this.getFile(path, file.content);
if (recursiveFile) {
return recursiveFile;
}
}
}
return undefined;
}
}

View File

@ -1,5 +1,5 @@
// class to manage undo and redo history
export class HistorySwitcher {
export class HistoryManager {
// path to the current file
public path: Ref<String>
// history of paths
@ -11,7 +11,7 @@ export class HistorySwitcher {
public readonly hasRedo: ComputedRef<boolean> = computed(() => this.index.value !== this.history.value.length - 1);
// constructor
constructor(path: Ref<String>) {
constructor(path: Ref<String>, fileList: Ref<String[]>) {
this.path = path
this.history = ref([path.value])
this.index = ref(0);

29
package-lock.json generated
View File

@ -8,7 +8,7 @@
"hasInstallScript": true,
"dependencies": {
"@mdi/font": "^7.2.96",
"libarchive.js": "^1.3.0",
"7z-wasm": "^1.0.2",
"moveable": "^0.47.7",
"sass": "^1.62.1",
"selecto": "^1.22.3",
@ -2038,6 +2038,20 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.2.tgz",
"integrity": "sha512-0rFu3h8JbclbnvvKrs7Fe5FNGV9/5X2rPD7KmOzhLSUAiQH5//Hq437Gv0fR5Mev3u/nbtvmLl8XgwCU20/ZfQ=="
},
"node_modules/7z-wasm": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/7z-wasm/-/7z-wasm-1.0.2.tgz",
"integrity": "sha512-HyqFcihZoM9u897gjcAVGFHb7AFBg8TrcfgSSA5mP4j5MkWYElAt+n/XoPuPQMll+mcbcp+9GmS+nPG1jDy8Yw==",
"dependencies": {
"readline-sync": "^1.4.10"
},
"bin": {
"7z-wasm": "bin/cli"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -4679,11 +4693,6 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/libarchive.js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/libarchive.js/-/libarchive.js-1.3.0.tgz",
"integrity": "sha512-EkQfRXt9DhWwj6BnEA2TNpOf4jTnzSTUPGgE+iFxcdNqjktY8GitbDeHnx8qZA0/IukNyyBUR3oQKRdYkO+HFg=="
},
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@ -6873,6 +6882,14 @@
"node": ">=8.10.0"
}
},
"node_modules/readline-sync": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
"integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",

View File

@ -18,7 +18,7 @@
},
"dependencies": {
"@mdi/font": "^7.2.96",
"libarchive.js": "^1.3.0",
"7z-wasm": "^1.0.2",
"moveable": "^0.47.7",
"sass": "^1.62.1",
"selecto": "^1.22.3",

BIN
public/7zz.wasm Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long