Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tuskit-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
strategy:
matrix:
os: ["macos-latest"]
swift: ["5.10"]
swift: ["6.2"]
runs-on: ${{ matrix.os }}
steps:
- name: Extract Branch Name
Expand Down
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.2.0
53 changes: 53 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Agent Guide

## Purpose
Agents act as senior Swift collaborators. Keep responses concise,
clarify uncertainty before coding, and align suggestions with the rules linked below.

## Rule Index
- @ai-rules/rule-loading.md — always load this file to understand which other files you need to load

## Repository Overview
- Deep product and architecture context: @ai-docs/
[Fill in by LLM assistant]

## Commands
[Fill in by LLM assistant]
- `swiftformat . --config .swiftformat`: Apply formatting (run before committing)
- `swiftlint --config .swiftlint.yml`: Lint Swift sources and address custom rules
- `pre-commit run --all-files`: Verify hooks prior to pushing

## Code Style
- Swift files use 4-space indentation, ≤180-character width, and always-trailing commas
- Inject dependencies (Point-Free Dependencies) instead of singletons; make impossible states unrepresentable
- Prefer shorthand optional binding syntax (e.g. `guard let handler`) instead of repeating the binding name

## Architecture & Patterns
[Fill in by LLM assistant]
- Shared UI lives in `SharedViews`; shared models and utilities in `Shared*` modules
- Use dependency injection for all services and environment values to keep code testable

## Key Integration Points
**Database**: [Fill in by LLM assistant]
**Services**: [Fill in by LLM assistant]
**Testing**: Swift Testing with `withDependencies` for deterministic test doubles
**UI**: [Fill in by LLM assistant]

## Workflow
- Ask for clarification when requirements are ambiguous; surface 2–3 options when trade-offs matter
- Update documentation and related rules when introducing new patterns or services
- Use commits in `<type>(<scope>): summary` format; squash fixups locally before sharing

## Testing
[Fill in by LLM assistant]

## Environment
[Fill in by LLM assistant]
- Requires SwiftUI, Combine, GRDB, and Point-Free Composable Architecture libraries
- Validate formatting and linting (swiftformat/swiftlint) before final review

## Special Notes
- Do not mutate files outside the workspace root without explicit approval
- Avoid destructive git operations unless the user requests them directly
- When unsure or need to make a significant decision ASK the user for guidance
- Commit only things you modified yourself, someone else might be modyfing other files.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,29 @@ To upload multiple files at once, you can use the `uploadFiles(filePaths:)` meth

To specify a custom upload URL (e.g. for TransloadIt) or custom headers to be added to a file upload, please refer to the `uploadURL` and `customHeaders` properties in the methods related to uploading. Such as: `upload`, `uploadFileAt`, `uploadFiles` or `uploadMultiple(dataFiles:)`.

### Custom header generation

Sometimes headers need to be signed or refreshed right before a request is sent.
`TUSClient` exposes a header generation hook so you can mutate previously supplied custom headers without rebuilding the upload. Pass the optional `generateHeaders` closure to the initializer and TUSKit will call it before every `POST`, `PATCH`, or `HEAD` request.

```swift
let client = try TUSClient(
server: serverURL,
sessionIdentifier: "UploadSession",
sessionConfiguration: configuration,
storageDirectory: storageDirectory,
supportedExtensions: [.creation]
) { uploadID, headers, onHeadersGenerated in
tokenProvider.fetchToken(for: uploadID) { token in
var mutatedHeaders = headers
mutatedHeaders["Authorization"] = "Bearer \(token)"
onHeadersGenerated(mutatedHeaders)
}
}
```

TUSKit will reuse whatever headers you return for automatic retries or when resuming an upload, ensuring the same values are applied consistently. New headers can be introduced as needed, while core TUS headers such as `Upload-Offset` and `Content-Length` remain under the SDK’s control.

## Measuring upload progress

