diff --git a/.github/images/aws.svg b/.github/images/aws.svg index fd5a6e7..d9227cc 100644 --- a/.github/images/aws.svg +++ b/.github/images/aws.svg @@ -1,3 +1,7 @@ + + diff --git a/aws-common/build.gradle.kts b/aws-common/build.gradle.kts index 7611492..6ba35eb 100644 --- a/aws-common/build.gradle.kts +++ b/aws-common/build.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinCocoapods) diff --git a/aws-common/src/androidUnitTest/kotlin/com/estivensh4/aws_kmp/Test.android.kt b/aws-common/src/androidUnitTest/kotlin/com/estivensh4/aws_kmp/Test.android.kt index b814e14..35682e3 100644 --- a/aws-common/src/androidUnitTest/kotlin/com/estivensh4/aws_kmp/Test.android.kt +++ b/aws-common/src/androidUnitTest/kotlin/com/estivensh4/aws_kmp/Test.android.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_kmp import org.junit.Assert.assertTrue diff --git a/aws-common/src/commonMain/kotlin/com/estivensh4/aws_kmp/AwsException.kt b/aws-common/src/commonMain/kotlin/com/estivensh4/aws_kmp/AwsException.kt index 83e0e4d..bd6a78d 100644 --- a/aws-common/src/commonMain/kotlin/com/estivensh4/aws_kmp/AwsException.kt +++ b/aws-common/src/commonMain/kotlin/com/estivensh4/aws_kmp/AwsException.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_kmp class AwsException : Exception { diff --git a/aws-common/src/commonTest/kotlin/com/estivensh4/aws_kmp/Test.kt b/aws-common/src/commonTest/kotlin/com/estivensh4/aws_kmp/Test.kt index 5291cc9..822a02c 100644 --- a/aws-common/src/commonTest/kotlin/com/estivensh4/aws_kmp/Test.kt +++ b/aws-common/src/commonTest/kotlin/com/estivensh4/aws_kmp/Test.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_kmp import kotlin.test.Test diff --git a/aws-common/src/iosTest/kotlin/com/estivensh4/aws_kmp/Test.ios.kt b/aws-common/src/iosTest/kotlin/com/estivensh4/aws_kmp/Test.ios.kt index 40a8d13..a134db3 100644 --- a/aws-common/src/iosTest/kotlin/com/estivensh4/aws_kmp/Test.ios.kt +++ b/aws-common/src/iosTest/kotlin/com/estivensh4/aws_kmp/Test.ios.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_kmp import kotlin.test.Test diff --git a/aws-s3/build.gradle.kts b/aws-s3/build.gradle.kts index a32087c..ce434f8 100644 --- a/aws-s3/build.gradle.kts +++ b/aws-s3/build.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinCocoapods) diff --git a/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt b/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt similarity index 68% rename from aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt rename to aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt index eb6d098..f9ca7c4 100644 --- a/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt +++ b/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import com.amazonaws.auth.BasicAWSCredentials @@ -7,6 +11,7 @@ import com.amazonaws.services.s3.AmazonS3Client import com.amazonaws.services.s3.S3ClientOptions import com.amazonaws.services.s3.model.AmazonS3Exception import com.amazonaws.services.s3.model.DeleteObjectsRequest +import com.amazonaws.services.s3.model.ListObjectsV2Result import com.amazonaws.services.s3.model.ObjectMetadata import com.estivensh4.aws_kmp.AwsException import com.estivensh4.aws_s3.util.toAWSMethod @@ -14,8 +19,7 @@ import kotlinx.datetime.Instant import java.io.FileNotFoundException import java.util.Calendar - -actual class AwsS3 actual constructor( +actual class AWSS3 actual constructor( private val accessKey: String, private val secretKey: String, private val endpoint: String @@ -30,13 +34,11 @@ actual class AwsS3 actual constructor( ), Region.getRegion(Regions.US_EAST_1) ).apply { - this.endpoint = this@AwsS3.endpoint + this.endpoint = this@AWSS3.endpoint setS3ClientOptions(S3ClientOptions.builder().setPathStyleAccess(true).build()) } } - actual val endpointAWS get() = client.endpoint - /** * * @@ -77,14 +79,14 @@ actual class AwsS3 actual constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @return A pre-signed URL which expires at the specified time, and can be * used to allow anyone to download the specified object from S3, * without exposing the owner's AWS secret access key. * @throws AwsException If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ actual fun generatePresignedUrl( bucketName: String, @@ -146,7 +148,7 @@ actual class AwsS3 actual constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @param method The HTTP method verb to use for this URL * @return A pre-signed URL which expires at the specified time, and can be @@ -154,8 +156,7 @@ actual class AwsS3 actual constructor( * without exposing the owner's AWS secret access key. * @throws AwsException If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ actual fun generatePresignedUrl( bucketName: String, @@ -234,8 +235,8 @@ actual class AwsS3 actual constructor( * security credentials. * @throws AwsException If there were any problems pre-signing the * request for the Amazon S3 resource. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ actual fun generatePresignedUrl( generatePresignedUrlRequest: GeneratePresignedUrlRequest @@ -258,37 +259,11 @@ actual class AwsS3 actual constructor( } } - - actual class Builder { - private var accessKey: String = "" - private var secretKey: String = "" - private var endpoint: String = "" - - actual fun accessKey(accessKey: String): Builder { - this.accessKey = accessKey - return this - } - - actual fun secretKey(secretKey: String): Builder { - this.secretKey = secretKey - return this - } - - actual fun setEndpoint(endpoint: String): Builder { - this.endpoint = endpoint - return this - } - - actual fun build(): AwsS3 { - return AwsS3(accessKey, secretKey, endpoint) - } - } - /** * * * Creates a new Amazon S3 bucket with the specified name in the default - * (US) region, [Region.US_Standard]. + * (US) region, [Region]. * * * @@ -354,14 +329,66 @@ actual class AwsS3 actual constructor( return result.toBucket() } + /** + * + * + * Returns a list of all Amazon S3 buckets that the authenticated sender of + * the request owns. + * + * + * + * Users must authenticate with a valid AWS Access Key ID that is registered + * with Amazon S3. Anonymous requests cannot list buckets, and users cannot + * list buckets that they did not create. + * + * + * @return A list of all of the Amazon S3 buckets owned by the authenticated + * sender of the request. + * @throws AWSS3 If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listBuckets + */ actual suspend fun listBuckets(): List { return client.listBuckets().map { it.toBucket() } } + /** + * + * + * Deletes the specified bucket. All objects (and all object versions, if + * versioning was ever enabled) in the bucket must be deleted before the + * bucket itself can be deleted. + * + * + * + * Only the owner of a bucket can delete it, regardless of the bucket's + * access control policy. + * + * + * @param bucketName The name of the bucket to delete. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.deleteBucket + */ actual suspend fun deleteBucket(bucketName: String) { client.deleteBucket(bucketName) } + /** + * Deletes multiple objects in a single bucket from S3. + * + * + * In some cases, some objects will be successfully deleted, while some + * attempts will cause an error. If any object in the request cannot be + * deleted, this method throws a [AwsException] with + * details of the error. + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param keys The request object containing all options for + * deleting multiple objects. + * @throws AwsException If any errors occurred in Amazon S3 while + * processing the request. + */ actual suspend fun deleteObjects(bucketName: String, vararg keys: String): DeleteObjectResult { val deleteObjectsRequest = DeleteObjectsRequest(bucketName) deleteObjectsRequest.withKeys(*keys) @@ -381,6 +408,69 @@ actual class AwsS3 actual constructor( ) } + /** + * + * + * Uploads the specified file to Amazon S3 under the specified bucket and + * key name. + * + * + * + * Amazon S3 never stores partial objects; if during this call an exception + * wasn't thrown, the entire object was stored. + * + * + * + * If you are uploading or accessing [AWS KMS](http://aws.amazon.com/kms/)-encrypted objects, you need to + * specify the correct region of the bucket on your client and configure AWS + * Signature Version 4 for added security. For more information on how to do + * this, see + * http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html# + * specify-signature-version + * + * + * + * Using the file extension, Amazon S3 attempts to determine the correct + * content type and content disposition to use for the object. + * + * + * + * If versioning is enabled for the specified bucket, this operation will + * this operation will never overwrite an existing object with the same key, + * but will keep the existing object as an older version until that version + * is explicitly deleted. + * + * + * + * If versioning is not enabled, this operation will overwrite an existing + * object with the same key; Amazon S3 will store the last write request. + * Amazon S3 does not provide object locking. If Amazon S3 receives multiple + * write requests for the same object nearly simultaneously, all of the + * objects might be stored. However, a single object will be stored with the + * final write request. + * + * + * + * When specifying a location constraint when creating a bucket, all objects + * added to the bucket are stored in the bucket's region. For example, if + * specifying a Europe (EU) region constraint for a bucket, all of that + * bucket's objects are stored in EU region. + * + * + * + * The specified bucket must already exist and the caller must have + * permission to the bucket to upload an object. + * + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param key The key under which to store the specified file. + * @param imageFile The file containing the data to be uploaded to Amazon S3. + * @return A [PutObjectResult] object containing the information + * returned by Amazon S3 for the newly created object. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.putObject + */ actual suspend fun putObject( bucketName: String, key: String, @@ -399,6 +489,63 @@ actual class AwsS3 actual constructor( contentMd5 = result.contentMd5, ) } + + /** + * + * + * Returns a list of summary information about the objects in the specified + * buckets. List results are *always* returned in lexicographic + * (alphabetical) order. + * + * + * + * Because buckets can contain a virtually unlimited number of keys, the + * complete results of a list query can be extremely large. To manage large + * result sets, Amazon S3 uses pagination to split them into multiple + * responses. + * + * + * The total number of keys in a bucket doesn't substantially affect list + * performance. + * + * + * @param bucketName The name of the Amazon S3 bucket to list. + * @return A listing of the objects in the specified bucket, along with any + * other associated information, such as common prefixes (if a + * delimiter was specified), the original request parameters, etc. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listObjects + */ + actual suspend fun listObjects(bucketName: String): ListObjectsResult { + val result = client.listObjectsV2(bucketName) + return result.toListObjectsResult() + } + + actual class Builder { + private var accessKey: String = "" + private var secretKey: String = "" + private var endpoint: String = "" + + actual fun accessKey(accessKey: String): Builder { + this.accessKey = accessKey + return this + } + + actual fun secretKey(secretKey: String): Builder { + this.secretKey = secretKey + return this + } + + actual fun setEndpoint(endpoint: String): Builder { + this.endpoint = endpoint + return this + } + + actual fun build(): AWSS3 { + return AWSS3(accessKey, secretKey, endpoint) + } + } } fun com.amazonaws.services.s3.model.Bucket.toBucket(): Bucket { @@ -406,4 +553,17 @@ fun com.amazonaws.services.s3.model.Bucket.toBucket(): Bucket { name = name, creationDate = creationDate?.let { Instant.fromEpochMilliseconds(it.time) } ) +} + +fun ListObjectsV2Result.toListObjectsResult(): ListObjectsResult { + return ListObjectsResult( + name = bucketName, + keyCount = keyCount, + commonPrefixes = commonPrefixes, + prefix = prefix, + maxKeys = maxKeys, + nextContinuationToken = nextContinuationToken, + delimiter = delimiter, + startAfter = startAfter + ) } \ No newline at end of file diff --git a/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt b/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt index a8cc962..cd45bc0 100644 --- a/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt +++ b/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import android.content.ContentResolver diff --git a/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt b/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt index b4b0a2a..44b26ec 100644 --- a/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt +++ b/aws-s3/src/androidMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3.util import com.estivensh4.aws_s3.HttpMethod diff --git a/aws-s3/src/androidUnitTest/kotlin/com/estivensh4/aws_s3/AwsS3Test.kt b/aws-s3/src/androidUnitTest/kotlin/com/estivensh4/aws_s3/AwsS3Test.kt index bb8c97f..74e8f23 100644 --- a/aws-s3/src/androidUnitTest/kotlin/com/estivensh4/aws_s3/AwsS3Test.kt +++ b/aws-s3/src/androidUnitTest/kotlin/com/estivensh4/aws_s3/AwsS3Test.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import io.kotest.core.spec.style.FunSpec diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt similarity index 61% rename from aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt rename to aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt index 916310f..1e6afab 100644 --- a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt @@ -1,15 +1,17 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import com.estivensh4.aws_kmp.AwsException -expect class AwsS3 private constructor( +expect class AWSS3 private constructor( accessKey: String, secretKey: String, endpoint: String ) { - val endpointAWS: String - /** * * @@ -50,14 +52,14 @@ expect class AwsS3 private constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @return A pre-signed URL which expires at the specified time, and can be * used to allow anyone to download the specified object from S3, * without exposing the owner's AWS secret access key. - * @throws AmazonClientException If there were any problems pre-signing the + * @throws AwsException If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ fun generatePresignedUrl(bucketName: String, key: String, expirationInSeconds: Long): String? @@ -101,7 +103,7 @@ expect class AwsS3 private constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @param method The HTTP method verb to use for this URL * @return A pre-signed URL which expires at the specified time, and can be @@ -109,8 +111,8 @@ expect class AwsS3 private constructor( * without exposing the owner's AWS secret access key. * @throws AwsException If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ fun generatePresignedUrl( bucketName: String, @@ -168,10 +170,10 @@ expect class AwsS3 private constructor( * @return A pre-signed URL that can be used to access an Amazon S3 resource * without requiring the user of the URL to know the account's AWS * security credentials. - * @throws AmazonS3Exception If there were any problems pre-signing the + * @throws AwsException If there were any problems pre-signing the * request for the Amazon S3 resource. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ fun generatePresignedUrl(generatePresignedUrlRequest: GeneratePresignedUrlRequest): String? @@ -179,7 +181,7 @@ expect class AwsS3 private constructor( * * * Creates a new Amazon S3 bucket with the specified name in the default - * (US) region, [Region.US_Standard]. + * (US) region. * * * @@ -242,20 +244,164 @@ expect class AwsS3 private constructor( */ suspend fun createBucket(bucketName: String): Bucket + /** + * + * + * Returns a list of all Amazon S3 buckets that the authenticated sender of + * the request owns. + * + * + * + * Users must authenticate with a valid AWS Access Key ID that is registered + * with Amazon S3. Anonymous requests cannot list buckets, and users cannot + * list buckets that they did not create. + * + * + * @return A list of all of the Amazon S3 buckets owned by the authenticated + * sender of the request. + * @throws AWSS3 If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listBuckets + */ suspend fun listBuckets(): List + /** + * + * + * Deletes the specified bucket. All objects (and all object versions, if + * versioning was ever enabled) in the bucket must be deleted before the + * bucket itself can be deleted. + * + * + * + * Only the owner of a bucket can delete it, regardless of the bucket's + * access control policy. + * + * + * @param bucketName The name of the bucket to delete. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.deleteBucket + */ suspend fun deleteBucket(bucketName: String) + /** + * Deletes multiple objects in a single bucket from S3. + * + * + * In some cases, some objects will be successfully deleted, while some + * attempts will cause an error. If any object in the request cannot be + * deleted, this method throws a [AwsException] with + * details of the error. + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param keys The request object containing all options for + * deleting multiple objects. + * @throws AwsException If any errors occurred in Amazon S3 while + * processing the request. + */ suspend fun deleteObjects(bucketName: String, vararg keys: String): DeleteObjectResult + /** + * + * + * Uploads the specified file to Amazon S3 under the specified bucket and + * key name. + * + * + * + * Amazon S3 never stores partial objects; if during this call an exception + * wasn't thrown, the entire object was stored. + * + * + * + * If you are uploading or accessing [AWS KMS](http://aws.amazon.com/kms/)-encrypted objects, you need to + * specify the correct region of the bucket on your client and configure AWS + * Signature Version 4 for added security. For more information on how to do + * this, see + * http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html# + * specify-signature-version + * + * + * + * Using the file extension, Amazon S3 attempts to determine the correct + * content type and content disposition to use for the object. + * + * + * + * If versioning is enabled for the specified bucket, this operation will + * this operation will never overwrite an existing object with the same key, + * but will keep the existing object as an older version until that version + * is explicitly deleted. + * + * + * + * If versioning is not enabled, this operation will overwrite an existing + * object with the same key; Amazon S3 will store the last write request. + * Amazon S3 does not provide object locking. If Amazon S3 receives multiple + * write requests for the same object nearly simultaneously, all of the + * objects might be stored. However, a single object will be stored with the + * final write request. + * + * + * + * When specifying a location constraint when creating a bucket, all objects + * added to the bucket are stored in the bucket's region. For example, if + * specifying a Europe (EU) region constraint for a bucket, all of that + * bucket's objects are stored in EU region. + * + * + * + * The specified bucket must already exist and the caller must have + * permission to the bucket to upload an object. + * + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param key The key under which to store the specified file. + * @param imageFile The file containing the data to be uploaded to Amazon S3. + * @return A [PutObjectResult] object containing the information + * returned by Amazon S3 for the newly created object. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.putObject + */ suspend fun putObject( bucketName: String, key: String, imageFile: ImageFile ): PutObjectResult + /** + * + * + * Returns a list of summary information about the objects in the specified + * buckets. List results are *always* returned in lexicographic + * (alphabetical) order. + * + * + * + * Because buckets can contain a virtually unlimited number of keys, the + * complete results of a list query can be extremely large. To manage large + * result sets, Amazon S3 uses pagination to split them into multiple + * responses. + * + * + * The total number of keys in a bucket doesn't substantially affect list + * performance. + * + * + * @param bucketName The name of the Amazon S3 bucket to list. + * @return A listing of the objects in the specified bucket, along with any + * other associated information, such as common prefixes (if a + * delimiter was specified), the original request parameters, etc. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listObjects + */ + suspend fun listObjects(bucketName: String): ListObjectsResult + class Builder() { fun accessKey(accessKey: String): Builder fun secretKey(secretKey: String): Builder fun setEndpoint(endpoint: String): Builder - fun build(): AwsS3 + fun build(): AWSS3 } } \ No newline at end of file diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/Bucket.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/Bucket.kt index c0b8736..5d5b119 100644 --- a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/Bucket.kt +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/Bucket.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import kotlinx.datetime.Instant diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/CopyObjectResult.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/CopyObjectResult.kt new file mode 100644 index 0000000..23dda4a --- /dev/null +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/CopyObjectResult.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + +package com.estivensh4.aws_s3 + +import kotlinx.datetime.Instant + +data class CopyObjectResult( + val versionId: String?, + val eTag: String?, + val expirationTime: Instant?, + val sseCustomerKeyMd5: String?, +) diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/DeleteObjectResult.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/DeleteObjectResult.kt index bcbb3b6..ea3fa9c 100644 --- a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/DeleteObjectResult.kt +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/DeleteObjectResult.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 data class DeleteObjectResult( diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/GeneratePresignedUrlRequest.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/GeneratePresignedUrlRequest.kt index a54de1d..479ef10 100644 --- a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/GeneratePresignedUrlRequest.kt +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/GeneratePresignedUrlRequest.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 data class GeneratePresignedUrlRequest( diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/HttpMethod.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/HttpMethod.kt index c539dc7..00102fb 100644 --- a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/HttpMethod.kt +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/HttpMethod.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 enum class HttpMethod { diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt index 58a1b20..2419662 100644 --- a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 expect class ImageFile diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/ListObjectsResult.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/ListObjectsResult.kt new file mode 100644 index 0000000..a6b1185 --- /dev/null +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/ListObjectsResult.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + +package com.estivensh4.aws_s3 + +data class ListObjectsResult( + val name: String?, + val keyCount: Int?, + val commonPrefixes: List, + val maxKeys: Int?, + val prefix: String?, + val nextContinuationToken: String?, + val delimiter: String?, + val startAfter: String? +) diff --git a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/PutObjectResult.kt b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/PutObjectResult.kt index 9737a31..37c34b0 100644 --- a/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/PutObjectResult.kt +++ b/aws-s3/src/commonMain/kotlin/com/estivensh4/aws_s3/PutObjectResult.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import kotlinx.datetime.Instant diff --git a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt similarity index 69% rename from aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt rename to aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt index a80fc8e..dc7fd87 100644 --- a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt +++ b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt @@ -1,6 +1,9 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 -import cocoapods.AWSCore.AWSServiceConfiguration import cocoapods.AWSS3.AWSRequest import cocoapods.AWSS3.AWSS3 import cocoapods.AWSS3.AWSS3Bucket @@ -9,6 +12,8 @@ import cocoapods.AWSS3.AWSS3DeleteBucketRequest import cocoapods.AWSS3.AWSS3DeleteObjectsRequest import cocoapods.AWSS3.AWSS3DeletedObject import cocoapods.AWSS3.AWSS3GetPreSignedURLRequest +import cocoapods.AWSS3.AWSS3ListObjectsV2Output +import cocoapods.AWSS3.AWSS3ListObjectsV2Request import cocoapods.AWSS3.AWSS3PreSignedURLBuilder import cocoapods.AWSS3.AWSS3PutObjectRequest import cocoapods.AWSS3.AWSS3Remove @@ -24,15 +29,12 @@ import platform.Foundation.NSURL import platform.Foundation.dateByAddingTimeInterval @OptIn(ExperimentalForeignApi::class) -actual class AwsS3 actual constructor( +actual class AWSS3 actual constructor( private val accessKey: String, private val secretKey: String, private val endpoint: String ) { - actual val endpointAWS get() = "" - private var awsServiceConfiguration: AWSServiceConfiguration? = null - private val client: AWSS3 get() = AWSS3.defaultS3() @@ -76,14 +78,14 @@ actual class AwsS3 actual constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @return A pre-signed URL which expires at the specified time, and can be * used to allow anyone to download the specified object from S3, * without exposing the owner's AWS secret access key. - * @throws AmazonClientException If there were any problems pre-signing the + * @throws AwsException If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ @OptIn(ExperimentalForeignApi::class) actual fun generatePresignedUrl( @@ -149,7 +151,7 @@ actual class AwsS3 actual constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @param method The HTTP method verb to use for this URL * @return A pre-signed URL which expires at the specified time, and can be @@ -157,8 +159,7 @@ actual class AwsS3 actual constructor( * without exposing the owner's AWS secret access key. * @throws AwsException If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ @OptIn(ExperimentalForeignApi::class) actual fun generatePresignedUrl( @@ -234,10 +235,9 @@ actual class AwsS3 actual constructor( * @return A pre-signed URL that can be used to access an Amazon S3 resource * without requiring the user of the URL to know the account's AWS * security credentials. - * @throws AmazonS3Exception If there were any problems pre-signing the + * @throws AwsException If there were any problems pre-signing the * request for the Amazon S3 resource. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ @OptIn(ExperimentalForeignApi::class) actual fun generatePresignedUrl( @@ -258,36 +258,11 @@ actual class AwsS3 actual constructor( } } - actual class Builder { - private var accessKey: String = "" - private var secretKey: String = "" - private var endpoint: String = "" - - actual fun accessKey(accessKey: String): Builder { - this.accessKey = accessKey - return this - } - - actual fun secretKey(secretKey: String): Builder { - this.secretKey = secretKey - return this - } - - actual fun setEndpoint(endpoint: String): Builder { - this.endpoint = endpoint - return this - } - - actual fun build(): AwsS3 { - return AwsS3(accessKey, secretKey, endpoint) - } - } - /** * * * Creates a new Amazon S3 bucket with the specified name in the default - * (US) region, [Region.US_Standard]. + * (US) region. * * * @@ -358,6 +333,25 @@ actual class AwsS3 actual constructor( return result.toBucket() } + /** + * + * + * Returns a list of all Amazon S3 buckets that the authenticated sender of + * the request owns. + * + * + * + * Users must authenticate with a valid AWS Access Key ID that is registered + * with Amazon S3. Anonymous requests cannot list buckets, and users cannot + * list buckets that they did not create. + * + * + * @return A list of all of the Amazon S3 buckets owned by the authenticated + * sender of the request. + * @throws AWSS3 If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listBuckets + */ @Suppress("UNCHECKED_CAST") actual suspend fun listBuckets(): List { val request = AWSRequest() @@ -366,12 +360,45 @@ actual class AwsS3 actual constructor( return result.map { it.toBucket() } } + /** + * + * + * Deletes the specified bucket. All objects (and all object versions, if + * versioning was ever enabled) in the bucket must be deleted before the + * bucket itself can be deleted. + * + * + * + * Only the owner of a bucket can delete it, regardless of the bucket's + * access control policy. + * + * + * @param bucketName The name of the bucket to delete. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.deleteBucket + */ actual suspend fun deleteBucket(bucketName: String) { val request = AWSS3DeleteBucketRequest() request.bucket = bucketName await { client.deleteBucket(request, it) } } + /** + * Deletes multiple objects in a single bucket from S3. + * + * + * In some cases, some objects will be successfully deleted, while some + * attempts will cause an error. If any object in the request cannot be + * deleted, this method throws a [AwsException] with + * details of the error. + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param keys The request object containing all options for + * deleting multiple objects. + * @throws AwsException If any errors occurred in Amazon S3 while + * processing the request. + */ @Suppress("UNCHECKED_CAST") actual suspend fun deleteObjects(bucketName: String, vararg keys: String): DeleteObjectResult { val deleteObjectsRequest = AWSS3DeleteObjectsRequest() @@ -394,6 +421,69 @@ actual class AwsS3 actual constructor( ) } + /** + * + * + * Uploads the specified file to Amazon S3 under the specified bucket and + * key name. + * + * + * + * Amazon S3 never stores partial objects; if during this call an exception + * wasn't thrown, the entire object was stored. + * + * + * + * If you are uploading or accessing [AWS KMS](http://aws.amazon.com/kms/)-encrypted objects, you need to + * specify the correct region of the bucket on your client and configure AWS + * Signature Version 4 for added security. For more information on how to do + * this, see + * http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html# + * specify-signature-version + * + * + * + * Using the file extension, Amazon S3 attempts to determine the correct + * content type and content disposition to use for the object. + * + * + * + * If versioning is enabled for the specified bucket, this operation will + * this operation will never overwrite an existing object with the same key, + * but will keep the existing object as an older version until that version + * is explicitly deleted. + * + * + * + * If versioning is not enabled, this operation will overwrite an existing + * object with the same key; Amazon S3 will store the last write request. + * Amazon S3 does not provide object locking. If Amazon S3 receives multiple + * write requests for the same object nearly simultaneously, all of the + * objects might be stored. However, a single object will be stored with the + * final write request. + * + * + * + * When specifying a location constraint when creating a bucket, all objects + * added to the bucket are stored in the bucket's region. For example, if + * specifying a Europe (EU) region constraint for a bucket, all of that + * bucket's objects are stored in EU region. + * + * + * + * The specified bucket must already exist and the caller must have + * permission to the bucket to upload an object. + * + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param key The key under which to store the specified file. + * @param imageFile The file containing the data to be uploaded to Amazon S3. + * @return A [PutObjectResult] object containing the information + * returned by Amazon S3 for the newly created object. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.putObject + */ actual suspend fun putObject( bucketName: String, key: String, @@ -412,6 +502,66 @@ actual class AwsS3 actual constructor( contentMd5 = result.SSECustomerKeyMD5, ) } + + /** + * + * + * Returns a list of summary information about the objects in the specified + * buckets. List results are *always* returned in lexicographic + * (alphabetical) order. + * + * + * + * Because buckets can contain a virtually unlimited number of keys, the + * complete results of a list query can be extremely large. To manage large + * result sets, Amazon S3 uses pagination to split them into multiple + * responses. + * + * + * The total number of keys in a bucket doesn't substantially affect list + * performance. + * + * + * @param bucketName The name of the Amazon S3 bucket to list. + * @return A listing of the objects in the specified bucket, along with any + * other associated information, such as common prefixes (if a + * delimiter was specified), the original request parameters, etc. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listObjects + */ + actual suspend fun listObjects(bucketName: String): ListObjectsResult { + val request = AWSS3ListObjectsV2Request() + request.bucket = bucketName + + val result = awaitResult { client.listObjectsV2(request, it) } + return result.toListObjectResult() + } + + actual class Builder { + private var accessKey: String = "" + private var secretKey: String = "" + private var endpoint: String = "" + + actual fun accessKey(accessKey: String): Builder { + this.accessKey = accessKey + return this + } + + actual fun secretKey(secretKey: String): Builder { + this.secretKey = secretKey + return this + } + + actual fun setEndpoint(endpoint: String): Builder { + this.endpoint = endpoint + return this + } + + actual fun build(): com.estivensh4.aws_s3.AWSS3 { + return AWSS3(accessKey, secretKey, endpoint) + } + } } @OptIn(ExperimentalForeignApi::class) @@ -422,4 +572,19 @@ fun AWSS3Bucket.toBucket(): Bucket { Instant.fromEpochMilliseconds(it) } ) +} + +@Suppress("UNCHECKED_CAST") +@OptIn(ExperimentalForeignApi::class) +fun AWSS3ListObjectsV2Output.toListObjectResult(): ListObjectsResult { + return ListObjectsResult( + name = name, + keyCount = keyCount?.intValue, + commonPrefixes = commonPrefixes as List, + maxKeys = maxKeys?.intValue, + prefix = prefix, + nextContinuationToken = nextContinuationToken, + delimiter = delimiter, + startAfter = startAfter, + ) } \ No newline at end of file diff --git a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt index fe3a19f..f881231 100644 --- a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt +++ b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/ImageFile.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import kotlinx.cinterop.ExperimentalForeignApi diff --git a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/AWSS3Ext.kt b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/AWSS3Ext.kt index 16eda7b..3fb3573 100644 --- a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/AWSS3Ext.kt +++ b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/AWSS3Ext.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3.util import cocoapods.AWSS3.AWSS3 diff --git a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt index 8b4e001..1b12b8f 100644 --- a/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt +++ b/aws-s3/src/iosMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3.util import cocoapods.AWSS3.AWSHTTPMethod diff --git a/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt b/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt similarity index 68% rename from aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt rename to aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt index c19317a..c323684 100644 --- a/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/AwsS3.kt +++ b/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/AWSS3.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import com.amazonaws.auth.AWSStaticCredentialsProvider @@ -7,13 +11,15 @@ import com.amazonaws.services.s3.AmazonS3 import com.amazonaws.services.s3.AmazonS3ClientBuilder import com.amazonaws.services.s3.model.AmazonS3Exception import com.amazonaws.services.s3.model.DeleteObjectsRequest +import com.amazonaws.services.s3.model.ListObjectsV2Result import com.amazonaws.services.s3.model.ObjectMetadata +import com.estivensh4.aws_kmp.AwsException import com.estivensh4.aws_s3.util.toAWSMethod import kotlinx.datetime.Instant import java.io.FileNotFoundException import java.util.Calendar -actual class AwsS3 actual constructor( +actual class AWSS3 actual constructor( private val accessKey: String, private val secretKey: String, private val endpoint: String @@ -31,9 +37,6 @@ actual class AwsS3 actual constructor( .build() } - actual val endpointAWS: String - get() = endpoint - /** * * @@ -74,14 +77,14 @@ actual class AwsS3 actual constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @return A pre-signed URL which expires at the specified time, and can be * used to allow anyone to download the specified object from S3, * without exposing the owner's AWS secret access key. * @throws Exception If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ actual fun generatePresignedUrl( bucketName: String, @@ -143,7 +146,7 @@ actual class AwsS3 actual constructor( * @param bucketName The name of the bucket containing the desired object. * @param key The key in the specified bucket under which the desired object * is stored. - * @param expiration The time at which the returned pre-signed URL will + * @param expirationInSeconds The time at which the returned pre-signed URL will * expire. * @param method The HTTP method verb to use for this URL * @return A pre-signed URL which expires at the specified time, and can be @@ -151,8 +154,8 @@ actual class AwsS3 actual constructor( * without exposing the owner's AWS secret access key. * @throws Exception If there were any problems pre-signing the * request for the specified S3 object. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ actual fun generatePresignedUrl( bucketName: String, @@ -232,8 +235,8 @@ actual class AwsS3 actual constructor( * security credentials. * @throws AmazonS3Exception If there were any problems pre-signing the * request for the Amazon S3 resource. - * @see AwsS3.generatePresignedUrl - * @see AwsS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl + * @see AWSS3.generatePresignedUrl */ actual fun generatePresignedUrl(generatePresignedUrlRequest: GeneratePresignedUrlRequest): String? { return try { @@ -274,8 +277,8 @@ actual class AwsS3 actual constructor( return this } - actual fun build(): AwsS3 { - return AwsS3(accessKey, secretKey, endpoint) + actual fun build(): AWSS3 { + return AWSS3(accessKey, secretKey, endpoint) } } @@ -283,7 +286,7 @@ actual class AwsS3 actual constructor( * * * Creates a new Amazon S3 bucket with the specified name in the default - * (US) region, [Region.US_Standard]. + * (US) region. * * * @@ -349,14 +352,66 @@ actual class AwsS3 actual constructor( return result.toBucket() } + /** + * + * + * Returns a list of all Amazon S3 buckets that the authenticated sender of + * the request owns. + * + * + * + * Users must authenticate with a valid AWS Access Key ID that is registered + * with Amazon S3. Anonymous requests cannot list buckets, and users cannot + * list buckets that they did not create. + * + * + * @return A list of all of the Amazon S3 buckets owned by the authenticated + * sender of the request. + * @throws AWSS3 If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listBuckets + */ actual suspend fun listBuckets(): List { return client.listBuckets().map { it.toBucket() } } + /** + * + * + * Deletes the specified bucket. All objects (and all object versions, if + * versioning was ever enabled) in the bucket must be deleted before the + * bucket itself can be deleted. + * + * + * + * Only the owner of a bucket can delete it, regardless of the bucket's + * access control policy. + * + * + * @param bucketName The name of the bucket to delete. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.deleteBucket + */ actual suspend fun deleteBucket(bucketName: String) { client.deleteBucket(bucketName) } + /** + * Deletes multiple objects in a single bucket from S3. + * + * + * In some cases, some objects will be successfully deleted, while some + * attempts will cause an error. If any object in the request cannot be + * deleted, this method throws a [AwsException] with + * details of the error. + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param keys The request object containing all options for + * deleting multiple objects. + * @throws AwsException If any errors occurred in Amazon S3 while + * processing the request. + */ actual suspend fun deleteObjects( bucketName: String, vararg keys: String @@ -379,6 +434,69 @@ actual class AwsS3 actual constructor( ) } + /** + * + * + * Uploads the specified file to Amazon S3 under the specified bucket and + * key name. + * + * + * + * Amazon S3 never stores partial objects; if during this call an exception + * wasn't thrown, the entire object was stored. + * + * + * + * If you are uploading or accessing [AWS KMS](http://aws.amazon.com/kms/)-encrypted objects, you need to + * specify the correct region of the bucket on your client and configure AWS + * Signature Version 4 for added security. For more information on how to do + * this, see + * http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html# + * specify-signature-version + * + * + * + * Using the file extension, Amazon S3 attempts to determine the correct + * content type and content disposition to use for the object. + * + * + * + * If versioning is enabled for the specified bucket, this operation will + * this operation will never overwrite an existing object with the same key, + * but will keep the existing object as an older version until that version + * is explicitly deleted. + * + * + * + * If versioning is not enabled, this operation will overwrite an existing + * object with the same key; Amazon S3 will store the last write request. + * Amazon S3 does not provide object locking. If Amazon S3 receives multiple + * write requests for the same object nearly simultaneously, all of the + * objects might be stored. However, a single object will be stored with the + * final write request. + * + * + * + * When specifying a location constraint when creating a bucket, all objects + * added to the bucket are stored in the bucket's region. For example, if + * specifying a Europe (EU) region constraint for a bucket, all of that + * bucket's objects are stored in EU region. + * + * + * + * The specified bucket must already exist and the caller must have + * permission to the bucket to upload an object. + * + * + * @param bucketName The name of an existing bucket, to which you have permission. + * @param key The key under which to store the specified file. + * @param imageFile The file containing the data to be uploaded to Amazon S3. + * @return A [PutObjectResult] object containing the information + * returned by Amazon S3 for the newly created object. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.putObject + */ actual suspend fun putObject( bucketName: String, key: String, @@ -397,6 +515,38 @@ actual class AwsS3 actual constructor( contentMd5 = result.contentMd5, ) } + + /** + * + * + * Returns a list of summary information about the objects in the specified + * buckets. List results are *always* returned in lexicographic + * (alphabetical) order. + * + * + * + * Because buckets can contain a virtually unlimited number of keys, the + * complete results of a list query can be extremely large. To manage large + * result sets, Amazon S3 uses pagination to split them into multiple + * responses. + * + * + * The total number of keys in a bucket doesn't substantially affect list + * performance. + * + * + * @param bucketName The name of the Amazon S3 bucket to list. + * @return A listing of the objects in the specified bucket, along with any + * other associated information, such as common prefixes (if a + * delimiter was specified), the original request parameters, etc. + * @throws AwsException If any errors are encountered in the client + * while making the request or handling the response. + * @see AWSS3.listObjects + */ + actual suspend fun listObjects(bucketName: String): ListObjectsResult { + val result = client.listObjectsV2(bucketName) + return result.toListObjectsResult() + } } fun com.amazonaws.services.s3.model.Bucket.toBucket(): Bucket { @@ -404,4 +554,17 @@ fun com.amazonaws.services.s3.model.Bucket.toBucket(): Bucket { name = name, creationDate = creationDate?.let { Instant.fromEpochMilliseconds(it.time) } ) +} + +fun ListObjectsV2Result.toListObjectsResult(): ListObjectsResult { + return ListObjectsResult( + name = bucketName, + keyCount = keyCount, + commonPrefixes = commonPrefixes, + prefix = prefix, + maxKeys = maxKeys, + nextContinuationToken = nextContinuationToken, + delimiter = delimiter, + startAfter = startAfter + ) } \ No newline at end of file diff --git a/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/ImageFIle.kt b/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/ImageFIle.kt index b93c63d..5e57f1b 100644 --- a/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/ImageFIle.kt +++ b/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/ImageFIle.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3 import java.io.File diff --git a/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt b/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt index b4b0a2a..44b26ec 100644 --- a/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt +++ b/aws-s3/src/jvmMain/kotlin/com/estivensh4/aws_s3/util/HttpMethodExt.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + package com.estivensh4.aws_s3.util import com.estivensh4.aws_s3.HttpMethod diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 8415479..f77ac00 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + plugins { `kotlin-dsl` } diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index f519643..b2ec1cb 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + dependencyResolutionManagement { repositories { mavenCentral() diff --git a/build-logic/src/main/kotlin/javadoc-stub-convention.gradle.kts b/build-logic/src/main/kotlin/javadoc-stub-convention.gradle.kts index e6458c3..145aed8 100644 --- a/build-logic/src/main/kotlin/javadoc-stub-convention.gradle.kts +++ b/build-logic/src/main/kotlin/javadoc-stub-convention.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + plugins { id("org.gradle.maven-publish") } diff --git a/build-logic/src/main/kotlin/publication-convention.gradle.kts b/build-logic/src/main/kotlin/publication-convention.gradle.kts index 882f59d..41c6830 100644 --- a/build-logic/src/main/kotlin/publication-convention.gradle.kts +++ b/build-logic/src/main/kotlin/publication-convention.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + plugins { id("org.gradle.maven-publish") } diff --git a/build.gradle.kts b/build.gradle.kts index e3fddb2..f42d7fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + plugins { //trick: for the same plugin versions in all sub-modules alias(libs.plugins.androidLibrary).apply(false) diff --git a/doc/Writerside/cfg/buildprofiles.xml b/doc/Writerside/cfg/buildprofiles.xml index ddd074e..c8a9bd1 100644 --- a/doc/Writerside/cfg/buildprofiles.xml +++ b/doc/Writerside/cfg/buildprofiles.xml @@ -1,4 +1,8 @@ + + diff --git a/doc/Writerside/cfg/platforms.xml b/doc/Writerside/cfg/platforms.xml index c6e9a52..2c7e9ab 100644 --- a/doc/Writerside/cfg/platforms.xml +++ b/doc/Writerside/cfg/platforms.xml @@ -1,4 +1,8 @@ + + keymap.xml diff --git a/doc/Writerside/keymap.xml b/doc/Writerside/keymap.xml index a5252f6..e9da133 100644 --- a/doc/Writerside/keymap.xml +++ b/doc/Writerside/keymap.xml @@ -1,4 +1,8 @@ + + Go to File... diff --git a/doc/Writerside/redirection-rules.xml b/doc/Writerside/redirection-rules.xml index 780b93d..87b83be 100644 --- a/doc/Writerside/redirection-rules.xml +++ b/doc/Writerside/redirection-rules.xml @@ -1,4 +1,8 @@ + + + diff --git a/example/androidapp/src/main/res/drawable/ic_launcher_background.xml b/example/androidapp/src/main/res/drawable/ic_launcher_background.xml index 07d5da9..51f2f8b 100644 --- a/example/androidapp/src/main/res/drawable/ic_launcher_background.xml +++ b/example/androidapp/src/main/res/drawable/ic_launcher_background.xml @@ -1,4 +1,8 @@ + + + + + diff --git a/example/androidapp/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/example/androidapp/src/main/res/mipmap-anydpi/ic_launcher_round.xml index 6f3b755..02780da 100644 --- a/example/androidapp/src/main/res/mipmap-anydpi/ic_launcher_round.xml +++ b/example/androidapp/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -1,4 +1,8 @@ + + diff --git a/example/androidapp/src/main/res/values/colors.xml b/example/androidapp/src/main/res/values/colors.xml index f8c6127..6e00d22 100644 --- a/example/androidapp/src/main/res/values/colors.xml +++ b/example/androidapp/src/main/res/values/colors.xml @@ -1,4 +1,8 @@ + + #FFBB86FC #FF6200EE diff --git a/example/androidapp/src/main/res/values/strings.xml b/example/androidapp/src/main/res/values/strings.xml index a1d5dd3..35513d1 100644 --- a/example/androidapp/src/main/res/values/strings.xml +++ b/example/androidapp/src/main/res/values/strings.xml @@ -1,3 +1,7 @@ + + androidApp \ No newline at end of file diff --git a/example/androidapp/src/main/res/values/themes.xml b/example/androidapp/src/main/res/values/themes.xml index d9878e2..c80e47e 100644 --- a/example/androidapp/src/main/res/values/themes.xml +++ b/example/androidapp/src/main/res/values/themes.xml @@ -1,4 +1,8 @@ + + diff --git a/example/gradle.properties b/example/gradle.properties index aa4911e..4ed386a 100755 --- a/example/gradle.properties +++ b/example/gradle.properties @@ -1,3 +1,7 @@ +# +# Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. +# + #Gradle org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" #Kotlin diff --git a/example/gradle/wrapper/gradle-wrapper.properties b/example/gradle/wrapper/gradle-wrapper.properties index a1df253..c109e9b 100644 --- a/example/gradle/wrapper/gradle-wrapper.properties +++ b/example/gradle/wrapper/gradle-wrapper.properties @@ -1,3 +1,7 @@ +# +# Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. +# + #Thu Nov 09 01:52:10 COT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists diff --git a/example/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh b/example/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh index eb44f30..fd3db11 100755 --- a/example/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh +++ b/example/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh @@ -1,4 +1,8 @@ #!/bin/sh +# +# Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. +# + set -e set -u set -o pipefail diff --git a/example/iosApp/Pods/Target Support Files/shared/shared-copy-dsyms.sh b/example/iosApp/Pods/Target Support Files/shared/shared-copy-dsyms.sh index d5e78c9..85165e6 100755 --- a/example/iosApp/Pods/Target Support Files/shared/shared-copy-dsyms.sh +++ b/example/iosApp/Pods/Target Support Files/shared/shared-copy-dsyms.sh @@ -1,4 +1,8 @@ #!/bin/sh +# +# Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. +# + set -e set -u set -o pipefail diff --git a/example/wearapp/src/main/AndroidManifest.xml b/example/wearapp/src/main/AndroidManifest.xml index 5dd861a..8b7d42f 100644 --- a/example/wearapp/src/main/AndroidManifest.xml +++ b/example/wearapp/src/main/AndroidManifest.xml @@ -1,4 +1,8 @@ + + diff --git a/example/wearapp/src/main/res/drawable/ic_android_black_24dp.xml b/example/wearapp/src/main/res/drawable/ic_android_black_24dp.xml index fe51230..a9f6ae7 100644 --- a/example/wearapp/src/main/res/drawable/ic_android_black_24dp.xml +++ b/example/wearapp/src/main/res/drawable/ic_android_black_24dp.xml @@ -1,3 +1,7 @@ + + diff --git a/example/wearapp/src/main/res/values-round/strings.xml b/example/wearapp/src/main/res/values-round/strings.xml index 42f1229..cead992 100644 --- a/example/wearapp/src/main/res/values-round/strings.xml +++ b/example/wearapp/src/main/res/values-round/strings.xml @@ -1,3 +1,7 @@ + + From the Round world,\nHello, %1$s! \ No newline at end of file diff --git a/example/wearapp/src/main/res/values/strings.xml b/example/wearapp/src/main/res/values/strings.xml index 8509d27..a048fee 100644 --- a/example/wearapp/src/main/res/values/strings.xml +++ b/example/wearapp/src/main/res/values/strings.xml @@ -1,3 +1,7 @@ + + wearApp diff --git a/gradle.properties b/gradle.properties index a0bf388..a355d79 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,7 @@ +# +# Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. +# + #Gradle org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" diff --git a/gradle/aws.versions.toml b/gradle/aws.versions.toml index 90ada64..64579b0 100644 --- a/gradle/aws.versions.toml +++ b/gradle/aws.versions.toml @@ -1,5 +1,5 @@ [versions] -aws = "0.4.4" +aws = "0.5.0" [libraries] aws-common = { module = "io.github.estivensh4:aws-common", version.ref = "aws" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a1df253..c109e9b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,3 +1,7 @@ +# +# Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. +# + #Thu Nov 09 01:52:10 COT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index f5227de..ae435ce 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,7 @@ +/* + * Copyright 2023 estiven. Use of this source code is governed by the Apache 2.0 license. + */ + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { repositories {