I have an S3Service which is a singleton that manages all the S3 related uploads and downloads.
When I upload the first image it works fine but if I try to upload an Image consecutively It gives me this warning and the completion block never gets called.
A background URLSession with identifier com.amazonaws.AWSS3TransferUtility.Identifier.TransferManager already exists.
This is how I upload method looks:
if let data = image.jpegData(compressionQuality: 0.5) {
let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: S3Service.TRANSFER_MANAGER_KEY)
transferUtility.uploadUsingMultiPart(data: data, bucket: EnvironmentUtils.getBucketName(), key: filename, contentType: "image/jpg", expression: nil, completionHandler: { task,error in
if let error = error {
print(error.localizedDescription)
} else {
print("Image upload success")
}
})
}
The call to register transfer utility AWSS3TransferUtility.register(with: serviceconfig, forKey: KEY) was causing the above issue. There are two things that should be kept in mind.
The AWSS3TransferUtility should be registered only once per Application session. Then we can use AWSS3TransferUtility.S3TransferUtilityForKey to get the instance wherever needed.
If these are for different users within the app, ( e.g. sign-up) and if we want to keep AWSS3TransferUtility separate for each user, register AWSS3TransferUtility with a different key (preferably the same key for the same user) and look up using that key.
Related
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 writing a sharing extension that will accept images and perform some action with them. Within a method of my UIViewController subclass, I can access URLs to a particular representation of the files by writing this:
guard let context = self.extensionContext else {
return
}
guard let items = context.inputItems as? [NSExtensionItem] else {
return
}
for item in items {
guard let attachments = item.attachments else {
continue
}
for attachment in attachments {
guard attachment.hasItemConformingToTypeIdentifier("public.jpeg") else {
continue
}
attachment.loadFileRepresentation(forTypeIdentifier: "public.jpeg") { (url, error) in
if let url = url {
// How long is this "url" valid?
}
}
}
}
In the block I pass to loadFileRepresentation(forTypeIdentifier:completionHandler:), I’m given the URL to a file—in this case, a JPEG. Can I assume anything about how long this URL is valid? Specifically, is it safe to write the URL itself to some shared storage area so that my app can access the pointed-to file later? Or should I assume that the URL is ephemeral and that, if I want access to the file it points at, I should make my own copy of that file within this block?
The documentation for loadFileRepresentation states:
This method writes a copy of the file’s data to a temporary file, which the system deletes when the completion handler returns.
So url is valid to the closing curly brace of the completion handler.
You need to copy the file to a known location with the sandbox before you are done in the completion handler if you need access to the file beyond the completion handler.
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...
I'm trying to get my head around Firebase Storage.
I've seen online and tried two methods of getting my images.
What is the difference between these two? (both work).
So after I get the photoUrl from my Firebase Database:
1.
if let data = NSData(contentsOfURL: NSURL(string:photoUrl)!)
{
let myImage = UIImage(data: data)!
MyImageCache.sharedCache.setObject(myImage, forKey: self.key)
//etc
}
2.
self.storage.referenceForURL(photoUrl).dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil)
{
print(error)
}
else
{
let myImage = UIImage(data: data!)
MyImageCache.sharedCache.setObject(myImage!, forKey: self.key)
//etc
}
}
Regarding the first method, you shouldn't be using that for network calls. From the docs:
Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated. Instead, for non-file URLs, consider using the dataTaskWithURL:completionHandler: method of the NSURLSession class. See URL Session Programming Guide for details.
The second method is baked into the firebase framework and provides you with convenience methods for downloading an image, i.e. it gives you the option to specify images size. This is probably optimised for getting images and is in most scenarios the preferred method.