Skip to content

Commit ab1521e

Browse files
shepazonrlhagerm
authored andcommitted
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 ab1521e

File tree

6 files changed

+202
-21
lines changed

6 files changed

+202
-21
lines changed

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)