Skip to content

Commit 62fbeae

Browse files
authored
Streamed binary downloading example for Swift (#7141)
* 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.
1 parent b60d516 commit 62fbeae

File tree

8 files changed

+261
-23
lines changed

8 files changed

+261
-23
lines changed

.doc_gen/metadata/s3_metadata.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3262,7 +3262,32 @@ s3_Scenario_UploadStream:
32623262
snippet_tags:
32633263
- s3.tm.java2.upload_stream.import
32643264
- s3.tm.java2.upload_stream.main
3265-
3265+
Swift:
3266+
versions:
3267+
- sdk_version: 1
3268+
github: swift/example_code/s3/binary-streaming
3269+
excerpts:
3270+
- description:
3271+
snippet_tags:
3272+
- swift.s3.streaming-up.imports
3273+
- swift.s3.streaming-up
3274+
services:
3275+
s3: {}
3276+
s3_Scenario_DownloadStream:
3277+
title: Download a stream of unknown size from an &S3; object using an &AWS; SDK
3278+
title_abbrev: Download stream of unknown size
3279+
synopsis: download a stream of unknown size from an &S3; object.
3280+
category: Scenarios
3281+
languages:
3282+
Swift:
3283+
versions:
3284+
- sdk_version: 1
3285+
github: swift/example_code/s3/binary-streaming
3286+
excerpts:
3287+
- description:
3288+
snippet_tags:
3289+
- swift.s3.streaming-down.imports
3290+
- swift.s3.streaming-down
32663291
services:
32673292
s3: {}
32683293
s3_Scenario_UseChecksums:

swift/example_code/s3/README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ Code excerpts that show you how to call individual service functions.
5050
- [ListObjectsV2](basics/Sources/ServiceHandler/ServiceHandler.swift#L280)
5151
- [PutObject](basics/Sources/ServiceHandler/ServiceHandler.swift#L107)
5252

53+
### Scenarios
54+
55+
Code examples that show you how to accomplish a specific task by calling multiple
56+
functions within the same service.
57+
58+
- [Download stream of unknown size](binary-streaming/Sources/streamdown/streamdown.swift)
59+
- [Upload stream of unknown size](binary-streaming/Sources/streamup/streamup.swift)
60+
5361

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

94102

103+
#### Download stream of unknown size
104+
105+
This example shows you how to download a stream of unknown size from an Amazon S3 object.
106+
107+
108+
<!--custom.scenario_prereqs.s3_Scenario_DownloadStream.start-->
109+
<!--custom.scenario_prereqs.s3_Scenario_DownloadStream.end-->
110+
111+
112+
<!--custom.scenarios.s3_Scenario_DownloadStream.start-->
113+
<!--custom.scenarios.s3_Scenario_DownloadStream.end-->
114+
115+
#### Upload stream of unknown size
116+
117+
This example shows you how to upload a stream of unknown size to an Amazon S3 object.
118+
119+
120+
<!--custom.scenario_prereqs.s3_Scenario_UploadStream.start-->
121+
<!--custom.scenario_prereqs.s3_Scenario_UploadStream.end-->
122+
123+
124+
<!--custom.scenarios.s3_Scenario_UploadStream.start-->
125+
<!--custom.scenarios.s3_Scenario_UploadStream.end-->
126+
95127
### Tests
96128

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

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

121-
SPDX-License-Identifier: Apache-2.0
153+
SPDX-License-Identifier: Apache-2.0

swift/example_code/s3/binary-streaming/Package.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import PackageDescription
99

1010
let package = Package(
11-
name: "streamup",
11+
name: "binary-streaming",
1212
// Let Xcode know the minimum Apple platforms supported.
1313
platforms: [
1414
.macOS(.v13),
@@ -34,6 +34,15 @@ let package = Package(
3434
.product(name: "AWSS3", package: "aws-sdk-swift"),
3535
.product(name: "ArgumentParser", package: "swift-argument-parser"),
3636
],
37-
path: "Sources"),
37+
path: "Sources/streamup"
38+
),
39+
.executableTarget(
40+
name: "streamdown",
41+
dependencies: [
42+
.product(name: "AWSS3", package: "aws-sdk-swift"),
43+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
44+
],
45+
path: "Sources/streamdown"
46+
),
3847
]
3948
)

swift/example_code/s3/binary-streaming/Sources/TransferError.swift renamed to swift/example_code/s3/binary-streaming/Sources/streamdown/TransferError.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@ enum TransferError: Error {
77
case directoryError
88
/// An error occurred while downloading a file from Amazon S3.
99
case downloadError(_ message: String = "")
10-
/// An error occurred moving the file to its final destination.
11-
case fileMoveError
12-
/// An error occurred while reading the file's contents.
13-
case readError
14-
/// An error occurred while uploading a file to Amazon S3.
15-
case uploadError(_ message: String = "")
1610
/// An error occurred while writing the file's contents.
1711
case writeError
1812

@@ -22,12 +16,6 @@ enum TransferError: Error {
2216
return "The destination directory could not be located or created"
2317
case .downloadError(message: let message):
2418
return "An error occurred attempting to download the file: \(message)"
25-
case .fileMoveError:
26-
return "The file couldn't be moved to the destination directory"
27-
case .readError:
28-
return "An error occurred while reading the file data"
29-
case .uploadError(message: let message):
30-
return "An error occurred attempting to upload the file: \(message)"
3119
case .writeError:
3220
return "An error occurred while writing the file data"
3321
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
/// A simple example that shows how to use the AWS SDK for Swift to
5+
/// upload files using binary streaming.
6+
7+
// snippet-start:[swift.s3.streaming-down.imports]
8+
import ArgumentParser
9+
import AWSClientRuntime
10+
import AWSS3
11+
import Foundation
12+
import Smithy
13+
import SmithyHTTPAPI
14+
import SmithyStreams
15+
16+
// snippet-end:[swift.s3.streaming-down.imports]
17+
18+
// -MARK: - Async command line tool
19+
20+
struct ExampleCommand: ParsableCommand {
21+
// -MARK: Command arguments
22+
@Option(help: "Name of the Amazon S3 bucket to download from")
23+
var bucket: String
24+
@Option(help: "Key of the file on Amazon S3 to download")
25+
var key: String
26+
@Option(help: "Local path to download the file to")
27+
var dest: String?
28+
@Option(help: "Name of the Amazon S3 Region to use (default: us-east-1)")
29+
var region = "us-east-1"
30+
31+
static var configuration = CommandConfiguration(
32+
commandName: "streamdown",
33+
abstract: """
34+
This example shows how to use binary data streaming to download a file
35+
from Amazon S3.
36+
""",
37+
discussion: """
38+
"""
39+
)
40+
41+
// snippet-start:[swift.s3.streaming-down]
42+
/// Download a file from the specified bucket.
43+
///
44+
/// - Parameters:
45+
/// - bucket: The Amazon S3 bucket name to get the file from.
46+
/// - key: The name (or path) of the file to download from the bucket.
47+
/// - destPath: The pathname on the local filesystem at which to store
48+
/// the downloaded file.
49+
func downloadFile(bucket: String, key: String, destPath: String?) async throws {
50+
let fileURL: URL
51+
52+
// If no destination path was provided, use the key as the name to use
53+
// for the file in the downloads folder.
54+
55+
if destPath == nil {
56+
do {
57+
try fileURL = FileManager.default.url(
58+
for: .downloadsDirectory,
59+
in: .userDomainMask,
60+
appropriateFor: URL(string: key),
61+
create: true
62+
).appendingPathComponent(key)
63+
} catch {
64+
throw TransferError.directoryError
65+
}
66+
} else {
67+
fileURL = URL(fileURLWithPath: destPath!)
68+
}
69+
70+
let config = try await S3Client.S3ClientConfiguration(region: region)
71+
let s3Client = S3Client(config: config)
72+
73+
// Create a `FileHandle` referencing the local destination. Then
74+
// create a `ByteStream` from that.
75+
76+
FileManager.default.createFile(atPath: fileURL.path, contents: nil, attributes: nil)
77+
let fileHandle = try FileHandle(forWritingTo: fileURL)
78+
79+
// Download the file using `GetObject`.
80+
81+
let getInput = GetObjectInput(
82+
bucket: bucket,
83+
key: key
84+
)
85+
86+
do {
87+
let getOutput = try await s3Client.getObject(input: getInput)
88+
89+
guard let body = getOutput.body else {
90+
throw TransferError.downloadError("Error: No data returned for download")
91+
}
92+
93+
// If the body is returned as a `Data` object, write that to the
94+
// file. If it's a stream, read the stream chunk by chunk,
95+
// appending each chunk to the destination file.
96+
97+
switch body {
98+
case .data:
99+
guard let data = try await body.readData() else {
100+
throw TransferError.downloadError("Download error")
101+
}
102+
103+
// Write the `Data` to the file.
104+
105+
do {
106+
try data.write(to: fileURL)
107+
} catch {
108+
throw TransferError.writeError
109+
}
110+
break
111+
112+
case .stream(let stream as ReadableStream):
113+
while (true) {
114+
let chunk = try await stream.readAsync(upToCount: 5 * 1024 * 1024)
115+
guard let chunk = chunk else {
116+
break
117+
}
118+
119+
// Write the chunk to the destination file.
120+
121+
do {
122+
try fileHandle.write(contentsOf: chunk)
123+
} catch {
124+
throw TransferError.writeError
125+
}
126+
}
127+
128+
break
129+
default:
130+
throw TransferError.downloadError("Received data is unknown object type")
131+
}
132+
} catch {
133+
throw TransferError.downloadError("Error downloading the file: \(error)")
134+
}
135+
136+
print("File downloaded to \(fileURL.path).")
137+
}
138+
// snippet-end:[swift.s3.streaming-down]
139+
140+
// -MARK: - Asynchronous main code
141+
142+
/// Called by ``main()`` to run the bulk of the example.
143+
func runAsync() async throws {
144+
try await downloadFile(bucket: bucket, key: key, destPath: dest)
145+
}
146+
}
147+
148+
// -MARK: - Entry point
149+
150+
/// The program's asynchronous entry point.
151+
@main
152+
struct Main {
153+
static func main() async {
154+
let args = Array(CommandLine.arguments.dropFirst())
155+
156+
do {
157+
let command = try ExampleCommand.parse(args)
158+
try await command.runAsync()
159+
} catch let error as TransferError {
160+
print("ERROR: \(error.errorDescription ?? "Unknown error")")
161+
} catch {
162+
ExampleCommand.exit(withError: error)
163+
}
164+
}
165+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/// Errors thrown by the example's functions.
5+
enum TransferError: Error {
6+
/// An error occurred while reading the file's contents.
7+
case readError
8+
/// An error occurred while uploading a file to Amazon S3.
9+
case uploadError(_ message: String = "")
10+
11+
var errorDescription: String? {
12+
switch self {
13+
case .readError:
14+
return "An error occurred while reading the file data"
15+
case .uploadError(message: let message):
16+
return "An error occurred attempting to upload the file: \(message)"
17+
}
18+
}
19+
}

swift/example_code/s3/binary-streaming/Sources/entry.swift renamed to swift/example_code/s3/binary-streaming/Sources/streamup/streamup.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/// A simple example that shows how to use the AWS SDK for Swift to
55
/// upload files using binary streaming.
66

7-
// snippet-start:[swift.s3.binary-streaming.imports]
7+
// snippet-start:[swift.s3.streaming-up.imports]
88
import ArgumentParser
99
import AWSClientRuntime
1010
import AWSS3
@@ -13,7 +13,7 @@ import Smithy
1313
import SmithyHTTPAPI
1414
import SmithyStreams
1515

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

1818
// -MARK: - Async command line tool
1919

@@ -38,14 +38,14 @@ struct ExampleCommand: ParsableCommand {
3838
"""
3939
)
4040

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

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

9696
// -MARK: - Asynchronous main code
9797

swift/example_code/s3/presigned-urls/Sources/presigned-download/entry.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ struct ExampleCommand: ParsableCommand {
8080

8181
let s3Client = try await S3Client()
8282

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

8585
let getInput = GetObjectInput(
8686
bucket: bucket,

0 commit comments

Comments
 (0)