29f35c4a创建于 2025年1月27日历史提交
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved.
 */
package zip4cj.tasks

import std.fs.*

public class MergeSplitZipFileTask <: AsyncZipTask<MergeSplitZipFileTaskParameters> {
    private let zipModel: ZipModel
    private let rawIO = RawIO()

    public MergeSplitZipFileTask(zipModel: ZipModel, asyncTaskParameters: AsyncTaskParameters) {
        super(asyncTaskParameters)
        this.zipModel = zipModel
    }

    protected func executeTask(taskParameters: MergeSplitZipFileTaskParameters, progressMonitor: ProgressMonitor): Unit {
        if (!zipModel.isSplitArchive()) {
            var e = ZipException("archive not a split zip file")
            progressMonitor.endProgressMonitor(e)
            throw e
        }
        if (exists(taskParameters.outputZipFile)) {
            remove(taskParameters.outputZipFile, recursive: true)
        }
        try (outputStream = File(taskParameters.outputZipFile, OpenMode.ReadWrite)) {
            var totalBytesWritten = 0
            var totalNumberOfSplitFiles = zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk()
            if (totalNumberOfSplitFiles <= 0) {
                throw ZipException("zip archive not a split zip file")
            }

            var signatureOverheadSplit = 0
            for (i in 0..totalNumberOfSplitFiles) {
                try (randomAccessFile = createSplitZipFileStream(zipModel, Int64(i))) {
                    var start = 0
                    var end = randomAccessFile.length

                    if (i == 0) {
                        if (Int64(rawIO.readIntLittleEndian(randomAccessFile)) == HeaderSignature.SPLIT_ZIP.getValue()) {
                            signatureOverheadSplit = 4
                            start = 4
                        } else {
                            randomAccessFile.seek(0)
                        }
                    }

                    if (i == totalNumberOfSplitFiles) {
                        end = zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory()
                    }

                    FileUtils.copyFile(randomAccessFile, outputStream, start, end, progressMonitor, taskParameters.zip4cjConfig.getBufferSize())
                    totalBytesWritten += (end - start)
                    if (i == 0) {
                        updateFileHeaderOffsetsForIndex(zipModel.getCentralDirectory().getOrThrow().getFileHeaders(), 0, Int64(i),
                            signatureOverheadSplit)
                    } else {
                        updateFileHeaderOffsetsForIndex(zipModel.getCentralDirectory().getOrThrow().getFileHeaders(),
                            totalBytesWritten, Int64(i), signatureOverheadSplit)
                    }
                    verifyIfTaskIsCancelled()
                }
            }
            updateHeadersForMergeSplitFileAction(zipModel, totalBytesWritten, outputStream)
            progressMonitor.endProgressMonitor()
        } catch (e: Exception) {
            throw ZipException(e.message)
        }
    }

    protected func calculateTotalWork(taskParameters: MergeSplitZipFileTaskParameters): Int64 {
        taskParameters
        if (!zipModel.isSplitArchive()) {
            return 0
        }

        var totalSize = 0
        for (i in 0..zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk()) {
            totalSize += FileInfo(getNextSplitZipFile(zipModel, Int64(i))).size
        }
        return totalSize
    }

    private func updateFileHeaderOffsetsForIndex(
        fileHeaders: ArrayList<FileHeader>,
        offsetToAdd: Int64,
        index: Int64,
        signatureOverheadSplit: Int64
    ) {
        for (i in 0..fileHeaders.size) {
            let fileHeader = fileHeaders[i]
            if (fileHeader.getDiskNumberStart() == index) {
                fileHeader.setOffsetLocalHeader(fileHeader.getOffsetLocalHeader() + offsetToAdd - signatureOverheadSplit
                )
                fileHeader.setDiskNumberStart(0)
            }
        }
    }

