본문 바로가기
#컴퓨터 과학 [Computer Science]/운영체제 (Operating System)

[OS - 🍎 macOS] Minizip 오픈소스를 활용 한 압축 (Compression) 기능 구현

by cy_mos 2022. 2. 8.
반응형
카테고리 게시글 작성 날짜 게시글 최근 수정 날짜 작성자
Operating System 2022.02.08. 22:30 2022.02.14. 20:57 Dev.Yang

 

아래의 첨부 파일은 Minizip OpenSource Library 입니다.

Minizip.zip
0.10MB

 

아래의 첨부 파일은 전체적인 프로젝트 파일입니다.

macMiniZip.zip
1.21MB

 

압축 (Compression) 속성은 NoCompression (압축을 하지 않음), BestSpeed (압축률 가장 낮지만 시간 빠름), DefaultCompression (기본적인 압축률)BestCompression (압축률이 가장 높지만 시간 느림)이 있습니다.

 

구현 된 소스코드 (Source Code)는 아래의 예시처럼 사용할 수 있습니다.

let target = URL(fileURLWithPath: "")
let zipFilePath = URL(fileURLWithPath: "")

macMiniZip.shared.compressZip(paths: [target],
                              zipFilePath: zipFilePath,
                              password: "password",
                              compression: .DefaultCompression) { progress, isEnd in }

🛠 macOS MiniZip with Swift Source Code

더보기
/*
 * Copyright (c) 2022 ChangYeop-Yang All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

import Minizip
import Foundation

/**
    - Date: `2022.02.08. 22:38:24`
    - Version: `[Lasted] 1.0.0.0`
 
    - NOTE: [Zip](https://github.com/marmelroy/Zip) 오픈소스 기반으로 구현되었습니다.
    - Authors: `ChangYeop-Yang`
    - Copyright: `Copyright (c) 2022 양창엽. All rights reserved.`
*/
public class macMiniZip: NSObject {
    
    // MARK: - Enum
    public enum ZipCompression: Int {
        case NoCompression
        case BestSpeed
        case DefaultCompression
        case BestCompression

        // MARK: Enum Properties
        internal var minizipCompression: Int32 {
            switch self {
            case .NoCompression:
                return Z_NO_COMPRESSION
            case .BestSpeed:
                return Z_BEST_SPEED
            case .DefaultCompression:
                return Z_DEFAULT_COMPRESSION
            case .BestCompression:
                return Z_BEST_COMPRESSION
            }
        }
    }
    
    // MARK: - Strucut
    fileprivate struct ProcessedFilePath {
        fileprivate let filePathURL: URL
        fileprivate let fileName: String?
        
        // MARK: Strucut Method
        fileprivate func filePath() -> String { return filePathURL.path }
    }
    
    // MARK: - Object Properteis
    internal static let shared = macMiniZip()
    private let chunkSize: Int = 16384
    private let includeRootDirectory: Bool = true
    
    // MARK: - Initalize
    private override init() { super.init() }
}

// MARK: - Private Extension Zip
private extension macMiniZip {
    
    func getAttributesOfItem(filePath: String) -> [FileAttributeKey : Any]? {
        
        do {
            return try FileManager.default.attributesOfItem(atPath: filePath)
        } catch let error as NSError {
            assert(false, error.localizedDescription)
            return nil
        }
    }

    func getFileSize(filePath: String) -> Double {
        
        guard let attribute = getAttributesOfItem(filePath: filePath) else { return Double.zero }
        
        guard let fileSize = attribute[FileAttributeKey.size] as? Double else { return Double.zero }
        
        return fileSize
    }

    func createZipFileInfo(filePath: String) -> zip_fileinfo? {
        
        let defaultDate = tm_zip(tm_sec: 0, tm_min: 0, tm_hour: 0, tm_mday: 0, tm_mon: 0, tm_year: 0)
        var zipInfo = zip_fileinfo(tmz_date: defaultDate, dosDate: 0, internal_fa: 0, external_fa: 0)
        
        guard let attribute = getAttributesOfItem(filePath: filePath) else { return nil }
        
        guard let fileDate = attribute[FileAttributeKey.modificationDate] as? Date else { return nil }
        
        let components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: fileDate)
        zipInfo.tmz_date.tm_sec = UInt32(components.second ?? Int.zero)
        zipInfo.tmz_date.tm_min = UInt32(components.minute ?? Int.zero)
        zipInfo.tmz_date.tm_hour = UInt32(components.hour ?? Int.zero)
        zipInfo.tmz_date.tm_mday = UInt32(components.day ?? Int.zero)
        zipInfo.tmz_date.tm_mon = UInt32(components.month ?? Int.zero)
        zipInfo.tmz_date.tm_year = UInt32(components.year ?? Int.zero)
        
