How to get download progress in the Google Drive API (Swift 5) - ios

I use this function to download file from the Google Drive API and I want to get download progress. Maybe anybody knows how to do it?
func download(file: GTLRDrive_File) {
let url = "https://www.googleapis.com/drive/v3/files/\(file.identifier!)?alt=media"
let fetcher = drive.fetcherService.fetcher(withURLString: url)
fetcher.beginFetch(completionHandler: { data, error in
if let error = error {
print(error.localizedDescription)
}
//Here I save data to the Documents
})
}
I tried to get it from fetcher.receivedProgressBlock but it's always return nil

Solution. Actual for Swift 5:
func download(file: GTLRDrive_File) {
let fileSize = file.size?.doubleValue //You need to fetch file size in your request q.fields = "kind,nextPageToken,files(mimeType,id,name,size)"
let url = "https://www.googleapis.com/drive/v3/files/\(file.identifier!)?alt=media"
let fetcher = drive.fetcherService.fetcher(withURLString: url)
fetcher.beginFetch(completionHandler: { data, error in
if let error = error {
print(error.localizedDescription)
}
//Here I save data to the Documents
})
//Here you can get total bytes received (value is updated in real time)
fetcher.receivedProgressBlock = { _, totalBytesReceived in
guard let fileSize = fileSize else { return }
let progress = Double(totalBytesReceived) / fileSize
print(progress) //Here you can update UI (progress bar) or do something else
}
}

For some reason, file.size returns nil so I used fetcher.response?.expectedContentLength instead.
func download(file: GTLRDrive_File, service: GTLRDriveService) {
let url = "https://www.googleapis.com/drive/v3/files/\(file.identifier!)?alt=media"
let fetcher = service.fetcherService.fetcher(withURLString: url)
fetcher.beginFetch(completionHandler: { fileData, error in
if error == nil {
print("finished downloading file Data...")
print(fileData as Any)
// do anything with data here
} else {
print("Error: \(error?.localizedDescription)")
}
})
fetcher.receivedProgressBlock = { _, totalBytesReceived in
print("totalBytesReceived: \(totalBytesReceived)")
print("size: \(fetcher.response?.expectedContentLength)")
if let fileSize = fetcher.response?.expectedContentLength {
let progress: Double = Double(totalBytesReceived) / Double(fileSize)
print(progress)
// Update progress bar here
}
}
}

Related

Swift Firebase Storage get all Download URL's of a specific child

Currently, I can fetch the download url by file name via firebase storage reference.
I would like to retrieve all download URLS in a specific child without using a file name and only using the last child name.
Simply adding every download url in a list/array
How can I accomplish this with my given reference.
func getDownloadURL() {
let ref = Storage.storage().reference()
let fileName = "Lessons_Lesson1_Class1.mp3"
let starsRef = ref.child("Daily Meditations").child("Lessons").child("Lesson 1").child(fileName)
// Fetch the download URL
starsRef.downloadURL { url, error in
if let error = error {
// Handle any errors
print(error)
} else {
// Get the download URL for 'Lessons_Lesson1_Class1.mp3'
print(url)
}
}
}
Firebase Refrence Docs
let stg = Storage.storage().reference()
let path = "Daily Meditations/Lessons/Lesson 1"
stg.child(path).listAll { (list, error) in
if let error = error {
print(error)
} else {
let inStorage = list.items.map({ $0.name })
print(inStorage) // an array of file names in string format
}
}
I assume spaces are allowed in path names since you're using them. To list all of the files in a path, use listAll. The method will return a StorageListResult object which I've named list.
https://firebase.google.com/docs/reference/swift/firebasestorage/api/reference/Classes/StorageListResult
So I was able to combine List all files and Download URL to achieve what I was trying to accomplish from the firebase documentation.
Here is the code:
func getDownloadURl() {
let ref = Storage.storage().reference()
let storageReference = ref.child("Lessons/Lesson 1")
storageReference.listAll { (result, error) in
if let error = error {
print(error)
}
for item in result.items {
//List storage reference
let storageLocation = String(describing: item)
let gsReference = Storage.storage().reference(forURL: storageLocation)
// Fetch the download URL
gsReference.downloadURL { url, error in
if let error = error {
// Handle any errors
print(error)
} else {
// Get the download URL for each item storage location
print(url!)
}
}
}
}
}
If anyone is using with VUE 2.6 and TS, here is my workaround
Imports
import {
getStorage,
ref,
getDownloadURL,
listAll,
StorageReference,
} from "firebase/storage";
async mounted(): Promise<void> {
const storage = getStorage();
const imageRefs = await listAll(ref(storage, "SOME BUCKET"))
.then((refs) => {
return refs.items;
})
.catch((error) => {
// Handle any errors
});
(imageRefs as StorageReference[]).forEach((item) => {
console.log(item);
getDownloadURL(item).then((downloadURL) => {
console.log(downloadURL);
this.model.listFiles.push(downloadURL);
});
});
console.log(imageRefs);
console.log(this.model.listFiles);
},

