Skip to content
Closed
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/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
However to keep things clean and neat we ask you follow these small guidelines to help ensure everything runs smoothly.

## Pull Requests
Please direct all Pull Requests to the `master` branch of the repo.
Please direct all Pull Requests to the `main` branch of the repo.

Also please

Expand Down
8 changes: 1 addition & 7 deletions .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.5"]
swift: ["5"]
runs-on: ${{ matrix.os }}
steps:
- name: Extract Branch Name
Expand All @@ -19,9 +19,3 @@ jobs:
run: swift build -Xswiftc --disable-experimental-concurrency
- name: Run tests
run: swift test -Xswiftc --disable-experimental-concurrency
- uses: 8398a7/action-slack@v3
if: failure() && env.BRANCH == 'master'
with:
status: failure
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
77 changes: 77 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/TUSKit.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TUSKit"
BuildableName = "TUSKit"
BlueprintName = "TUSKit"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TUSKitTests"
BuildableName = "TUSKitTests"
BlueprintName = "TUSKitTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TUSKit"
BuildableName = "TUSKit"
BlueprintName = "TUSKit"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
# 3.2

## Enhancements

- TUSKit can now leverage Background URLSession to allow uploads to continue while an app is backgrounded. See the README.md for instructions on migrating to leverage this functionality.

# 3.1.7

## Enhancements
- It's now possible to inspect the status code for failed uploads that did not have a 200 OK HTTP status code. See the following example from the sample app:

```swift
func uploadFailed(id: UUID, error: Error, context: [String : String]?, client: TUSClient) {
Task { @MainActor in
uploads[id] = .failed(error: error)

if case TUSClientError.couldNotUploadFile(underlyingError: let underlyingError) = error,
case TUSAPIError.failedRequest(let response) = underlyingError {
print("upload failed with response \(response)")
}
}
}
```

# 3.1.6

## Enhancements
- Added ability to fetch in progress / current uploads using `getStoredUploads()` on a `TUSClient` instance.

# 3.1.5
## Fixed
- Fixed issue with missing custom headers.

# 3.1.4
## Fixed
- Fix compile error Xcode 14
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription

