Swift: Uploading a file to Firebase Storage from a Share Extension - ios

I am trying to upload an image to firebase storage from a share extension in iOS, I have authed and am communicating with the database but when I attempt to upload the file it fails straight away.
I have made sure that the code that I am using works by using it in my main app. I have also made sure that the file is being saved in the file manager prior to being uploaded correctly.
Here is the code for saving the file prior to the upload:
if let data = downsizeImage(image: image).jpegData(compressionQuality: 0.2) {
let fileManager = FileManager.default
let url = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.<DOMAIN>.imageShare")?.appendingPathComponent("ImageToSend.jpg")
do {
try data.write(to: url!)
}
catch {
print(error.localizedDescription)
}
}
Here is the code for the upload task:
let storageRef: StorageReference = Storage.storage().reference().child(storageLocation).child(UUID().uuidString)
var completed = false
var mediaUploadTask: StorageUploadTask?
let mediaTimeoutTask = DispatchWorkItem{ () in
if !completed {
mediaUploadTask?.cancel()
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 30, execute: mediaTimeoutTask)
mediaUploadTask = storageRef.putFile(from: mediaUrl, metadata: nil) {(metadata, error) in
completed = true
...
}
What should happen is the image is successfully uploaded and the function would continue as normal. What actually happens is the upload fails nearly straight away. Here is the error returned:
Printing description of error:
▿ Optional<Error>
- some : Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response."
UserInfo={bucket=link-ages-55880.appspot.com,
_NSURLErrorFailingURLSessionTaskErrorKey=BackgroundUploadTask <AC5EADEA-6257-4C32-9454-17626156AA15>.<1>,
object=media/4qnjSBKysi79uCR3cTzf/04D22317-D2C0-4A5C-B032-4F37DB2C8F7A,
_NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundUploadTask <AC5EADEA-6257-4C32-9454-17626156AA15>.<1>"
),
NSLocalizedDescription=An unknown error occurred, please check the server response.,
ResponseErrorDomain=NSURLErrorDomain, ResponseErrorCode=-995}
If anyone has any idea what the problem might be, your ideas would be greatly appreciated. Thanks.
Here is a screenshot of the debugger:

I have found out what the problem was. Due to the way that iOS sandboxing works, calling:
storageRef.putFile(from: mediaUrl, metadata: nil, completion: {(metadata, error) in})
fails. More info Here: Original Answer.
Instead calling:
storageRef.putData(Data, metadata: nil, completion: {(metadata, error) in})
worked as intended.

Related

Firebase method is returning an unknown Error

I'm trying to send data to Cloud Storage using the Firebase SDK for iOS. In doing so I'm creating a reference (StorageReference) in this way:
let storageRef = Storage.storage().reference(forURL: Config.CONFIG_ROOF_REF).child("Posts").child(videoIDString)
And using the putFile method:
storageRef.putFile(from: videoURL, metadata: nil) { (metadata, error) in
if error != nil{
ProgressHUD.showError(error?.localizedDescription)
return
}
storageRef.downloadURL(completion: { (url, error) in
if error != nil {
return
}else {
self.videourl = url?.absoluteString
onSuccess(videourl!)
}
})
}
I pass in the argument videoURL and I receive back the error:
An unknown error occurred, please check the server response
I cannot understand why I receive that error since the videoURL I'm passing seems legit.
The videoURL I'm passing is this one:
file:///Users/andreagualandris/Library/Developer/CoreSimulator/Devices/7D3329AC-AD8D-4553-AC61-FDF8435134B6/data/Containers/Data/PluginKitPlugin/8229E521-A7A2-4C09-8312-CC6106C2FED8/tmp/trim.7CC60B3A-6982-4F42-AA63-ED094288A562.MOV
So I think this is a normal URL and there shouldn't be any problem with it.
The actual server response on the console is :
2020-01-26 10:31:27.107215+0100 InstagramClone[2517:235684] Task <4AC8F3F2-1CC6-4064-BBF7-6D0375769C9A>.<1> finished with error [-1] Error Domain=NSURLErrorDomain Code=-1 "unknown error" UserInfo={NSErrorFailingURLStringKey=https://firebasestorage.googleapis.com/v0/b/instagramclone-1ed36.appspot.com/o/Posts%2F412BA396-6CAD-4197-817F-1AA0D9E6E6FE?uploadType=resumable&name=Posts%2F412BA396-6CAD-4197-817F-1AA0D9E6E6FE&upload_id=AEnB2UoAHEWQ0WDv_Id2TK_xSL_71Jrj8kbmuDfLpGkO8fBe6e9sBF74QUj5r0MLdltJN79z5fWAtoqOx8JSAfpppyPe6qKrxA&upload_protocol=resumable, NSErrorFailingURLKey=https://firebasestorage.googleapis.com/v0/b/instagramclone-1ed36.appspot.com/o/Posts%2F412BA396-6CAD-4197-817F-1AA0D9E6E6FE?uploadType=resumable&name=Posts%2F412BA396-6CAD-4197-817F-1AA0D9E6E6FE&upload_id=AEnB2UoAHEWQ0WDv_Id2TK_xSL_71Jrj8kbmuDfLpGkO8fBe6e9sBF74QUj5r0MLdltJN79z5fWAtoqOx8JSAfpppyPe6qKrxA&upload_protocol=resumable, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundUploadTask <4AC8F3F2-1CC6-4064-BBF7-6D0375769C9A>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=BackgroundUploadTask <4AC8F3F2-1CC6-4064-BBF7-6D0375769C9A>.<1>, NSLocalizedDescription=unknown error}

Firebase Storage iOS put file completion handler called before upload finishes

I was trying to upload videos to Firebase Storage, and when the upload finishes, I store the location of this video file to an object in the database, my code is as below:
Storage.storage().reference(withPath: mediaURL!).putFile(from: localCacheURL, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("❗️failed to upload video")
} else {
print("a video is uploaded")
json = ["text": "", "image": "", "video": mediaURL!, "connections": []]
upload()
}
})
But I found the completion handler called before the upload actually finishes, I went to Firebase console and downloaded the uploaded file, it was unfinished.
Does anybody know why?
One for your reason for your problem may be that your cache file is somehow broken. To figure it out what's happening you need more information.
You can do that by observing the FIRFileStorageUploadTaskwhich is returned by putFile.
let uploadTask = Storage.storage().reference(withPath: mediaURL!).putFile(from: localCacheURL, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("❗️failed to upload video")
} else {
print("a video is uploaded")
json = ["text": "", "image": "", "video": mediaURL!, "connections": []]
// unclear what this does
// looks strange if it's just a notification rename it into something like videoUploadCompleted
// if it needs the json stuff pass it as parameter to make it thread safe
upload()
}
})
uploadTask.observe(.progress) { snapshot in
print("\(snapshot.progress)")
}
It will give you something like:
<NSProgress: 0x604000123020> :
Parent: 0x0 / Fraction completed: 0.0062 / Completed: 1867836 of
301329576
You can also get more information about failures by using
uploadTask.observe(.failure) { snapshot in
if let error = snapshot.error as? NSError {
print ("Error : \(error)")
}
}
For more details about monitoring and error handling see also Upload Files on iOS Firebase Documentation

