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

View File

View File

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

View File

@ -1,134 +1,30 @@
// class to load 7zip wasm module // class to load 7zip wasm module
// and extract files from archive // 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 { export class FilesManager {
sevenZip?: SevenZipModule;
consoleOutputBuffer: string[] = []; consoleOutputBuffer: string[] = [];
path: Ref<string> = useSelectedPath(); path: Ref<string> = useSelectedPath();
remoteSevenZipManager?: Comlink.Remote<SevenZipManager>;
constructor(private filesList: Ref<any[]>) { constructor(private filesList: Ref<iFile[]>) {
this.init(); this.init();
} }
async init() { async init() {
this.sevenZip = await SevenZip({ this.remoteSevenZipManager = await new (Comlink.wrap(new SevenZipWorker()) as any);
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) { async loadArchive(file: File) {
if (!this.sevenZip) return; if (!this.remoteSevenZipManager) return;
this.filesList.value = await this.remoteSevenZipManager.loadArchive(file) || [];
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;
} }
// wait until console output is empty getFile(path: string, innerList: iFile[] | undefined = undefined): any {
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 {
if (path == "/") { if (path == "/") {
return { return {
content: this.filesList.value.sort((a:any, b:any) => { 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": { "dependencies": {
"@mdi/font": "^7.2.96", "@mdi/font": "^7.2.96",
"7z-wasm": "^1.0.2", "7z-wasm": "^1.0.2",
"comlink": "^4.4.1",
"moveable": "^0.47.7", "moveable": "^0.47.7",
"sass": "^1.62.1", "sass": "^1.62.1",
"selecto": "^1.22.3", "selecto": "^1.22.3",
@ -2787,6 +2788,11 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true "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": { "node_modules/commander": {
"version": "2.20.3", "version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",

View File

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

Binary file not shown.