Almofire multiple images Download and save them locally

I have more than 500 image links I want to download those images and store locally in app document directory when app starts. I am using Almofire for download but I am getting error like
"URLSession task was cancelled" and Request timeOut
func downloadAllImages(images:[String: String], retryCount: Int = 0,completion: #escaping((Bool)->Void)){
var success: Bool = true
var failedImages = [String: String]()
for (localName, severPath) in images {
self.dispatchGroup.enter()
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent(localName)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
let path = severPath.replacingOccurrences(of: "\\", with: "/").addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
//AF.sessionConfiguration.httpShouldSetCookies = false
AF.download(path, to: destination).response { response in
switch response.result {
case .success(_):
break
case .failure(let error):
if response.response?.statusCode != 404 {
success = false
failedImages[localName] = path
print("Image Download Error = \(error.localizedDescription)")
}
break
}
debugPrint(response)
self.dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
//retry If some Images failed to download
if failedImages.isEmpty || retryCount >= self.maximumRetryCount {
completion(success)
}else {
self.downloadAllImages(images: failedImages, retryCount: retryCount + 1) { (success) in
completion(success)
}
}
}
}
images dictionary contains
localName as key
serverPath as value
AFImageDownloaders have limit of active downloads, and I believe changing maximumActiveDownloads or something similar in your API will fix that. The new downloads just cancel the previous ones. But it's better to download them in chunks.
For example this one is for ImageDownloader
public init(session: Session,
downloadPrioritization: DownloadPrioritization = .fifo,
maximumActiveDownloads: Int = 4,
imageCache: ImageRequestCache? = AutoPurgingImageCache()) {
precondition(!session.startRequestsImmediately, "Session must set `startRequestsImmediately` to `false`.")
self.session = session
self.downloadPrioritization = downloadPrioritization
self.maximumActiveDownloads = maximumActiveDownloads
self.imageCache = imageCache
}
UPD:
The limit is not on AF, but URLSession's. And one AF downloader uses one URLSession. You have to pass custom URLSessionConfigurations to handle more active downloads HTTPMaximumConnectionsPerHost. And pass it AF Session class

Swift: Downloading Images from DropBox

Here's the code I've tried so far:
client?.files.download(path: "/AlloyTest/\(imageName)").response { response, error in
if let response = response {
let responseMetadata = response.0
print(responseMetadata)
let fileContents = response.1
print(fileContents)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
This is the error I'm getting when trying the function below:
API route error - {
".tag" = path;
path = {
".tag" = "not_found";
};
}
NEW CODE
func getImage(imageName: String, completion: #escaping (UIImage, NetworkingError) -> ()) {
// Get Image from dropbox
// Download to Data
client?.files.listFolder(path: "/AlloyTest").response { response, error in
if let response = response {
let entries = response.entries
print("ENTRIES:", entries)
} else if let error = error {
print(error)
}
}
}
A path/not_found error indicates that there was nothing at the specified path, in this case "/AlloyTest/\(imageName)", in the connected Dropbox account. Make sure you provide the correct path.
For example, you can list the contents of any particular folder to get the correct path values of its contents using listFolder/listFolderContinue. The path for any particular returned item is Metadata.pathLower.

How to download A LOT of files from S3 using the transfer utility?

I have several thousand images I want to download from a S3 bucket to an iOS App.
But I'm getting memory issues I'm unable to track down.
Here is my sketchy code:
let client = HttpClient<[SomeImage]>()
client.get(fromURL: URL(string: endpoint)!) {
(result, error) in
if let error = error {
self.log(message: "\(error)", level: .error)
return
}
if let result = result {
let downloadGroup = DispatchGroup()
var count = 0
// just assembling a list of s3 keys to download here...
for item in result {
for image in (item.images ?? []) {
let prefix = "\(image.key)/"
for key in ["\(globalGetThumbnailS3Key(byImageKey: image.key))",
"\(globalGetPreviewS3Key(byImageKey: image.key))"] {
count = count + 1
let completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock = {
(task, URL, data, error) in
if let error = error {
self.log(message: "\(error)", level: .error)
return
}
if let data = data, let localDir = FileManager.default.applicationSupportURL {
do {
let imageURL = localDir.appendingPathComponent(key)
FileManager.default.directoryExistsOrCreate(localDir.appendingPathComponent(prefix))
try data.write(to: imageURL)
self.log(message: "downloaded \(prefix)\(key) to \(imageURL.absoluteString)", level: .verbose)
} catch let error {
self.log(message: "\(error)", level: .error)
return
}
}
}
bgSyncQueue.async(group: downloadGroup) {
self.transferUtility.downloadData(fromBucket: "\(globalDerivedImagesBucket)", key: key,
expression: nil,
completionHandler: completionHandler).continueWith {
(task) in
if let error = task.error {
// iirc, this error is caused, if the task couldnt be created due to being offline
self.log(message: "\(error)", level: .error)
return nil
}
if let result = task.result {
// do something with the task?
return nil
}
return nil
}
}
}
}
}
self.log(message: "\(count) images to download...", level: .debug)
bgSyncQueue.activate()
downloadGroup.notify(queue: DispatchQueue.main) {
self.log(message: "All items downloaded?!")
}
}
}
}
So I put all calls to the transfer utility in a serial dispatch queue, which is initially inactive. Then I activate the queue and downloading starts just fine. But after a while the app crashes with "Message from debugger: Terminated due to memory issue."
The app is only consuming about 100M of memory though. What am I overlooking?
Rob's suggestion to use the "downloadToUrl" method was the way to go, without using GCD on my part. Thanks again, Rob!
The transferUtility seems to be a fine tool, though very badly documented.
Here is the simple code used to download about 20k of images:
for key in keys {
let imageURL = localDir.appendingPathComponent(key.1)
let completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock = {
(task, URL, data, error) in
if let error = error {
self.log(message: "failed downloading \(key.1): \(error)", level: .error)
DispatchQueue.main.async {
countingDown()
}
return
}
DispatchQueue.main.async {
countingDown()
if let onProgress = self.onProgress {
onProgress(100.0 - ((100.0 / Double(total)) * Double(count)))
}
}
//self.log(message: "downloaded \(key.1)")
}
transferUtility.download(to: imageURL, bucket: "\(globalDerivedImagesBucket)", key: key.1, expression: nil, completionHandler: completionHandler).continueWith {
(task) in
if let error = error {
self.log(message: "\(error)", level: .error)
DispatchQueue.main.async {
countingDown()
}
return nil
}
return nil
}
}
You may need to consider using an autoreleasepool to better manage the memory used by the bridged data types as detailed here
Exert from article (in case of link changes)
Consider the code:
func run() {
guard let file = Bundle.main.path(forResource: "bigImage", ofType: "png") else {
return
}
for i in 0..<1000000 {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
}
Even though we’re in Swift, this will result in the same absurd memory spike shown in the Obj-C example! This is because the Data init is a bridge to the original Obj-C [NSDatadataWithContentsOfURL] -- which unfortunately still calls autorelease somewhere inside of it. Just like in Obj-C, you can solve this with the Swift version of #autoreleasepool; autoreleasepool without the #:
autoreleasepool {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
Disclaimer: I am no expert in Swift or Objective-C advanced memory management but I have used this in a similar scenario with good results.

Dropbox usersGetCurrentAccount no member compiler error

Using IOS 9.1, running on Xcode 7.1.1. under El Capitan. I am trying out the tutorial on accessing dropbox thru their (new maybe) SwiftDropbox library by following this page.
https://blogs.dropbox.com/developers/2015/05/try-out-swiftydropbox-the-new-swift-sdk-for-dropbox-api-v2/
Started well, managed to connect and get authorised, but unable to do much more cause the code posted to their tutorial doesn't compile.
if let client = Dropbox.authorizedClient {
// Get the current user's account info
client.usersGetCurrentAccount().response { response, error in
if let account = response {
println("Hello \(account.name.givenName)")
} else {
println(error!)
}
}
// List folder
client.filesListFolder(path: "").response { response, error in
if let result = response {
println("Folder contents:")
for entry in result.entries {
println(entry.name)
}
} else {
println(error!)
}
}
Complaining that DropboxClient has no member "usersGetCurrentAccount" or "filesListFolder" which I assume are some sort method calls {pretty new to swift too}. Can not seem to google any useful leads on this? What should this code read/say?
Bon,
THANKYOU smarx; you put me on the right path. Here is the final code; with two minor fixes to the tutorial he mentioned here.
// ViewController.swift
import UIKit
import SwiftyDropbox
class ViewController: UIViewController {
#IBAction func linkButtonPressed(sender: AnyObject) {
if (Dropbox.authorizedClient == nil) {
Dropbox.authorizeFromController(self)
} else {
print("User is already authorized!")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Verify user is logged into Dropbox
if let client = Dropbox.authorizedClient {
// Get the current user's account info
client.users.getCurrentAccount().response { response, error in
print("*** Get current account ***")
if let account = response {
print("Hello \(account.name.givenName)!")
} else {
print(error!)
}
}
// List folder
client.files.listFolder(path: "").response { response, error in
print("*** List folder ***")
if let result = response {
print("Folder contents:")
for entry in result.entries {
print(entry.name)
}
} else {
print(error!)
}
}
// Upload a file
let fileData = "Hello!".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
client.files.upload(path: "/hello.txt", body: fileData!).response { response, error in
if let metadata = response {
print("*** Upload file ****")
print("Uploaded file name: \(metadata.name)")
print("Uploaded file revision: \(metadata.rev)")
// Get file (or folder) metadata
client.files.getMetadata(path: "/hello.txt").response { response, error in
print("*** Get file metadata ***")
if let metadata = response {
if let file = metadata as? Files.FileMetadata {
print("This is a file with path: \(file.pathLower)")
print("File size: \(file.size)")
} else if let folder = metadata as? Files.FolderMetadata {
print("This is a folder with path: \(folder.pathLower)")
}
} else {
print(error!)
}
}
// Download a file
let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
// generate a unique name for this file in case we've seen it before
let UUID = NSUUID().UUIDString
let pathComponent = "\(UUID)-\(response.suggestedFilename!)"
return directoryURL.URLByAppendingPathComponent(pathComponent)
}
client.files.download(path: "/hello.txt", destination: destination).response { response, error in
//client.files.download(path: "/hello.txt").response { response, error in
if let (metadata, url) = response {
print("*** Download file ***")
let data = NSData(contentsOfURL: url)
print("Downloaded file name: \(metadata.name)")
print("Downloaded file url: \(url)")
print("Downloaded file data: \(data)")
} else {
print(error!)
}
}
} else {
print(error!)
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You're reading a blog post from May about an early preview version of the SDK. Lots of things have changed since that post! Check out https://www.dropbox.com/developers/documentation/swift#tutorial for an up-to-date tutorial.
I believe you want users.getCurrentAccount and files.listFolder.

Resources