Firebase Storage download to local file error

I'm trying to download the image f5bd8360.jpeg from my Firebase Storage.When I download this image to memory using dataWithMaxSize:completion, I'm able to download it.
My problem comes when I try to download the image to a local file using the writeToFile: instance method. I'm getting the following error:
Optional(Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown
error occurred, please check the server response."
UserInfo={object=images/f5bd8360.jpeg,
bucket=fir-test-3d9a6.appspot.com, NSLocalizedDescription=An unknown
error occurred, please check the server response.,
ResponseErrorDomain=NSCocoaErrorDomain, NSFilePath=/Documents/images,
NSUnderlyingError=0x1700562c0 {Error Domain=NSPOSIXErrorDomain Code=1
"Operation not permitted"}, ResponseErrorCode=513}"
Here is a snippet of my Swift code:
#IBAction func buttonClicked(_ sender: UIButton) {
// Get a reference to the storage service, using the default Firebase App
let storage = FIRStorage.storage()
// Get reference to the image on Firebase Storage
let imageRef = storage.reference(forURL: "gs://fir-test-3d9a6.appspot.com/images/f5bd8360.jpeg")
// Create local filesystem URL
let localURL: URL! = URL(string: "file:///Documents/images/f5bd8360.jpeg")
// Download to the local filesystem
let downloadTask = imageRef.write(toFile: localURL) { (URL, error) -> Void in
if (error != nil) {
print("Uh-oh, an error occurred!")
print(error)
} else {
print("Local file URL is returned")
}
}
}
I found another question with the same error I'm getting but it was never answered in full. I think the proposal is right. I don't have permissions to write in the file. However, I don't know how gain permissions. Any ideas?
The problem is that at the moment when you write this line:
let downloadTask = imageRef.write(toFile: localURL) { (URL, error) -> Void in
etc.
you don't yet have permission to write to that (localURL) location. To get the permission you need to write the following code before trying to write anything to localURL
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localURL = documentsURL.appendingPathComponent("filename")
By doing it you will write the file into the following path on your device (if you are testing on the real device):
file:///var/mobile/Containers/Data/Application/XXXXXXXetc.etc./Documents/filename
If you are testing on the simulator, the path will obviously be somewhere on the computer.

Firebase Storage: child() doesn't work with iOS App

I'm getting the following error when trying to download an image from my Firebase Storage:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object 2xxxxxxx8/profile_pic does not exist."
(I obviously put the x's up there to mask private info.)
I'm adding a path reference to my Firebase Storage using the following code:
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://project-4xxxxxxxxxxxxx.appspot.com")
let profilePicReference = storageRef.child(signedInUser.uid + "/profile_pic")
I know the code above is good cause everything was working correctly: I could see a folder was added in my Storage space, and an image was uploaded into that folder - all directly from my iOS App.
The problems started when I manually deleted said folder from my Firebase Storage (I did this through the Firebase web portal) - just cause I wanted verify everything was working, so I deleted the folder to start fresh - expecting the code above would recreate it once I ran the App again - and since then I'm getting this error over and over again.
Really makes no sense.
Are there any quirks or issues with Firebase Storage? Some sort of caching that has to be addressed?
Any tips would be greatly appreciated!
Are there any quirks or issues with Firebase Storage? Some sort of
caching that has to be addressed?
An UploadTask executes asynchronously. If I try downloading an image immediately after uploading an image, I can reproduce your error. What's happening is that the download code executes before the image finishes uploading, producing the image-does-not-exist error. You can see that the download code executes too early by printing out some messages in the callbacks:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
That code produces the output:
[My Download Error]: ...."Object images/align_menu.tiff does not exist."...
and then after a few seconds I see the output:
[My Upload Success]:
[URL for download]: ...
which demonstrates that the download callback is executing before the upload callback. I can't quite figure out the details of why that happens--but obviously the callbacks are not added to a serial queue.*
To cure the asynchronous problem, you have several options:
1) Put the download code inside the callback for the upload code.
That way, the download won't start executing until after the image has successfully uploaded. After I did that, deleting the image using the Firebase Storage webpage before running the app had no deleterious effect on my upload/download, and the messages were output in the expected order:
[My Upload Success]:
[URL for download]: ...
[My Download Success]:
2) Attach a .Success observer to the uploadTask.
As described in the Firebase docs, in the Monitor Upload Progress section, you can get notified if the uploadTask successfully uploads the image:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
let observer = uploadTask.observeStatus(.Success) { (snapshot) -> Void in
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
3) Use Grand Central Dispatch to notify you when the upload is successful.
You don't have control over what queues the callbacks get added to (the Firebase method implementations decide that), but you can use Grand Central Dispatch to notify you when arbitrary code finishes executing. The following works for me:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
let myExecutionGroup = dispatch_group_create()
dispatch_group_enter(myExecutionGroup)
//Upload the image:
let _ = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
dispatch_group_leave(myExecutionGroup)
}
}
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_group_notify(myExecutionGroup, queue) {
//This callback executes for every dispatch_group_leave().
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
* I tried putting a sleep(10) between the original upload code and download code, and that did not alleviate the problem. I thought that if the upload callback was executing on a background thread, then the upload callback would have time to complete while the main thread was sleeping, then after the sleep finished the download code would execute and the download callback would be added to a queue somewhere, then the download callback would execute. Because the sleep(10) didn't solve the problem, that meant the upload callback had to have been added to an execution queue for the main thread, and the sleep halted the main thread and anything in the queue from executing.
That leads me to believe that the upload and download callbacks are added to an asynchronous queue on the main thread (it's not a synchronous queue otherwise the callbacks would execute in order). I think an asynchronous queue on the main thread means that when there is dead time on the main thread, the tasks in the queue will execute, and you also get rapid switching between the various tasks when there is dead time in a particular task, like waiting for an HTTP response. For example, if there are two tasks in an asynchronous queue on the main thread, then there is rapid switching between the main thread, task1, and task2 whenever there is dead time in any one of them.

Error when downloading from Firebase storage

I am trying to get basic upload/download working with the new Firebase storage. Uploading worked fine but I am unable to download the file to the device. Can someone please shed some light on what I am doing wrong. Thanks!
func downloadAudio() {
let storageRef = FIRStorage.storage().reference()
let pathReference = storageRef.child("testAudio/audio_test.m4a")
let localURL = getDocumentsDirectory().URLByAppendingPathComponent("audio_test2.m4a")
let downloadTask = pathReference.writeToFile(localURL) { (URL, error) -> Void in
if (error != nil) {
print("ERROR - ", error.debugDescription)
} else {
print("SUCCESS - ", URL)
}
}
}
PRINTS:
ERROR - Optional(Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response." UserInfo={ResponseErrorDomain=NSCocoaErrorDomain, object=testAudio/audio_test.m4a, NSURL=/Users/Ben/Library/Developer/CoreSimulator/Devices/02AF50F2-E9BE-4EED-A3BE-485D63264731/data/Containers/Data/Application/31BDED56-0135-4E70-943E-F897080768D6/Documents/, bucket=mydevslopesapp.appspot.com, ResponseErrorCode=518, NSLocalizedDescription=An unknown error occurred, please check the server response.})
This is not a storage error, it's actually an issue with the file you're attempting to write to.
Looks like URLByAppengingString should be fileURLWithPath to get a file system URL (per NSFileManager creating directory error 518 NSFileWriteUnsupportedSchemeError).
Long term we need to fish this out and serve it as a "see relevant error" rather than "read network response."

Resources