mirror of
https://github.com/xlmnxp/extractify.zip.git
synced 2024-11-23 17:13:12 +03:00
move wasm import to be handle by vite. move 7zip management to separate worker
This commit is contained in:
parent
7968a8c032
commit
16e1d5b701
5
app.vue
5
app.vue
@ -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;
|
||||||
}
|
}
|
||||||
|
0
components/media/video-player.vue
Normal file
0
components/media/video-player.vue
Normal 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>
|
||||||
|
@ -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) => {
|
||||||
|
140
composables/worker/7zip-manager.ts
Normal file
140
composables/worker/7zip-manager.ts
Normal 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
6
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
BIN
public/7zz.wasm
BIN
public/7zz.wasm
Binary file not shown.
Loading…
Reference in New Issue
Block a user