        return zipInfo
    }
    
    func processZipPaths(_ paths: [URL]) -> [ProcessedFilePath]{
        
        var processedFilePaths = [ProcessedFilePath]()
        
        for path in paths {
            let filePath = path.path
            var isDirectory: ObjCBool = false
            
            _ = FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory)
            
            if !isDirectory.boolValue {
                let processedPath = ProcessedFilePath(filePathURL: path, fileName: path.lastPathComponent)
                processedFilePaths.append(processedPath)
            }
            else {
                let directoryContents = expandDirectoryFilePath(path)
                processedFilePaths.append(contentsOf: directoryContents)
            }
        }
        
        return processedFilePaths
    }
    
    func expandDirectoryFilePath(_ directory: URL) -> [ProcessedFilePath] {
        
        var processedFilePaths = [ProcessedFilePath]()
        let directoryPath = directory.path
        
        if let enumerator = FileManager.default.enumerator(atPath: directoryPath) {
            while let filePathComponent = enumerator.nextObject() as? String {
                let path = directory.appendingPathComponent(filePathComponent)
                let filePath = path.path

                var isDirectory: ObjCBool = false
                _ = FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory)
                if !isDirectory.boolValue {
                    var fileName = filePathComponent
                    if includeRootDirectory {
                        let directoryName = directory.lastPathComponent
                        fileName = (directoryName as NSString).appendingPathComponent(filePathComponent)
                    }
                    let processedPath = ProcessedFilePath(filePathURL: path, fileName: fileName)
                    processedFilePaths.append(processedPath)
                }
            }
        }
        return processedFilePaths
    }
}

// MARK: - Puiblic Extension Zip
public extension macMiniZip {
    
    func compressZip(paths: [URL], zipFilePath: URL, password: String,
                     compression: ZipCompression, progress: (Double, Bool) -> Swift.Void) {
        
        // Check whether a zip file exists at path.
        let destinationPath = zipFilePath.path
                
        // Process zip paths
        let processedPaths = self.processZipPaths(paths)
        
        // Progress handler set up
        var totalSize = Double.zero
        var currentPosition = Double.zero
        
        // Get totalSize for progress handler
        processedPaths.forEach { [unowned self] path in
            let filePath = path.filePath()
            totalSize = totalSize + self.getFileSize(filePath: filePath)
        }
        
        let progressTracker = Progress(totalUnitCount: Int64(totalSize))
        progressTracker.isCancellable = false
        progressTracker.isPausable = false
        progressTracker.kind = ProgressKind.file
        
        let zip = zipOpen(destinationPath, APPEND_STATUS_CREATE)
        for path in processedPaths {
            
            let filePath = path.filePath()
            var isDirectory: ObjCBool = false
            
            // 압축하고자 하는 대상이 파일이 존재하지 않는 경우에는 압축 작업을 수행하지 않습니다.
            guard FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory) else { continue }
            
            // 압축하고자 하는 대상이 파일이 아닌 폴더인 경우에는 작업을 수행하지 않습니다.
            if !isDirectory.boolValue {
                // 압축하고자 하는 대상에 대한 파일 내용을 읽어들이기 위해서 파일을 열지 못하는 경우에는 압축 작업을 수행하지 않습니다.
                guard let input = fopen(filePath, "r") else { continue }
                
                // 압축하고자 하는 대상에 대한 파일 이름을 가져오지 못하는 경우에는 압축 작업을 수행하지 않습니다.
                guard let fileName = path.fileName else {
                    fclose(input)
                    continue
                }
                
                // 압축하고자 하는 대상에 대한 파일 정보를 가져오지 못하는 경우에는 압축 작업을 수행하지 않습니다.
                guard var zipInfo = createZipFileInfo(filePath: filePath) else {
                    fclose(input)
                    continue
                }
                
                currentPosition += getFileSize(filePath: filePath)
                
                zipOpenNewFileInZip3(zip, fileName, &zipInfo, nil, 0, nil, 0, nil,
                                     Z_DEFLATED, compression.minizipCompression, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
                                     password, 0)
                
                let buffer = malloc(self.chunkSize)
                while feof(input) == Int32.zero {
                    let length = fread(buffer, 1, chunkSize, input)
                    zipWriteInFileInZip(zip, buffer, UInt32(length))
                }
                
                // 현재 압축 작업 중인 파일 크기와 전체 압축 파일 크기를 나누어 작업 중인 크기를 전달합니다.
                progress(currentPosition/totalSize, false)
                progressTracker.completedUnitCount = Int64(currentPosition)
                                
                zipCloseFileInZip(zip)
                
                free(buffer)
                fclose(input)
            }
        }
        
        zipClose(zip, nil)
        
        // Completed. Update progress handler.
        progress(Double.nan, true)
        progressTracker.completedUnitCount = Int64(totalSize)
    }
}

🚀 REFERENCE

반응형

댓글