Skip to content

Commit

Permalink
Streamed binary downloading example for Swift (#7141)
Browse files Browse the repository at this point in the history
* Streamed binary downloading example for Swift
Added an example showing streaming binary downloads using the Swift SDK. Also
made minor adjustments to the upload streaming example, including moving files
a bit to build both examples in one package.
  • Loading branch information
shepazon authored Dec 11, 2024
1 parent b60d516 commit 62fbeae
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 23 deletions.
27 changes: 26 additions & 1 deletion .doc_gen/metadata/s3_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3262,7 +3262,32 @@ s3_Scenario_UploadStream:
snippet_tags:
- s3.tm.java2.upload_stream.import
- s3.tm.java2.upload_stream.main

Swift:
versions:
- sdk_version: 1
github: swift/example_code/s3/binary-streaming
excerpts:
- description:
snippet_tags:
- swift.s3.streaming-up.imports
- swift.s3.streaming-up
services:
s3: {}
s3_Scenario_DownloadStream:
title: Download a stream of unknown size from an &S3; object using an &AWS; SDK
title_abbrev: Download stream of unknown size
synopsis: download a stream of unknown size from an &S3; object.
category: Scenarios
languages:
Swift:
versions:
- sdk_version: 1
github: swift/example_code/s3/binary-streaming
excerpts:
- description:
snippet_tags:
- swift.s3.streaming-down.imports
- swift.s3.streaming-down
services:
s3: {}
s3_Scenario_UseChecksums:
Expand Down
34 changes: 33 additions & 1 deletion swift/example_code/s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ Code excerpts that show you how to call individual service functions.
- [ListObjectsV2](basics/Sources/ServiceHandler/ServiceHandler.swift#L280)
- [PutObject](basics/Sources/ServiceHandler/ServiceHandler.swift#L107)

### Scenarios

Code examples that show you how to accomplish a specific task by calling multiple
functions within the same service.

- [Download stream of unknown size](binary-streaming/Sources/streamdown/streamdown.swift)
- [Upload stream of unknown size](binary-streaming/Sources/streamup/streamup.swift)


<!--custom.examples.start-->
<!--custom.examples.end-->
Expand Down Expand Up @@ -92,6 +100,30 @@ This example shows you how to do the following:
<!--custom.basics.s3_Scenario_GettingStarted.end-->


#### Download stream of unknown size

This example shows you how to download a stream of unknown size from an Amazon S3 object.


<!--custom.scenario_prereqs.s3_Scenario_DownloadStream.start-->
<!--custom.scenario_prereqs.s3_Scenario_DownloadStream.end-->


<!--custom.scenarios.s3_Scenario_DownloadStream.start-->
<!--custom.scenarios.s3_Scenario_DownloadStream.end-->

#### Upload stream of unknown size

This example shows you how to upload a stream of unknown size to an Amazon S3 object.


<!--custom.scenario_prereqs.s3_Scenario_UploadStream.start-->
<!--custom.scenario_prereqs.s3_Scenario_UploadStream.end-->


<!--custom.scenarios.s3_Scenario_UploadStream.start-->
<!--custom.scenarios.s3_Scenario_UploadStream.end-->

### Tests

⚠ Running tests might result in charges to your AWS account.
Expand All @@ -118,4 +150,4 @@ in the `swift` folder.

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
SPDX-License-Identifier: Apache-2.0
13 changes: 11 additions & 2 deletions swift/example_code/s3/binary-streaming/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import PackageDescription

let package = Package(
name: "streamup",
name: "binary-streaming",
// Let Xcode know the minimum Apple platforms supported.
platforms: [
.macOS(.v13),
Expand All @@ -34,6 +34,15 @@ let package = Package(
.product(name: "AWSS3", package: "aws-sdk-swift"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
path: "Sources"),
path: "Sources/streamup"
),
.executableTarget(
name: "streamdown",
dependencies: [
.product(name: "AWSS3", package: "aws-sdk-swift"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
path: "Sources/streamdown"
),
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ enum TransferError: Error {
case directoryError
/// An error occurred while downloading a file from Amazon S3.
case downloadError(_ message: String = "")
/// An error occurred moving the file to its final destination.
case fileMoveError
/// An error occurred while reading the file's contents.
case readError
/// An error occurred while uploading a file to Amazon S3.
case uploadError(_ message: String = "")
/// An error occurred while writing the file's contents.
case writeError

Expand All @@ -22,12 +16,6 @@ enum TransferError: Error {
return "The destination directory could not be located or created"
case .downloadError(message: let message):
return "An error occurred attempting to download the file: \(message)"
case .fileMoveError:
return "The file couldn't be moved to the destination directory"
case .readError:
return "An error occurred while reading the file data"
case .uploadError(message: let message):
return "An error occurred attempting to upload the file: \(message)"
case .writeError:
return "An error occurred while writing the file data"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
/// A simple example that shows how to use the AWS SDK for Swift to
/// upload files using binary streaming.

// snippet-start:[swift.s3.streaming-down.imports]
import ArgumentParser
import AWSClientRuntime
import AWSS3
import Foundation
import Smithy
import SmithyHTTPAPI
import SmithyStreams

// snippet-end:[swift.s3.streaming-down.imports]

// -MARK: - Async command line tool

struct ExampleCommand: ParsableCommand {
// -MARK: Command arguments
@Option(help: "Name of the Amazon S3 bucket to download from")
var bucket: String
@Option(help: "Key of the file on Amazon S3 to download")
var key: String
@Option(help: "Local path to download the file to")
var dest: String?
@Option(help: "Name of the Amazon S3 Region to use (default: us-east-1)")
var region = "us-east-1"

static var configuration = CommandConfiguration(
commandName: "streamdown",
abstract: """
This example shows how to use binary data streaming to download a file
from Amazon S3.
""",
discussion: """
"""
)

// snippet-start:[swift.s3.streaming-down]
/// Download a file from the specified bucket.
///
/// - Parameters:
/// - bucket: The Amazon S3 bucket name to get the file from.
/// - key: The name (or path) of the file to download from the bucket.
/// - destPath: The pathname on the local filesystem at which to store
/// the downloaded file.
func downloadFile(bucket: String, key: String, destPath: String?) async throws {
let fileURL: URL

// If no destination path was provided, use the key as the name to use
// for the file in the downloads folder.

if destPath == nil {
do {
try fileURL = FileManager.default.url(
for: .downloadsDirectory,
in: .userDomainMask,
appropriateFor: URL(string: key),
create: true
).appendingPathComponent(key)
} catch {
throw TransferError.directoryError
}
} else {
fileURL = URL(fileURLWithPath: destPath!)
}

let config = try await S3Client.S3ClientConfiguration(region: region)
let s3Client = S3Client(config: config)

// Create a `FileHandle` referencing the local destination. Then
// create a `ByteStream` from that.

FileManager.default.createFile(atPath: fileURL.path, contents: nil, attributes: nil)
let fileHandle = try FileHandle(forWritingTo: fileURL)

// Download the file using `GetObject`.

let getInput = GetObjectInput(
bucket: bucket,
key: key
)

do {
let getOutput = try await s3Client.getObject(input: getInput)

guard let body = getOutput.body else {
throw TransferError.downloadError("Error: No data returned for download")
}

// If the body is returned as a `Data` object, write that to the
// file. If it's a stream, read the stream chunk by chunk,
// appending each chunk to the destination file.

switch body {
case .data:
guard let data = try await body.readData() else {
throw TransferError.downloadError("Download error")
}

// Write the `Data` to the file.

do {
try data.write(to: fileURL)
} catch {
throw TransferError.writeError
}
break

case .stream(let stream as ReadableStream):
while (true) {
let chunk = try await stream.readAsync(upToCount: 5 * 1024 * 1024)
guard let chunk = chunk else {
break
}

// Write the chunk to the destination file.

do {
try fileHandle.write(contentsOf: chunk)
} catch {
throw TransferError.writeError
}
}

break
default:
throw TransferError.downloadError("Received data is unknown object type")
}
} catch {
throw TransferError.downloadError("Error downloading the file: \(error)")
}

print("File downloaded to \(fileURL.path).")
}
// snippet-end:[swift.s3.streaming-down]

// -MARK: - Asynchronous main code

/// Called by ``main()`` to run the bulk of the example.
func runAsync() async throws {
try await downloadFile(bucket: bucket, key: key, destPath: dest)
}
}

// -MARK: - Entry point

/// The program's asynchronous entry point.
@main
struct Main {
static func main() async {
let args = Array(CommandLine.arguments.dropFirst())

do {
let command = try ExampleCommand.parse(args)
try await command.runAsync()
} catch let error as TransferError {
print("ERROR: \(error.errorDescription ?? "Unknown error")")
} catch {
ExampleCommand.exit(withError: error)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

/// Errors thrown by the example's functions.
enum TransferError: Error {
/// An error occurred while reading the file's contents.
case readError
/// An error occurred while uploading a file to Amazon S3.
case uploadError(_ message: String = "")

var errorDescription: String? {
switch self {
case .readError:
return "An error occurred while reading the file data"
case .uploadError(message: let message):
return "An error occurred attempting to upload the file: \(message)"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// A simple example that shows how to use the AWS SDK for Swift to
/// upload files using binary streaming.

// snippet-start:[swift.s3.binary-streaming.imports]
// snippet-start:[swift.s3.streaming-up.imports]
import ArgumentParser
import AWSClientRuntime
import AWSS3
Expand All @@ -13,7 +13,7 @@ import Smithy
import SmithyHTTPAPI
import SmithyStreams

// snippet-end:[swift.s3.binary-streaming.imports]
// snippet-end:[swift.s3.streaming-up.imports]

// -MARK: - Async command line tool

Expand All @@ -38,14 +38,14 @@ struct ExampleCommand: ParsableCommand {
"""
)

// snippet-start:[swift.s3.binary-streaming.upload-file]
// snippet-start:[swift.s3.streaming-up]
/// Upload a file to the specified bucket.
///
/// - Parameters:
/// - bucket: The Amazon S3 bucket name to store the file into.
/// - key: The name (or path) of the file to upload to in the `bucket`.
/// - sourcePath: The pathname on the local filesystem at which to store
/// the uploaded file.
/// - sourcePath: The pathname on the local filesystem of the file to
/// upload.
func uploadFile(sourcePath: String, bucket: String, key: String?) async throws {
let fileURL: URL = URL(fileURLWithPath: sourcePath)
let fileName: String
Expand Down Expand Up @@ -91,7 +91,7 @@ struct ExampleCommand: ParsableCommand {

print("File uploaded to \(fileURL.path).")
}
// snippet-end:[swift.s3.binary-streaming.upload-file]
// snippet-end:[swift.s3.streaming-up]

// -MARK: - Asynchronous main code

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct ExampleCommand: ParsableCommand {

let s3Client = try await S3Client()

// Create a presigned URLRequest with the `GetObject` action.
// Download the file using `GetObject` and the stream's `readData()`.

let getInput = GetObjectInput(
bucket: bucket,
Expand Down

0 comments on commit 62fbeae

Please sign in to comment.