I am trying to set up my S3 bucket to be able to upload an image and that image have a url from AWS. Currently I use the Amplify package to turn the image into an object and then upload it to AWS with a certain name key for that image. Here is the function and link to the package:
https://github.com/aws-amplify/amplify-swift
let profileImage = imageSelected
let profileImageData = profileImage.jpegData(compressionQuality: 1)!
Amplify.Storage.uploadData(key: imageKey, data: profileImageData) { result in
switch result {
case .success(let uploadedData):
print(uploadedData)
case .failure(let error):
print(error)
}
}
My question is, is it possible to make a function that uploads to my bucket and create a AWS url to that image so that I can just save the url in my other database without having to use pods. If this is possible could I have help creating that function? Please let me know if I need to do more explaining.
Thank you!
Related
is there a good function to download images from AWS S3 bucket? I have an access key and a secret key for permisson. The URL is thru a different database accessible. I also already imported AWSS3 and AWSCore.
I have already found a upload function:
func uploadFile(withImage image: UIImage) {
let access = "access_key"
let secret = "secret_key"
let credentials = AWSStaticCredentialsProvider(accessKey: access, secretKey: secret)
let configuration = AWSServiceConfiguration(region: AWSRegionType.EUCentral1, credentialsProvider: credentials)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let s3BucketName = "bucket_name"
let compressedImage = image.resizedImage(newSize: CGSize(width: 80, height: 80))
let data: Data = compressedImage.pngData()!
let remoteName = generateRandomStringWithLength(length: 12)+"."+data.format
print("REMOTE NAME : ",remoteName)
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { (task, progress) in
DispatchQueue.main.async(execute: {
// Update a progress bar
})
}
var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
completionHandler = { (task, error) -> Void in
DispatchQueue.main.async(execute: {
// Do something e.g. Alert a user for transfer completion.
// On failed uploads, `error` contains the error object.
})
}
let transferUtility = AWSS3TransferUtility.default()
transferUtility.uploadData(data, bucket: s3BucketName, key: remoteName, contentType: "image/"+data.format, expression: expression, completionHandler: completionHandler).continueWith { (task) -> Any? in
if let error = task.error {
print("Error : \(error.localizedDescription)")
}
if task.result != nil {
let url = AWSS3.default().configuration.endpoint.url
let publicURL = url?.appendingPathComponent(s3BucketName).appendingPathComponent(remoteName)
if let absoluteString = publicURL?.absoluteString {
// Set image with URL
print("Image URL : ",absoluteString)
}
}
return nil
}
}
I would not recommend to download files directly from S3 using an access and secret key.
I'd propose you do the following:
Make sure the bucket is as "private" as can be.
Have an API with authentication and authorisation (AWS API Gateway) that checks if the user is authenticated and permitted to download the S3 object.
Generate a pre-signed download URL with that is only valid for a short period of time (15-60 minutes).
Return that pre-signed download URL to your app through the API.
Use the URL within your app to download the S3 object.
This way you don't have to ship username and password in your app and the bucket is closed off to the "outside" reducing the risk of accidental information leakage.
Why I wouldn't recommend using the access key and secret key:
This is a potential security issue. People that reverse engineer the app could gain access to those "static" keys and depending on the underlying IAM role do all sorts of harm. But even if you have proper IAM roles with very limited access, essentially shipping a username and password with your app is not a good idea under any circumstance. How would you "rotate" the secret if something bad happens etc.
I currently have a view controller where the user uploads an image and that image is stored in the Firebase Storage, with folders of their user id and in there their uploaded image. I want to fetch that image's url and display it on the view controller. The question I have is, do i need to store that uploaded image's download url into the realtime database ie; Users - > User Id - > Media -> Image Title -> Download URL? Or is there a way for me to refer an image view to Firebase storage and accordingly into the User Id's folder containing all the images and display this? Would appreciate it a lot if someone could help me out. Thank you!
This is my code:
let imageName = (Auth.auth().currentUser?.uid)!+"/\(imageTitle.text!)"
let imageReference = Storage.storage().reference().child(MyKeys.imagesFolder).child(imageName)
imageReference.putData(data, metadata: nil) { (metadata, err) in
if let err = err {
print ("Error")
return
}
imageReference.downloadURL(completion: { (url, err) in
if let err = err {
print ("Error")
return
}
guard let url = url else {
print ("Error")
return
}
let dataReference = Firestore.firestore().collection(MyKeys.imagesCollection).document()
let documentUid = dataReference.documentID
let urlString = url.absoluteString
let data = [
MyKeys.uid:documentUid,
MyKeys.imageUrl: urlString,
MyKeys.imageTitle: self.imageTitle.text!,
] as [String : Any]
dataReference.setData(data) { (err) in
if let err = err {
print ("Error")
return
}
}
})
}
There are two ways to read the data from Storage:
Through the Firebase SDK by calling getData or write on a reference, as shown in the documentation on downloading data.
Through a download URL for the reference, which provides public read-only access.
To call getData, write or to get a download URL, you need to have a reference to the file as shown in creating a reference.
Since you store the files under the UID of the user, you can always create a reference to any for for a user if you known their UID. So you can always perform one of the two methods above to read the data for the file, even if you didn't store the download URL.
That said, it is fairly common to store the download URL in a database, as it means you can then treat it like any other image URL. By doing so the rest of your code won't have to know anything about Cloud Storage.
So: it can work without storing the download URL in the database, but it's also fine (and common) if you do store the download URL. The choice is really up to you.
I'm using AWS S3 Storage with Amplify and to avoid multiple uploads of the same file i want to check if the file already exists.
Currently by getting the download url via Amplify but it also generates an url if the file doesn't exists. I was hoping it returns an error:
_ = Amplify.Storage.getURL(key: "myKey") { event in
switch event {
case let .success(url):
print("Completed: \(url)")
case let .failure(storageError):
print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)")
}
}
Are there any other ways to check if files exists in amplify?
Without downloading it of course.
The whole point is to save traffic.
It looks like you might be able to do something similar with Amplify.Storage.list as shown here https://docs.amplify.aws/lib/storage/list/q/platform/ios
_ = Amplify.Storage.list { event in
switch event {
case .success(let listResult):
let keys = listResult.items.map { $0.key }
if !keys.contains("myKey") {
// upload unique file
}
case .failure(let error):
print("Failed: \(error.errorDescription).")
}
}
I am saving a list of file names/paths so I can load the image at a later time to upload it.
When the user selects the images from the camera roll, I get back this
file:///Users/admin/Library/Developer/CoreSimulator/Devices/B31CE61D-FB46-41F0-B254-B66B9335E1E4/data/Media/DCIM/100APPLE/IMG_0005.JPG
But when I try to load up the image,
if let image = UIImage(named: filepath) {
imageView.image = image
}
It doesn't load.
How do I load an image from a filepath?
The code I use to get the file path
func getURL(ofPhotoWith mPhasset: PHAsset, completionHandler : #escaping ((_ responseURL : URL?) -> Void)) {
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
completionHandler(contentEditingInput!.fullSizeImageURL)
})
}
func add(images: [PHAsset]) {
for image in images {
getURL(ofPhotoWith: image) { (imgURL) in
if let imgURL = imgURL {
print ("ImageURL: \(imgURL.absoluteString)")
}
}
}
}
I am saving a list of file names/paths so I can load the image at a later time to upload it.
PHContentEditingInput is the wrong tool for that job. As the names of that class and the functions you're using to get one suggest, it's for content editing — tasks like applying a filter to an asset in the library.
When PHContentEditingInput gives you a file URL, it's granting you temporary access to that file. PhotoKit makes no guarantee that the asset in question will always be backed by a file at that URL, and even if it is, PhotoKit revokes temporary access to that URL when the owning PHContentEditingInput is deallocated.
A user's Photos library isn't a directory full of image files — it's a database, where each asset can have data resources stored in one or more files, which might or might not even be in local storage at all times. If you want to upload assets to an external service and preserve all the original data, you need an API that's meant for getting data resources. PhotoKit gives you two choices for that:
If you want just some image representation of the current state of the asset, use PHImageManager. This downloads and/or generates image data ready for you to save as a file, incorporating whatever edits the user has already applied to the asset:
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true
PHImageManager.default().requestImageData(for: myAsset, options: options) { data, uti, orientation, info in
// save `data` to file / upload to web service
// use `uti` to find out what file type it is
}
If you want the original image data resources — that is, enough data that you could back up and restore the asset, including features like in-progress edits, Live Photo modes, and RAW format image data — use PHAssetResource and PHAssetResourceManager:
let resources = PHAssetResource.resources(for: myAsset)
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = true
for resource in resources {
let outputURL = myOutputDirectory.appendingPathComponent(resource.originalFilename)
PHAssetResourceManager.default().writeData(for: resource, to: outputURL, options: options) { error in
// handle error if not nil
}
}
I am saving a list of file names/paths so I can load the image at a later time to upload it when the user selects the images from the camera roll
Don't. The thing to save so that you can retrieve something from the camera roll at a later time is the PHAsset's localIdentifier. You can use that to get the same asset again later, and now you can ask for the associated image.
here is the Context : My iOS swift app
records a sound,
creates a firebase object,
renames the file with the key of the object
uploads on firebase cloud the wav file.
A firebase cloud function is triggered that sends the audio file to google speech .recognize
My problem :
When I upload manually a sound file to the cloud storage, it works fine, but when the file is uploaded by the app automatically, I get the following error message as a return form the speech API :
{ Error: The caller does not have permission
at /user_code/node_modules/#google-cloud/speech/node_modules/grpc/src/node/src/client.js:554:15
code: 7, metadata: Metadata { _internal_repr: {} }, note:
'Exception occurred in retry method that was not classified as
transient' }
Here is the swift part :
func uploadFile(fileName:String){
// File located on disk
let localFileURL = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask)[0]
let fileURL = localFileURL.appendingPathComponent(fileName)
if FileManager.default.fileExists(atPath: fileURL.path) {
print("FilePath", fileURL.path)
// Create a reference to the file you want to upload
let newMnemoRef = MnemoDatabase.shared.createNew()
let newMnemoId = newMnemoRef.key
let filename=newMnemoId+".wav"
//let filename=fileName
let audioStorageRef = storage.reference().child(filename)
let storagePath = "gs://\(audioStorageRef.bucket)/\(audioStorageRef.fullPath)"
print(storagePath)
// Upload the file to the path "audio"
let uploadTask = audioStorageRef.putFile(from: fileURL, metadata: nil) { metadata, error in
if let error = error {
print("Upload error : ", error.localizedDescription)
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print ("OK")
}
}
// Add a progress observer to an upload task
let observer = uploadTask.observe(.success) { snapshot in
print("uploaded!")
newMnemoRef.child("audio").setValue([
"encoding_converted":"LINEAR16",
"sampleRate_converted":"44100",
"path_converted":storagePath])
}
} else {
print ("Non existent file", fileURL.path)
}
}
The cloud function calling the the speech API is fine with manually uploaded files.
here is the extract
const request = {
encoding: encoding,
sampleRateHertz: sampleRateHertz,
languageCode: language,
speechContexts: context
};
speech.recognize(uri, request)
The cloud storage bucket and cloud function all share the same project credentials.
I removed all authentification from the bucket
// Anyone can read or write to the bucket, even non-users of your app.
// Because it is shared with Google App Engine, this will also make
// files uploaded via GAE public.
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}
I even tried hard coding the path into the cloud function, but to no avail.
I will appreciate any help
Well it seems that I was working on two different projects, and the one I was calling functions from didn't have the speech API activated, and I was passing credentials of the other project.
I should really stop working too late...
I re-engineered my project to work with a file trigger now, this is how I found the bug...