tarballjs

实现前端打包tar压缩包

Questo script non dovrebbe essere installato direttamente. È una libreria per altri script da includere con la chiave // @require https://update.greasyfork.org/scripts/539732/1609156/tarballjs.js

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

let tarball = {};

if (typeof module === "object" && typeof module.exports === "object") {
    // CommonJS
    module.exports = tarball;
} else if (typeof this === "object") {
    // Browser
    // use this instead of window, since window might not exist and throw and error
    this.tarball = tarball;
}

tarball.TarReader = class {
    constructor() {
        this.fileInfo = [];
    }

    readFile(file) {
        return new Promise((resolve, reject) => {
            let reader = new FileReader();
            reader.onload = (event) => {
                this.buffer = event.target.result;
                this.fileInfo = [];
                this._readFileInfo();
                resolve(this.fileInfo);
            };
            reader.readAsArrayBuffer(file);
        });
    }

    readArrayBuffer(arrayBuffer) {
        this.buffer = arrayBuffer;
        this.fileInfo = [];
        this._readFileInfo();
        return this.fileInfo;
    }

    _readFileInfo() {
        this.fileInfo = [];
        let offset = 0;
        let file_size = 0;       
        let file_name = "";
        let file_type = null;
        while(offset < this.buffer.byteLength - 512) {
            file_name = this._readFileName(offset); // file name
            if(file_name.length == 0) {
                break;
            }
            file_type = this._readFileType(offset);
            file_size = this._readFileSize(offset);

            this.fileInfo.push({
                "name": file_name,
                "type": file_type,
                "size": file_size,
                "header_offset": offset
            });

            offset += (512 + 512*Math.trunc(file_size/512));
            if(file_size % 512) {
                offset += 512;
            }
        }
    }

    getFileInfo() {
        return this.fileInfo;
    }

    _readString(str_offset, size) {
        let strView = new Uint8Array(this.buffer, str_offset, size);
        let i = strView.indexOf(0);
        let td = new TextDecoder();
        return td.decode(strView.slice(0, i));
    }

    _readFileName(header_offset) {
        let name = this._readString(header_offset, 100);
        return name;
    }

    _readFileType(header_offset) {
        // offset: 156
        let typeView = new Uint8Array(this.buffer, header_offset+156, 1);
        let typeStr = String.fromCharCode(typeView[0]);
        if(typeStr == "0") {
            return "file";
        } else if(typeStr == "5") {
            return "directory";
        } else {
            return typeStr;
        }
    }

    _readFileSize(header_offset) {
        // offset: 124
        let szView = new Uint8Array(this.buffer, header_offset+124, 12);
        let szStr = "";
        for(let i = 0; i < 11; i++) {
            szStr += String.fromCharCode(szView[i]);
        }
        return parseInt(szStr,8);
    }

    _readFileBlob(file_offset, size, mimetype) {
        let view = new Uint8Array(this.buffer, file_offset, size);
        let blob = new Blob([view], {"type": mimetype});
        return blob;
    }

    _readFileBinary(file_offset, size) {
        let view = new Uint8Array(this.buffer, file_offset, size);
        return view;
    }

    _readTextFile(file_offset, size) {
        let view = new Uint8Array(this.buffer, file_offset, size);
        let td = new TextDecoder();
        return td.decode(view);
    }

    getTextFile(file_name) {
        let info = this.fileInfo.find(info => info.name == file_name);
        if (info) {
            return this._readTextFile(info.header_offset+512, info.size); 
        }
    }

    getFileBlob(file_name, mimetype) {
        let info = this.fileInfo.find(info => info.name == file_name);
        if (info) {
            return this._readFileBlob(info.header_offset+512, info.size, mimetype); 
        }
    }

    getFileBinary(file_name) {
        let info = this.fileInfo.find(info => info.name == file_name);
        if (info) {
            return this._readFileBinary(info.header_offset+512, info.size); 
        }
    }
};

