Skip to content

Commit

Permalink
more methods
Browse files Browse the repository at this point in the history
  • Loading branch information
rafiki270 committed May 12, 2018
1 parent 1860ba7 commit d6b3a3b
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 43 deletions.
33 changes: 33 additions & 0 deletions Sources/S3/Extensions/HTTPHeaders+Tools.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// HTTPHeaders+Tools.swift
// S3
//
// Created by Ondrej Rafaj on 12/05/2018.
//

import Foundation
import Vapor


extension HTTPHeaders {

func string(_ name: String) -> String? {
let header = HTTPHeaderName(name)
return self[header].first
}

func int(_ name: String) -> Int? {
guard let headerValue = string(name) else {
return nil
}
return Int(headerValue)
}

func date(_ name: String) -> Date? {
guard let headerValue = string(name) else {
return nil
}
return Response.headerDateFormatter.date(from: headerValue)
}

}
24 changes: 18 additions & 6 deletions Sources/S3/Extensions/Response+XMLDecoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,31 @@ import XMLCoding

extension Response {

func decode<T>(to: T.Type) throws -> T where T: Decodable {
guard let data = http.body.data else {
throw S3.Error.badResponse(self)
}

static var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()

static var headerDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"
return formatter
}()

func decode<T>(to: T.Type) throws -> T where T: Decodable {
guard let data = http.body.data else {
throw S3.Error.badResponse(self)
}

let decoder = XMLDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
decoder.dateDecodingStrategy = .formatted(Response.dateFormatter)
return try decoder.decode(T.self, from: data)
}

Expand Down
3 changes: 0 additions & 3 deletions Sources/S3/Extensions/S3+Bucket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public extension S3 {
// "Host": host
// ]
// let awsHeaders = try signer.headers(for: .GET, urlString: url.absoluteString, region: region, headers: headers, payload: .none)
//
// return try make(request: url, method: .GET, headers: awsHeaders, data: "".convertToData(), on: container).map(to: Bucket.Location.self) { response in
// if response.http.status == .notFound {
// throw Error.notFound
Expand Down Expand Up @@ -57,7 +56,6 @@ public extension S3 {
"Host": host
]
let awsHeaders = try signer.headers(for: .DELETE, urlString: url.absoluteString, region: region, headers: headers, payload: .none)

return try make(request: url, method: .DELETE, headers: awsHeaders, data: "".convertToData(), on: container).map(to: Void.self) { response in
try self.check(response)
return Void()
Expand Down Expand Up @@ -85,7 +83,6 @@ public extension S3 {
"Host": host
]
let awsHeaders = try signer.headers(for: .PUT, urlString: url.absoluteString, region: region, headers: headers, payload: .bytes(data))

return try make(request: url, method: .PUT, headers: awsHeaders, data: data, on: container).map(to: Void.self) { response in
try self.check(response)
return Void()
Expand Down
3 changes: 0 additions & 3 deletions Sources/S3/Extensions/S3+Delete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ public extension S3 {
/// Delete file from S3
public func delete(file: LocationConvertible, headers: [String: String] = [:], on container: Container) throws -> Future<Void> {
let signer = try container.makeS3Signer()

let url = try self.url(file: file, on: container)

let headers = try signer.headers(for: .DELETE, urlString: url.absoluteString, headers: headers, payload: .none)

return try make(request: url, method: .DELETE, headers: headers, data: "".convertToData(), on: container).map(to: Void.self) { response in
try self.check(response)

Expand Down
3 changes: 0 additions & 3 deletions Sources/S3/Extensions/S3+Get.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ public extension S3 {
/// Retrieve file data from S3
public func get(file: LocationConvertible, headers: [String: String] = [:], on container: Container) throws -> Future<File.Response> {
let signer = try container.makeS3Signer()

let url = try self.url(file: file, on: container)

let headers = try signer.headers(for: .GET, urlString: url.absoluteString, headers: headers, payload: .none)

return try make(request: url, method: .GET, headers: headers, on: container).map(to: File.Response.self) { response in
try self.check(response)

Expand Down
16 changes: 16 additions & 0 deletions Sources/S3/Extensions/S3+List.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// S3+List.swift
// S3
//
// Created by Ondrej Rafaj on 12/05/2018.
//

import Foundation


// Helper S3 extension for getting file indexes
extension S3 {



}
43 changes: 36 additions & 7 deletions Sources/S3/Extensions/S3+ObjectInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,48 @@ public extension S3 {

// MARK: Buckets

/// Get bucket location
public func get(fileInfo file: LocationConvertible, headers: [String: String] = [:], on container: Container) throws -> Future<File.Response> {
/// Get acl file information (ACL)
/// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGETacl.html
public func get(acl file: LocationConvertible, headers: [String: String] = [:], on container: Container) throws -> Future<File.Info> {
fatalError("Not implemented")
}

/// Get acl file information
/// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGETacl.html
func get(acl file: LocationConvertible, on container: Container) throws -> Future<File.Info> {
return try get(fileInfo: file, headers: [:], on: container)
}

/// Get file information (HEAD)
/// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
public func get(fileInfo file: LocationConvertible, headers: [String: String] = [:], on container: Container) throws -> Future<File.Info> {
let signer = try container.makeS3Signer()

let url = try self.url(file: file, on: container)

let headers = try signer.headers(for: .HEAD, urlString: url.absoluteString, headers: headers, payload: .none)

return try make(request: url, method: .HEAD, headers: headers, data: "".convertToData(), on: container).map(to: File.Response.self) { response in
return try make(request: url, method: .HEAD, headers: headers, data: "".convertToData(), on: container).map(to: File.Info.self) { response in
try self.check(response)

return try response.decode(to: File.Response.self)
let bucket = file.bucket ?? self.defaultBucket
let region = file.region ?? signer.config.region
let mime = response.http.headers.string(File.Info.CodingKeys.mime.rawValue)
let size = response.http.headers.int(File.Info.CodingKeys.size.rawValue)
let server = response.http.headers.string(File.Info.CodingKeys.server.rawValue)
let etag = response.http.headers.string(File.Info.CodingKeys.etag.rawValue)
let expiration = response.http.headers.date(File.Info.CodingKeys.expiration.rawValue)
let created = response.http.headers.date(File.Info.CodingKeys.created.rawValue)
let modified = response.http.headers.date(File.Info.CodingKeys.modified.rawValue)
let versionId = response.http.headers.string(File.Info.CodingKeys.versionId.rawValue)
let storageClass = response.http.headers.string(File.Info.CodingKeys.storageClass.rawValue)

let info = File.Info(bucket: bucket, region: region, path: file.path, access: .authenticatedRead, mime: mime, size: size, server: server, etag: etag, expiration: expiration, created: created, modified: modified, versionId: versionId, storageClass: storageClass)
return info
}
}

/// Get file information (HEAD)
/// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
func get(fileInfo file: LocationConvertible, on container: Container) throws -> Future<File.Info> {
return try get(fileInfo: file, headers: [:], on: container)
}

}
2 changes: 0 additions & 2 deletions Sources/S3/Extensions/S3+Put.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public extension S3 {
var awsHeaders: [String: String] = headers
awsHeaders["Content-Type"] = file.mime.description
awsHeaders["X-Amz-Acl"] = file.access.rawValue

let headers = try signer.headers(for: .PUT, urlString: url.absoluteString, headers: awsHeaders, payload: Payload.bytes(file.data))

let request = Request(using: container)
Expand All @@ -35,7 +34,6 @@ public extension S3 {
let client = try container.make(Client.self)
return client.send(request).map(to: File.Response.self) { response in
try self.check(response)

let res = File.Response(data: file.data, bucket: file.bucket ?? self.defaultBucket, path: file.path, access: file.access, mime: file.mime)
return res
}
Expand Down
4 changes: 0 additions & 4 deletions Sources/S3/Extensions/S3+Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@ public extension S3 {
/// Get list of buckets
public func buckets(on container: Container) throws -> Future<BucketsInfo> {
let signer = try container.makeS3Signer()

let url = try self.url(on: container)

let headers = try signer.headers(for: .GET, urlString: url.absoluteString, payload: .none)

return try make(request: url, method: .GET, headers: headers, data: "".convertToData(), on: container).map(to: BucketsInfo.self) { response in
try self.check(response)

return try response.decode(to: BucketsInfo.self)
}
}
Expand Down
14 changes: 14 additions & 0 deletions Sources/S3/Models/BucketResults.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// BucketResults.swift
// S3
//
// Created by Ondrej Rafaj on 12/05/2018.
//

import Foundation
import Vapor


public class BucketResults {

}
64 changes: 62 additions & 2 deletions Sources/S3/Models/File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ public struct File {

}

/// File response comming back from S3
/// File data response comming back from S3
public struct Response: Content {

/// Data
public internal(set) var data: Data

/// Override target bucket
public internal(set) var bucket: String?
public internal(set) var bucket: String

/// S3 file path
public internal(set) var path: String
Expand All @@ -104,4 +104,64 @@ public struct File {

}

/// File info response comming back from S3
public struct Info: Content {

/// Override target bucket
public internal(set) var bucket: String

/// Override target bucket
public internal(set) var region: Region

/// S3 file path
public internal(set) var path: String

/// Access control for file
public internal(set) var access: AccessControlList

/// File type (mime)
public internal(set) var mime: String?

/// File size
public internal(set) var size: Int?

/// Server
public internal(set) var server: String?

/// ETag
public internal(set) var etag: String?

/// Expiration
public internal(set) var expiration: Date?

/// Date created
public internal(set) var created: Date?

/// Last modified
public internal(set) var modified: Date?

/// Version ID
public internal(set) var versionId: String?

/// Storage class
public internal(set) var storageClass: String?

enum CodingKeys: String, CodingKey {
case bucket = "Bucket"
case region = "Region"
case path = "Path"
case access = "Access"
case mime = "Content-Type"
case size = "Content-Length"
case server = "Server"
case etag = "ETag"
case expiration = "x-amz-expiration"
case created = "Date"
case modified = "Last-Modified"
case versionId = "x-amz-version-id"
case storageClass = "x-amz-storage-class"
}

}

}
2 changes: 2 additions & 0 deletions Sources/S3/Models/Owner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import Vapor
public struct Owner: Content {

public let id: String
public let name: String?

enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "DisplayName"
}

}
3 changes: 3 additions & 0 deletions Sources/S3/Protocols/S3Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public protocol S3Client: Service {
func put(string: String, mime: MediaType, destination: String, access: AccessControlList, on: Container) throws -> Future<File.Response>
func put(string: String, mime: MediaType, destination: String, bucket: String?, access: AccessControlList, on: Container) throws -> Future<File.Response>

func get(fileInfo file: LocationConvertible, on container: Container) throws -> Future<File.Info>
func get(fileInfo file: LocationConvertible, headers: [String: String], on container: Container) throws -> Future<File.Info>

func get(file: LocationConvertible, on: Container) throws -> Future<File.Response>
func get(file: LocationConvertible, headers: [String: String], on: Container) throws -> Future<File.Response>

Expand Down
2 changes: 1 addition & 1 deletion Sources/S3/S3.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import HTTP


/// Main S3 class
public class S3: S3Client {
public class S3: S3Client {

/// Error messages
public enum Error: Swift.Error {
Expand Down
25 changes: 15 additions & 10 deletions Sources/S3DemoApp/S3DemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,22 @@ public func routes(_ router: Router) throws {
print("GET response:")
print(getResponse)
print(String(data: getResponse.data, encoding: .utf8) ?? "Unknown content!")
return try s3.delete(file: fileName, on: req).map() { response in
print("DELETE response:")
print(response)
return String(data: getResponse.data, encoding: .utf8) ?? "Unknown content!"
}.catchMap({ error -> (String) in
if let error = error.s3ErroMessage() {
return error.message
return try s3.get(fileInfo: fileName, on: req).flatMap(to: String.self) { infoResponse in
print("HEAD/Info response:")
print(infoResponse)
return try s3.delete(file: fileName, on: req).map() { response in
print("DELETE response:")
print(response)
let json = try JSONEncoder().encode(infoResponse)
return String(data: json, encoding: .utf8) ?? "Unknown content!"
}.catchMap({ error -> (String) in
if let error = error.s3ErroMessage() {
return error.message
}
return ":("
}
return ":("
}
)
)
}
}
}
} catch {
Expand Down
2 changes: 1 addition & 1 deletion Sources/S3Signer/Region.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation


/// AWS Region
public enum Region: String {
public enum Region: String, Codable {

/// US East (N. Virginia)
case usEast1 = "us-east-1"
Expand Down
Loading

0 comments on commit d6b3a3b

Please sign in to comment.