let package = Package(
name: "TUSKit",
platforms: [.iOS(.v10), .macOS(.v10_10)],
platforms: [.iOS(.v10), .macOS(.v10_11)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
Expand Down
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,31 @@ do {

## Background uploading

When you incorporate background-uploading, we strongly recommend you to inspect any failed uploads that may have occured in the background. Please refer to [Starting a new Session](#starting-a-new-session) for more information.

iOS can leverage a background URLSession to enable background uploads that continue when a user leaves your app or locks their device. For more information, take a look at Apple's docs on [background URLSession](https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background). The docs focus on downloads but uploads follow pretty much the exact same principles.

To make incorporating background uploads as straightforward as possible, TUSKit handles all the complex details of managing the URLSession and delegate callbacks. As a consumer of TUSKit all you need to do is leverage the new initializer on `TUSClient` as shown below:

```swift
do {
tusClient = try TUSClient(
server: URL(string: "https://tusd.tusdemo.net/files")!,
sessionIdentifier: "TUS DEMO",
sessionConfiguration: .background(withIdentifier: "com.TUSKit.sample"),
storageDirectory: URL(string: "/TUS")!,
chunkSize: 0
)
} catch {
// ...
}
```

The easiest way to set everything up is to pass a `URLSession.background` configuration to the `TUSClient`. If you require a different configuration or don't want any background support at all, you're free to pass a different configuration.

Because TUSKit can now have uploads running while your app is no longer actively in memory, you should always use the `getStoredUpload` method on `TUSClient` on app launch to retrieve all stored uploads and extract information about which uploads are currently completed. Afterwards you can call `cleanup` to allow `TUSClient` to remove metadata for completed items. See the sample app for more details.

### Warning: information below is deprecated in TUSKit 3.2.0.
Available from iOS13, you can schedule uploads to be performed in the background using the `scheduleBackgroundTasks()` method on `TUSClient`.

Scheduled tasks are handled by iOS. Which means that each device will decide when it's best to upload in the background. Such as when it has a wifi connection and late at night.
Expand All @@ -170,9 +195,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
```


If you incorporate background-uploading, we strongly recommend you to inspect any failed uploads that may have occured in the background. Please refer to [Starting a new Session](#Starting a new Session) for more information.

## TUS Protocol Extensions
The client assumes by default that the server implements the [Creation TUS protocol extension](https://tus.io/protocols/resumable-upload.html#protocol-extensions). If your server does not support that, please ensure to provide an empty array for the `supportedExtensions` parameter in the client initializer.

Expand Down
13 changes: 9 additions & 4 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Roadmap

- [x] Release version with latest changes (changes: https://github.com/tus/TUSKit/compare/3.1.4...main, relevant issues: https://github.com/tus/TUSKit/issues/138, https://github.com/tus/TUSKit/issues/141 and https://community.transloadit.com/t/ios-tus-client-version-update/16395/3)
- [x] Create a documentation describing the release process (pods, swift package manager?)
- [x] Update metadata (author, language, repo) in CocoaPods: https://cocoapods.org/pods/TUSKit
- [ ] Create an example app where TUSKit is used in (would show you how to use the client from a customer’s point of view. Should help evolve the API)
- [ ] Add pause / resume functionality
- [ ] Add progress bar
- [ ] Add ability to resume uploads from previous app session
- [x] Add pause / resume functionality
- [x] Add progress indicator
- [x] Add ability to resume uploads from previous app session
- [ ] Add ability to upload files in background
- [ ] Review and address issues & PRs in GitHub until there are zero
- [ ] Create a release blog post on tus.io with the changes from 2.x to 3.x and some usage examples
- [ ] ~~Create a release blog post on tus.io with the changes from 2.x to 3.x and some usage examples~~
- [ ] Fix CI tests
- [ ] Think about automating releasing using CI
24 changes: 18 additions & 6 deletions Sources/TUSKit/Files.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,16 @@ final class Files {
}

static private var documentsDirectory: URL {
#if os(macOS)
var directory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
if let bundleId = Bundle.main.bundleIdentifier {
directory = directory.appendingPathComponent(bundleId)
}
return directory
#else
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
#endif

}

/// Loads all metadata (decoded plist files) from the target directory.
Expand All @@ -80,7 +89,6 @@ final class Files {
/// - Returns: An array of UploadMetadata types
func loadAllMetadata() throws -> [UploadMetadata] {
try queue.sync {

let directoryContents = try FileManager.default.contentsOfDirectory(at: storageDirectory, includingPropertiesForKeys: nil)

// if you want to filter the directory contents you can do like this:
Expand Down Expand Up @@ -143,7 +151,8 @@ final class Files {
}

let targetLocation = storageDirectory.appendingPathComponent(fileName)
try data.write(to: targetLocation, options: .atomic)

try! data.write(to: targetLocation, options: .atomic)
return targetLocation
}
}
Expand All @@ -153,11 +162,14 @@ final class Files {
/// - Throws: Any error from FileManager when removing a file.
func removeFileAndMetadata(_ metaData: UploadMetadata) throws {
let filePath = metaData.filePath
let metaDataPath = metaData.filePath.appendingPathExtension("plist")
let fileName = filePath.lastPathComponent
let metaDataPath = storageDirectory.appendingPathComponent(fileName).appendingPathExtension("plist")

try queue.sync {
try FileManager.default.removeItem(at: filePath)
try FileManager.default.removeItem(at: metaDataPath)
#if os(iOS)
try FileManager.default.removeItem(at: filePath)
#endif
}
}

Expand All @@ -174,8 +186,8 @@ final class Files {
// Could not find the file that's related to this metadata.
throw FilesError.relatedFileNotFound
}

let targetLocation = metaData.filePath.appendingPathExtension("plist")
let fileName = metaData.filePath.lastPathComponent
let targetLocation = storageDirectory.appendingPathComponent(fileName).appendingPathExtension("plist")
try self.makeDirectoryIfNeeded()

let encoder = PropertyListEncoder()
Expand Down
3 changes: 3 additions & 0 deletions Sources/TUSKit/Network.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ extension URLSession {
return uploadTask(with: request, from: data, completionHandler: makeCompletion(completion: completion))
}

func uploadTask(with request: URLRequest, fromFile file: URL, completion: @escaping (Result<(Data?, HTTPURLResponse), Error>) -> Void) -> URLSessionUploadTask {
return uploadTask(with: request, fromFile: file, completionHandler: makeCompletion(completion: completion))
}
}

/// Convenience method to turn a URLSession completion handler into a modern Result version. It also checks if response is a HTTPURLResponse
Expand Down
4 changes: 2 additions & 2 deletions Sources/TUSKit/Scheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class Scheduler {
// Tasks are processed in background
let queue = DispatchQueue(label: "com.TUSKit.Scheduler")

/// Add multiple tasks. Note that these are independent tasks. If you want multiple tasks that are related in one way or another, use addGroupedTasks
/// Add multiple tasks. Note that these are independent tasks.
/// - Parameter tasks: The tasks to add
func addTasks(tasks: [ScheduledTask]) {
queue.async {
Expand Down Expand Up @@ -126,7 +126,7 @@ final class Scheduler {
}

}
}
}

/// Get first available task, removes it from current tasks
/// - Returns: First next task, or nil if tasks are empty
Expand Down
Loading