Skip to content

skiptools/skip-zip

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SkipZip

SkipZip is a Skip framework that provides support for creating and extracting zip archives.

Setup

To include this framework in your project, add the following dependency to your Package.swift file:

let package = Package(
    name: "my-package",
    products: [
        .library(name: "MyProduct", targets: ["MyTarget"]),
    ],
    dependencies: [
        .package(url: "https://source.skip.dev/skip-zip.git", "0.0.0"..<"2.0.0"),
    ],
    targets: [
        .target(name: "MyTarget", dependencies: [
            .product(name: "SkipZip", package: "skip-zip")
        ])
    ]
)

Usage

Local Zip Archives

Use ZipReader and ZipWriter for working with zip files on disk:

import SkipZip

// Reading a zip file
if let reader = ZipReader(path: "/path/to/archive.zip") {
    try reader.first()
    while true {
        if let name = try reader.currentEntryName {
            print("Entry: \(name)")
        }
        if let data = try reader.currentEntryData {
            // Process entry data...
        }
        if !(try reader.next()) { break }
    }
    try reader.close()
}

// Writing a zip file
if let writer = ZipWriter(path: "/path/to/output.zip", append: false) {
    try writer.add(path: "hello.txt", data: "Hello, World!".data(using: .utf8)!, compression: 5)
    try writer.close()
}

Remote Zip Archives

RemoteZipReader allows reading individual entries from a remote zip archive over HTTP without downloading the entire file. It uses HTTP Range requests to fetch only the archive's central directory (metadata) and the specific entries you request.

This is useful for selectively extracting files from large remote archives where downloading the full archive would be impractical.

Requirements: The HTTP server must support Range requests (indicated by the Accept-Ranges: bytes header). If the server does not support range requests, the operation will fail with a descriptive error.

import SkipZip

// Open a remote zip archive (fetches only the metadata)
let reader = try await RemoteZipReader.open(url: URL(string: "https://example.com/archive.zip")!)

// Browse entries without downloading their data
for entry in reader.entries {
    print("\(entry.name): \(entry.uncompressedSize) bytes")
}

// Fetch and decompress a specific entry
let data = try await reader.data(forEntryNamed: "README.md")
print(String(data: data, encoding: .utf8)!)

Configuration

RemoteZipConfiguration controls retry behavior for transient network failures. Retryable conditions include network errors and HTTP status codes 429, 502, 503, and 504.

let config = RemoteZipConfiguration(
    maxRetries: 5,                 // Retry up to 5 times (default: 3)
    initialBackoffInterval: 1.0,   // Wait 1 second before first retry (default: 0.5)
    backoffMultiplier: 2.0,        // Double the wait time after each retry (default: 2.0)
    maxBackoffInterval: 60.0,      // Cap backoff at 60 seconds (default: 30.0)
    endOfFileReadSize: 131_072     // Read last 128KB to find EOCD (default: 65536)
)

let reader = try await RemoteZipReader.open(
    url: archiveURL,
    session: myURLSession,        // Optional: provide a custom URLSession
    configuration: config
)

Custom URLSession

You can pass a custom URLSession for authentication, caching, or proxy configuration:

let sessionConfig = URLSessionConfiguration.default
sessionConfig.httpAdditionalHeaders = ["Authorization": "Bearer token123"]
let session = URLSession(configuration: sessionConfig)

let reader = try await RemoteZipReader.open(url: archiveURL, session: session)

Error Handling

All errors are reported as RemoteZipError with descriptive messages:

do {
    let reader = try await RemoteZipReader.open(url: archiveURL)
    let data = try await reader.data(forEntryNamed: "file.txt")
} catch let error as RemoteZipError {
    switch error {
    case .serverDoesNotSupportRangeRequests:
        print("Server doesn't support Range requests")
    case .entryNotFound(let name):
        print("Entry '\(name)' not found")
    case .retryLimitExceeded(let count, let lastError):
        print("Failed after \(count) retries: \(lastError)")
    default:
        print(error.localizedDescription)
    }
}

Supported Compression Methods

RemoteZipReader supports the two most common zip compression methods:

  • STORE (method 0): No compression; data is returned as-is.
  • DEFLATE (method 8): Standard zip compression; data is decompressed automatically.

ZIP64 extensions are supported for archives and entries larger than 4 GB.

Building

This project is a Swift Package Manager module that uses the Skip plugin to build the package for both iOS and Android.

Testing

The module can be tested using the standard swift test command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment.

Parity testing can be performed with skip test, which will output a table of the test results for both platforms.

License

This software is licensed under the Mozilla Public License 2.0.

About

Zip support for Skip

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

Contributors

Languages