    private func getNextSplitZipFile(zipModel: ZipModel, partNumber: Int64): Path {
        if (partNumber == Int64(zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk())) {
            return zipModel.getZipFile()
        }

        var splitZipExtension = ".z0"
        if (partNumber >= 9) {
            splitZipExtension = ".z"
        }
        var rootZipFile = zipModel.getZipFile().toString()
        var nextSplitZipFileName = String(
            zipModel.getZipFile().toString().toRuneArray()[0..rootZipFile.lastIndexOf(".").getOrThrow()]) +
            splitZipExtension + "${partNumber + 1}"
        return Path(nextSplitZipFileName)
    }

    private func createSplitZipFileStream(zipModel: ZipModel, partNumber: Int64): RandomAccessFile {
        var splitFile = getNextSplitZipFile(zipModel, partNumber)
        return RandomAccessFile(splitFile, OpenMode.Read)
    }

    private func updateHeadersForMergeSplitFileAction(
        zipModel: ZipModel,
        totalBytesWritten: Int64,
        outputStream: OutputStream
    ) {
        var newZipModel = zipModel.clone()
        newZipModel.getEndOfCentralDirectoryRecord().setOffsetOfStartOfCentralDirectory(totalBytesWritten)

        updateSplitZipModel(newZipModel, totalBytesWritten)

        var headerWriter: HeaderWriter = HeaderWriter()
        headerWriter.finalizeZipFileWithoutValidations(newZipModel, outputStream)
    }

    private func updateSplitZipModel(zipModel: ZipModel, totalFileSize: Int64) {
        zipModel.setSplitArchive(false)
        updateSplitEndCentralDirectory(zipModel)

        if (zipModel.isZip64Format()) {
            updateSplitZip64EndCentralDirLocator(zipModel, totalFileSize)
            updateSplitZip64EndCentralDirRec(zipModel, totalFileSize)
        }
    }

    private func updateSplitEndCentralDirectory(zipModel: ZipModel) {
        var numberOfFileHeaders: Int64 = zipModel.getCentralDirectory().getOrThrow().getFileHeaders().size
        var endOfCentralDirectoryRecord: EndOfCentralDirectoryRecord = zipModel.getEndOfCentralDirectoryRecord()
        endOfCentralDirectoryRecord.setNumberOfThisDisk(0)
        endOfCentralDirectoryRecord.setNumberOfThisDiskStartOfCentralDir(0)
        endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectory(numberOfFileHeaders)
        endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(Int32(numberOfFileHeaders))
    }

    private func updateSplitZip64EndCentralDirLocator(zipModel: ZipModel, totalFileSize: Int64) {
        var zip64EndOfCentralDirectoryLocator: Zip64EndOfCentralDirectoryLocator = zipModel.
            getZip64EndOfCentralDirectoryLocator().getOrThrow()
        zip64EndOfCentralDirectoryLocator.setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(0)
        zip64EndOfCentralDirectoryLocator.setOffsetZip64EndOfCentralDirectoryRecord(
            zip64EndOfCentralDirectoryLocator.getOffsetZip64EndOfCentralDirectoryRecord() + totalFileSize)
        zip64EndOfCentralDirectoryLocator.setTotalNumberOfDiscs(1)
    }

    private func updateSplitZip64EndCentralDirRec(zipModel: ZipModel, totalFileSize: Int64) {

        var zip64end: Zip64EndOfCentralDirectoryRecord = zipModel.
            getZip64EndOfCentralDirectoryRecord()
        zip64end.setNumberOfThisDisk(0)
        zip64end.setNumberOfThisDiskStartOfCentralDirectory(0)
        zip64end.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
            zipModel.getEndOfCentralDirectoryRecord().getTotalNumberOfEntriesInCentralDirectory())
        zip64end.setOffsetStartCentralDirectoryWRTStartDiskNumber(
            zip64end.getOffsetStartCentralDirectoryWRTStartDiskNumber() + totalFileSize)
    }

    protected func getTask(): ProgressMonitorTask {
        return ProgressMonitorTask.MERGE_ZIP_FILES
    }
}