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

import std.fs.*

public class SplitOutputStream <: OutputStream & OutputStreamWithSplitZipSupport & Resource {
    private var raf: RandomAccessFile
    private var splitLength: Int64
    private var zipFile: Path
    private var currSplitFileCounter: Int32
    private var bytesWrittenForThisPart: Int64
    private var rawIO = RawIO()
    private var isClose = false

    public init(file: Path) {
        this(file, -1)
    }

    public init(file: Path, splitLength: Int64) {
        if (splitLength >= 0 && splitLength < InternalZipConstants.MIN_SPLIT_LENGTH) {
            throw ZipException(
                "split length less than minimum allowed split length of ${InternalZipConstants.MIN_SPLIT_LENGTH} Bytes")
        }
        this.raf = if (exists(file) && FileInfo(file).isRegular()) {
            RandomAccessFile(file, OpenMode.ReadWrite)
        } else {
            RandomAccessFile(file, OpenMode.ReadWrite)
        }
        this.splitLength = splitLength
        this.zipFile = file
        this.currSplitFileCounter = 0
        this.bytesWrittenForThisPart = 0
    }

    public func write(b: Array<Byte>) {
        let len = b.size
        if (len <= 0) {
            return
        }
        if (splitLength == -1) {
            raf.write(b)
            bytesWrittenForThisPart += len
            return
        }
        if (bytesWrittenForThisPart >= splitLength) {
            startNextSplitFile()
            raf.write(b)
            bytesWrittenForThisPart = len
        } else if (bytesWrittenForThisPart + len > splitLength) {
            if (isHeaderData(b)) {
                startNextSplitFile()
                raf.write(b)
                bytesWrittenForThisPart = len
            } else {
                raf.write(b[0..splitLength - bytesWrittenForThisPart])
                startNextSplitFile()
                raf.write(b[splitLength - bytesWrittenForThisPart..len])
                bytesWrittenForThisPart = len - (splitLength - bytesWrittenForThisPart)
            }
        } else {
            raf.write(b)
            bytesWrittenForThisPart += len
        }
    }

    private func startNextSplitFile() {
        var zipFileWithoutExt = zipFile.fileNameWithoutExtension
        var zipFileName = canonicalize(zipFile)
        var parentPath = zipFileName.parent
        var fileExtension =  if (currSplitFileCounter >= 9) {
            ".z${currSplitFileCounter + 1}"
        } else {
            ".z0${currSplitFileCounter + 1}"
        }

        var currSplitFile = parentPath.join(zipFileWithoutExt + fileExtension)
        raf.flush()
        raf.close()

        if (exists(currSplitFile)) {
            throw ZipException(
                "split file: ${currSplitFile} already exists in the current directory, cannot rename this file")
        }

        if (exists(currSplitFile)) {
            throw ZipException("cannot rename newly created split file")
        }
        rename(zipFileName, to: currSplitFile, overwrite: true)
        raf = RandomAccessFile(zipFile, OpenMode.ReadWrite)
        currSplitFileCounter++
    }

    private func isHeaderData(buff: Array<Byte>): Bool {
        var signature = Int64(rawIO.readIntLittleEndian(buff))
        let values = HeaderSignature.values()
        for (i in 0..values.size) {
            //Ignore split signature
            if (values[i] != HeaderSignature.SPLIT_ZIP && values[i].getValue() == signature) {
                return true
            }
        }
        return false
    }

    /**
     * Checks if the buffer size is sufficient for the current split file. If not
     * a new split file will be started.
     *
     * @param bufferSize
     * @return true if a new split file was started else false
     * @throws ZipException
     */
    public func checkBufferSizeAndStartNextSplitFile(bufferSize: Int64): Bool {
        if (bufferSize < 0) {
            throw ZipException("negative buffersize for checkBufferSizeAndStartNextSplitFile")
        }

        if (!isBufferSizeFitForCurrSplitFile(bufferSize)) {
            try {
                startNextSplitFile()
                bytesWrittenForThisPart = 0
                return true
            } catch (e: Exception) {
                throw ZipException(e.message)
            }
        }

        return false
    }

    /**
     * Checks if the given buffer size will be fit in the current split file.
     * If this output stream is a non-split file, then this method always returns true
     *
     * @param bufferSize
     * @return true if the buffer size is fit in the current split file or else false.
     */
    private func isBufferSizeFitForCurrSplitFile(bufferSize: Int64): Bool {
        if (splitLength >= InternalZipConstants.MIN_SPLIT_LENGTH) {
            return (bytesWrittenForThisPart + bufferSize <= splitLength)
        } else {
            //Non split zip -- return true
            return true
        }
    }

    public func seek(pos: Int64): Unit {
        raf.seek(pos)
    }

    public func skipBytes(n: Int64): Int64 {
        var temp = Array<Byte>(1, repeat: 0)
        for (i in 0..n) {
            let read = raf.read(temp)
            if (read <= 0) {
                return i
            }
        }
        return n
    }

    public func isClosed() {
        return this.isClose
    }

    public func close() {
        try {
            raf.close()
        } finally {
            this.isClose = true
        }
    }

    public func getFilePointer(): Int64 {
        return raf.getFilePointer()
    }

    public func isSplitZipFile(): Bool {
        return splitLength != -1
    }

    public func getSplitLength(): Int64 {
        return splitLength
    }

    public func getCurrentSplitFileCounter(): Int32 {
        return currSplitFileCounter
    }
}