-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #138 from amzn/s3_folder
S3 "file system" utilities
- Loading branch information
Showing
4 changed files
with
323 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// | ||
// S3ClientProtocolV2+listFolder.swift | ||
// S3Client | ||
// | ||
|
||
import S3Model | ||
|
||
public extension S3ClientProtocolV2 { | ||
// Returns all S3 objects within the same S3 "folder" as a given object. | ||
// If the object identifier provided is a directory, the return value will | ||
// contain all objects within its parent "folder". | ||
// Optionally, the caller can pass a filename prefix provider; only objects | ||
// with the same prefix in the "folder" will be returned. | ||
func listFolder( | ||
for objectIdentifier: S3ObjectIdentifier, | ||
fileNamePrefixProvider: (String) throws -> String = { _ in "" }) async throws | ||
-> [S3ObjectIdentifier] { | ||
let bucketName = objectIdentifier.bucketName | ||
let s3Folder = try objectIdentifier.parentPath ?? "" | ||
let fileName = try objectIdentifier.fileName ?? "" | ||
let fileNamePrefix = try fileNamePrefixProvider(fileName) | ||
let listBucketPrefix = s3Folder + fileNamePrefix | ||
|
||
var nextToken: NextToken? = nil | ||
var objectKeys = Set<String>() | ||
repeat { | ||
let request = ListObjectsV2Request( | ||
bucket: bucketName, | ||
continuationToken: nextToken, | ||
prefix: listBucketPrefix) | ||
let response = try await self.listObjectsV2(input: request) | ||
|
||
// Filter objects from sub-folders | ||
objectKeys.formUnion((response.contents ?? []).compactMap(\.key) | ||
.filter { ($0.lastIndex(of: "/") ?? $0.startIndex) <= s3Folder.endIndex }) | ||
nextToken = response.nextContinuationToken | ||
} while nextToken != nil | ||
|
||
return objectKeys.map { S3ObjectIdentifier(bucketName: bucketName, keyPath: $0) } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// | ||
// S3ObjectIdentifier.swift | ||
// S3Client | ||
// | ||
|
||
import Foundation | ||
import S3Model | ||
|
||
/** | ||
An identifier for an S3 object, specifying the name of its bucket | ||
and its key path. | ||
*/ | ||
public struct S3ObjectIdentifier: Equatable { | ||
internal static let s3Prefix = "s3://" | ||
internal static let httpsPrefix = "https://" | ||
internal static let httpPrefix = "http://" | ||
internal static let s3EndpointRegex = #"^https?:\/\/(.+\.)?s3[.-][a-z0-9-]+\."# | ||
|
||
public let bucketName: String | ||
public let keyPath: String | ||
|
||
public init(bucketName: String, | ||
keyPath: String) { | ||
self.bucketName = bucketName | ||
self.keyPath = keyPath | ||
} | ||
|
||
// Returns the key path of the parent S3 "folder" containing the object, WITH trailing '/'. | ||
// For example, for an object at path "a/b/c/d.ext", the return value will be "a/b/c/". | ||
public var parentPath: String? { | ||
get throws { | ||
let pathComponents = try url.pathComponents | ||
if pathComponents.count <= 1 { | ||
return nil | ||
} else { | ||
var slashCharacterSet = CharacterSet() | ||
slashCharacterSet.insert(charactersIn: "/") | ||
let path = pathComponents.dropLast().joined(separator: "/").trimmingCharacters(in: slashCharacterSet) | ||
if path.isEmpty { | ||
return nil | ||
} else { | ||
return path + "/" | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Returns the "file name" of the object within its S3 "folder". If the key path is a directory path | ||
// (e.g. ending with '/'), the return value will be nil. | ||
public var fileName: String? { | ||
get throws { | ||
if try url.hasDirectoryPath { | ||
return nil | ||
} else { | ||
return try url.pathComponents.last | ||
} | ||
} | ||
} | ||
|
||
private var url: URL { | ||
get throws { | ||
let url = URL(string: "\(Self.httpsPrefix)\(bucketName).s3.amazonaws.com/\(keyPath)") | ||
guard let url else { | ||
throw S3Error.validationError(reason: "Cannot initialize URL for bucket \(bucketName) " + | ||
"and key \(keyPath).") | ||
} | ||
|
||
return url | ||
} | ||
} | ||
} | ||
|
||
// To keep backwards compatibility after fixing the typo | ||
public typealias S3ObjectIdentifer = S3ObjectIdentifier |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.