mirror of
https://github.com/xlmnxp/extractify.zip.git
synced 2024-11-23 17:13:12 +03:00
replace libarchive.js with 7z-wasm and fix utf-8 issue
This commit is contained in:
parent
5f4329d8f9
commit
e644375b75
56
app.vue
56
app.vue
@ -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>
|
||||
|
131
composables/files-manager.ts
Normal file
131
composables/files-manager.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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
29
package-lock.json
generated
@ -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",
|
||||
|
@ -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
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
Loading…
Reference in New Issue
Block a user