tarball.TarWriter = class {
    constructor() {
        this.fileData = [];
    }

    addTextFile(name, text, opts) {
        let te = new TextEncoder();
        let arr = te.encode(text);
        this.fileData.push({
            name: name,
            array: arr,
            type: "file",
            size: arr.length,
            dataType: "array",
            opts: opts
        });
    }

    addFileArrayBuffer(name, arrayBuffer, opts) {
        let arr = new Uint8Array(arrayBuffer);
        this.fileData.push({
            name: name,
            array: arr,
            type: "file",
            size: arr.length,
            dataType: "array",
            opts: opts
        });
    }

    addFile(name, file, opts) {
        this.fileData.push({
            name: name,
            file: file,
            size: file.size,
            type: "file",
            dataType: "file",
            opts: opts
        });
    }

    addFolder(name, opts) {
        this.fileData.push({
            name: name,
            type: "directory",
            size: 0,
            dataType: "none",
            opts: opts
        });
    }

    _createBuffer() {
        let tarDataSize = 0;
        for(let i = 0; i < this.fileData.length; i++) {                        
            let size = this.fileData[i].size;
            tarDataSize += 512 + 512*Math.trunc(size/512);
            if(size % 512) {
                tarDataSize += 512;
            }
        }
        let bufSize = 10240*Math.trunc(tarDataSize/10240);
        if(tarDataSize % 10240) {
            bufSize += 10240;
        }
        this.buffer = new ArrayBuffer(bufSize); 
    }

    async download(filename) {
        let blob = await this.writeBlob();
        let $downloadElem = document.createElement('a');
        $downloadElem.href = URL.createObjectURL(blob);
        $downloadElem.download = filename;
        $downloadElem.style.display = "none";
        document.body.appendChild($downloadElem);
        $downloadElem.click();
        document.body.removeChild($downloadElem);
    }

    async writeBlob(onUpdate) {
        return new Blob([await this.write(onUpdate)], {"type":"application/x-tar"});
    }

    write(onUpdate) {
        return new Promise((resolve,reject) => {
            this._createBuffer();
            let offset = 0;
            let filesAdded = 0;
            let onFileDataAdded = () => {
                filesAdded++;
                if (onUpdate) {
                    onUpdate(filesAdded / this.fileData.length * 100);
                }
                if(filesAdded === this.fileData.length) {
                    let arr = new Uint8Array(this.buffer);
                    resolve(arr);
                }
            };
            for(let fileIdx = 0; fileIdx < this.fileData.length; fileIdx++) {
                let fdata = this.fileData[fileIdx];
                // write header
                this._writeFileName(fdata.name, offset);
                this._writeFileType(fdata.type, offset);
                this._writeFileSize(fdata.size, offset);
                this._fillHeader(offset, fdata.opts, fdata.type);
                this._writeChecksum(offset);

                // write file data
                let destArray = new Uint8Array(this.buffer, offset+512, fdata.size);
                if(fdata.dataType === "array") {
                    for(let byteIdx = 0; byteIdx < fdata.size; byteIdx++) {
                        destArray[byteIdx] = fdata.array[byteIdx];
                    }
                    onFileDataAdded();
                } else if(fdata.dataType === "file") {
                    let reader = new FileReader();
                    
                    reader.onload = (function(outArray) {
                        let dArray = outArray;
                        return function(event) {
                            let sbuf = event.target.result;
                            let sarr = new Uint8Array(sbuf);
                            for(let bIdx = 0; bIdx < sarr.length; bIdx++) {
                                dArray[bIdx] = sarr[bIdx];
                            }
                            onFileDataAdded();
                        };
                    })(destArray);
                    reader.readAsArrayBuffer(fdata.file);
                } else if(fdata.type === "directory") {
                    onFileDataAdded();
                }

                offset += (512 + 512*Math.trunc(fdata.size/512));
                if(fdata.size % 512) {
                    offset += 512;
                }
            }
        });
    }

    _writeString(str, offset, size) {
        let strView = new Uint8Array(this.buffer, offset, size);
        let te = new TextEncoder();
        if (te.encodeInto) {
            // let the browser write directly into the buffer
            let written = te.encodeInto(str, strView).written;
            for (let i = written; i < size; i++) {
                strView[i] = 0;
            }
        } else {
            // browser can't write directly into the buffer, do it manually
            let arr = te.encode(str);
            for (let i = 0; i < size; i++) {
                strView[i] = i < arr.length ? arr[i] : 0;
            }
        }
    }

    _writeFileName(name, header_offset) {
        // offset: 0
        this._writeString(name, header_offset, 100);
    }

    _writeFileType(typeStr, header_offset) {
        // offset: 156
        let typeChar = "0";
        if(typeStr === "file") {
            typeChar = "0";
        } else if(typeStr === "directory") {
            typeChar = "5";
        }
        let typeView = new Uint8Array(this.buffer, header_offset + 156, 1);
        typeView[0] = typeChar.charCodeAt(0); 
    }

    _writeFileSize(size, header_offset) {
        // offset: 124
        let sz = size.toString(8);
        sz = this._leftPad(sz, 11);
        this._writeString(sz, header_offset+124, 12);
    }

    _leftPad(number, targetLength) {
        let output = number + '';
        while (output.length < targetLength) {
            output = '0' + output;
        }
        return output;
    }

    _writeFileMode(mode, header_offset) {
        // offset: 100
        this._writeString(this._leftPad(mode,7), header_offset+100, 8);         
    }

    _writeFileUid(uid, header_offset) {
        // offset: 108
        this._writeString(this._leftPad(uid,7), header_offset+108, 8);
    }
    
    _writeFileGid(gid, header_offset) {
        // offset: 116
        this._writeString(this._leftPad(gid,7), header_offset+116, 8);
    }

    _writeFileMtime(mtime, header_offset) {
        // offset: 136
        this._writeString(this._leftPad(mtime,11), header_offset+136, 12);
    }

    _writeFileUser(user, header_offset) {
        // offset: 265
        this._writeString(user, header_offset+265, 32);
    }
    
    _writeFileGroup(group, header_offset) {
        // offset: 297
        this._writeString(group, header_offset+297, 32);
    }

    _writeChecksum(header_offset) {
        // offset: 148
        this._writeString("        ", header_offset+148, 8); // first fill with spaces

        // add up header bytes
        let header = new Uint8Array(this.buffer, header_offset, 512);
        let chksum = 0;
        for(let i = 0; i < 512; i++) {
            chksum += header[i];
        }
        this._writeString(chksum.toString(8), header_offset+148, 8);
    }

    _getOpt(opts, opname, defaultVal) {
        if(opts != null) {
            if(opts[opname] != null) {
                return opts[opname];
            }
        }
        return defaultVal;
    }
    
    _fillHeader(header_offset, opts, fileType) {
        let uid = this._getOpt(opts, "uid", 1000);
        let gid = this._getOpt(opts, "gid", 1000);
        let mode = this._getOpt(opts, "mode", fileType === "file" ? "664" : "775");
        let mtime = this._getOpt(opts, "mtime", Date.now());
        let user = this._getOpt(opts, "user", "tarballjs");
        let group = this._getOpt(opts, "group", "tarballjs");

        this._writeFileMode(mode, header_offset);
        this._writeFileUid(uid.toString(8), header_offset);
        this._writeFileGid(gid.toString(8), header_offset);
        this._writeFileMtime(Math.trunc(mtime/1000).toString(8), header_offset);

        this._writeString("ustar", header_offset+257,6); // magic string
        this._writeString("00", header_offset+263,2); // magic version

        this._writeFileUser(user, header_offset);
        this._writeFileGroup(group, header_offset);
    }
};