move wasm import to be handle by vite. move 7zip management to separate worker

This commit is contained in:
Salem Yaslem 2023-06-13 01:58:19 +03:00
parent 7968a8c032
commit 16e1d5b701
8 changed files with 164 additions and 119 deletions

View File

@ -4,13 +4,14 @@ import { VueSelecto } from "vue3-selecto";
import { useDisplay } from 'vuetify/lib/framework.mjs';
import { HistoryManager } from './composables/history-manager';
import { FilesManager } from './composables/files-manager';
import type { iFile } from "composables/worker/7zip-manager"
let display = useDisplay();
let drawer = ref(!display.mdAndDown.value);
let loadingModel = ref(false);
let files = ref([]);
let filesList = ref<any>([]);
let filesList = ref<iFile[]>([]);
let selectedPath = useSelectedPath();
let filesGridList = ref<any>([])
let selectedList = ref<any>([]);
@ -22,7 +23,7 @@ watchEffect(async () => {
loadingModel.value = true;
filesList.value = [];
filesManager.loadArchive(files.value?.[0]);
await filesManager.loadArchive(files.value?.[0]);
loadingModel.value = false;
}

View File

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
import { CompressedFile } from 'libarchive.js/src/compressed-file';
import type { iFile } from '~/composables/worker/7zip-manager';
interface Props {
filesList: (File | CompressedFile) & { name: string, path: string, toggle: Boolean, isFolder: Boolean, content: any, active: boolean }[],
filesList: iFile[],
nav: boolean
}
@ -13,7 +14,7 @@ let selectedPath = useSelectedPath();
<template>
<v-list :selected="[selectedPath]" density="compact" :nav="nav">
<template v-for="file in filesList" :key="file.path">
<v-list-item active-color="light-blue-darken-4" :active=file.active :title="file.name" :subtitle="file.path"
<v-list-item active-color="light-blue-darken-4" :active="selectedPath == file.path" :title="file.name" :subtitle="file.path"
:value="file.path" @click="() => {
selectedPath = file.path;
file.toggle = !file.toggle;
@ -27,7 +28,7 @@ let selectedPath = useSelectedPath();
<v-icon>{{ `mdi-chevron-${file.toggle ? 'up' : 'down'}` }}</v-icon>
</template>
</v-list-item>
<div v-if="file.isFolder && file.toggle"
<div v-if="file.isFolder && file.toggle && file.content"
:style="`background-color: rgba(0,0,0,0.05);${nav ? `border-radius: 4px;` : ''}`">
<TreeView :files-list="file.content" :nav="false"></TreeView>
</div>

View File

@ -1,134 +1,30 @@
// class to load 7zip wasm module
// and extract files from archive
import SevenZip, { SevenZipModule } from "7z-wasm";
import * as Comlink from "comlink";
// @ts-expect-error typescript can't find it when query it with ?worker
import SevenZipWorker from "./worker/7zip-manager?worker";
import { SevenZipManager, iFile } from "./worker/7zip-manager";
export class FilesManager {
sevenZip?: SevenZipModule;
consoleOutputBuffer: string[] = [];
path: Ref<string> = useSelectedPath();
remoteSevenZipManager?: Comlink.Remote<SevenZipManager>;
constructor(private filesList: Ref<any[]>) {
constructor(private filesList: Ref<iFile[]>) {
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;
this.remoteSevenZipManager = await new (Comlink.wrap(new SevenZipWorker()) as any);
}
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 = /[\s+|(\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) => {
const aPathArrayLength = (a.path.match(/\//g) || []).length;
const bPathArrayLength = (b.path.match(/\//g) || []).length;
if (aPathArrayLength > bPathArrayLength) return -1;
if (aPathArrayLength < bPathArrayLength) return 1;
return 0;
});
// sort files and folder inside each folder
const pathArrays: any = {};
for (let file of unorganizedFiles) {
// get parent folder file
const parentPath = file.path.substring(0, file.path.lastIndexOf("/"));
let parentFolderFile = pathArrays[parentPath] || unorganizedFiles.find(_file => _file.path === parentPath);
if (!parentFolderFile) {
unorganizedFiles.push({
name: parentPath.substring(parentPath.lastIndexOf('/') + 1),
path: parentPath,
isFolder: true,
content: [],
});
parentFolderFile = unorganizedFiles.find(_file => _file.path === parentPath);
};
// add file to parent folder content
if (!parentFolderFile.content) parentFolderFile.content = [];
parentFolderFile.content.push(file);
// cache split path array to avoid calling split() multiple times
const pathArray = pathArrays[file.path] || file.path.split("/");
pathArrays[file.path] = pathArray;
}
// remove folders from root
const files = unorganizedFiles.filter(file => (file.path.match(/\//g) || []).length == 1).sort((a:any, b:any) => {
// sort by folder and from a to z
if (a.isFolder && !b.isFolder) return -1;
if (!a.isFolder && b.isFolder) return 1;
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
});
this.filesList.value = files;
return files;
if (!this.remoteSevenZipManager) return;
this.filesList.value = await this.remoteSevenZipManager.loadArchive(file) || [];
}
// 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) {
breakLoop = true;
return;
}
lastLength = this.consoleOutputBuffer.length;
setTimeout(resolve, 10)
});
}
return;
}
getFile(path: string, innerList = undefined): any {
getFile(path: string, innerList: iFile[] | undefined = undefined): any {
if (path == "/") {
return {
content: this.filesList.value.sort((a:any, b:any) => {

View File

@ -0,0 +1,140 @@
// class to load 7zip wasm module
// and extract files from archive
import SevenZip, { SevenZipModule } from "7z-wasm";
// @ts-expect-error 7z-wasm have that file but typescript can't find it when query it with url
import SevenZipWasm from "7z-wasm/7zz.wasm?url";
import * as Comlink from "comlink";
export interface iFile {
name: string;
path: string;
isFolder: boolean;
toggle: boolean;
content?: iFile[];
}
export class SevenZipManager {
sevenZip?: SevenZipModule;
consoleOutputBuffer: string[] = [];
constructor() {
this.init();
}
async init() {
this.sevenZip = await SevenZip({
locateFile: () => SevenZipWasm,
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 = /[\s+|(\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) => {
const aPathArrayLength = (a.path.match(/\//g) || []).length;
const bPathArrayLength = (b.path.match(/\//g) || []).length;
if (aPathArrayLength > bPathArrayLength) return -1;
if (aPathArrayLength < bPathArrayLength) return 1;
return 0;
});
// sort files and folder inside each folder
const pathArrays: any = {};
for (let file of unorganizedFiles) {
// get parent folder file
const parentPath = file.path.substring(0, file.path.lastIndexOf("/"));
let parentFolderFile = pathArrays[parentPath] || unorganizedFiles.find(_file => _file.path === parentPath);
if (!parentFolderFile) {
unorganizedFiles.push({
name: parentPath.substring(parentPath.lastIndexOf('/') + 1),
path: parentPath,
isFolder: true,
content: [],
});
parentFolderFile = unorganizedFiles.find(_file => _file.path === parentPath);
};
// add file to parent folder content
if (!parentFolderFile.content) parentFolderFile.content = [];
parentFolderFile.content.push(file);
// cache split path array to avoid calling split() multiple times
const pathArray = pathArrays[file.path] || file.path.split("/");
pathArrays[file.path] = pathArray;
}
// remove folders from root
const files = unorganizedFiles.filter(file => (file.path.match(/\//g) || []).length == 1).sort((a:any, b:any) => {
// sort by folder and from a to z
if (a.isFolder && !b.isFolder) return -1;
if (!a.isFolder && b.isFolder) return 1;
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
});
return files as iFile[];
}
// 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) {
breakLoop = true;
return;
}
lastLength = this.consoleOutputBuffer.length;
setTimeout(resolve, 10)
});
}
return;
}
}
Comlink.expose(SevenZipManager);

6
package-lock.json generated
View File

@ -9,6 +9,7 @@
"dependencies": {
"@mdi/font": "^7.2.96",
"7z-wasm": "^1.0.2",
"comlink": "^4.4.1",
"moveable": "^0.47.7",
"sass": "^1.62.1",
"selecto": "^1.22.3",
@ -2787,6 +2788,11 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true
},
"node_modules/comlink": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz",
"integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q=="
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",

View File

@ -19,6 +19,7 @@
"dependencies": {
"@mdi/font": "^7.2.96",
"7z-wasm": "^1.0.2",
"comlink": "^4.4.1",
"moveable": "^0.47.7",
"sass": "^1.62.1",
"selecto": "^1.22.3",

Binary file not shown.