To know how many files have yet to be uploaded, please refer to the `remainingUploads` property.
Expand Down
17 changes: 8 additions & 9 deletions Sources/TUSKit/TUSAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ final class TUSAPI {
/// - metaData: The file metadata.
/// - completion: Completes with a result that gives a URL to upload to.
@discardableResult
func create(metaData: UploadMetadata, completion: @escaping (Result<URL, TUSAPIError>) -> Void) -> URLSessionDataTask {
let request = makeCreateRequest(metaData: metaData)
func create(metaData: UploadMetadata, customHeaders: [String: String], completion: @escaping (Result<URL, TUSAPIError>) -> Void) -> URLSessionDataTask {
let request = makeCreateRequest(metaData: metaData, customHeaders: customHeaders)
let identifier = UUID().uuidString
let task = session.dataTask(with: request)
task.taskDescription = identifier
Expand Down Expand Up @@ -205,7 +205,7 @@ final class TUSAPI {
return task
}

func makeCreateRequest(metaData: UploadMetadata) -> URLRequest {
func makeCreateRequest(metaData: UploadMetadata, customHeaders: [String: String]) -> URLRequest {
func makeUploadMetaHeader() -> [String: String] {
var metaDataDict: [String: String] = [:]

Expand Down Expand Up @@ -247,7 +247,7 @@ final class TUSAPI {
}

/// Attach all headers from customHeader property
let headers = defaultHeaders.merging(metaData.customHeaders ?? [:]) { _, new in new }
let headers = defaultHeaders.merging(customHeaders) { _, new in new }

return makeRequest(url: metaData.uploadURL, method: .post, headers: headers)
}
Expand All @@ -259,7 +259,7 @@ final class TUSAPI {
/// - location: The location of where to upload to.
/// - completion: Completionhandler for when the upload is finished.
@discardableResult
func upload(data: Data, range: Range<Int>?, location: URL, metaData: UploadMetadata, completion: @escaping (Result<Int, TUSAPIError>) -> Void) -> URLSessionUploadTask {
func upload(data: Data, range: Range<Int>?, location: URL, metaData: UploadMetadata, customHeaders: [String: String], completion: @escaping (Result<Int, TUSAPIError>) -> Void) -> URLSessionUploadTask {
let offset: Int
let length: Int
if let range = range {
Expand All @@ -278,7 +278,7 @@ final class TUSAPI {
]

/// Attach all headers from customHeader property
let headersWithCustom = headers.merging(metaData.customHeaders ?? [:]) { _, new in new }
let headersWithCustom = headers.merging(customHeaders) { _, new in new }

let request = makeRequest(url: location, method: .patch, headers: headersWithCustom)
let task = session.uploadTask(with: request, from: data)
Expand Down Expand Up @@ -310,7 +310,7 @@ final class TUSAPI {
return task
}

func upload(fromFile file: URL, offset: Int = 0, location: URL, metaData: UploadMetadata, completion: @escaping (Result<Int, TUSAPIError>) -> Void) -> URLSessionUploadTask {
func upload(fromFile file: URL, offset: Int = 0, location: URL, metaData: UploadMetadata, customHeaders: [String: String], completion: @escaping (Result<Int, TUSAPIError>) -> Void) -> URLSessionUploadTask {
let length: Int
if let fileAttributes = try? FileManager.default.attributesOfItem(atPath: file.path) {
if let bytes = fileAttributes[.size] as? Int64 {
Expand All @@ -329,7 +329,7 @@ final class TUSAPI {
]

/// Attach all headers from customHeader property
let headersWithCustom = headers.merging(metaData.customHeaders ?? [:]) { _, new in new }
let headersWithCustom = headers.merging(customHeaders) { _, new in new }

let request = makeRequest(url: location, method: .patch, headers: headersWithCustom)
let task = session.uploadTask(with: request, fromFile: file)
Expand Down Expand Up @@ -503,4 +503,3 @@ private extension TUSAPI {
}
}
}

Loading
Loading