App freezes / locks up when writing very large files - swift

I'm downloading and writing ~200mb podcasts into the Documents directory with the following code:
var podcastRequest = NSURLRequest(URL: audioUrl)
NSURLConnection.sendAsynchronousRequest(podcastRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if let myPodcastDataFromUrl = NSData(contentsOfURL: audioUrl) {
if myPodcastDataFromUrl.writeToURL(destinationUrl, atomically: true) {
// add to the array to track the download
var tempDic = self.posts[theRow] as! NSMutableDictionary as NSMutableDictionary
tempDic["downloaded"] = "true"
self.posts[theRow] = tempDic
} else {
println("Error saving file")
}
}
})
The sendAsynchronousRequest call prevents the lockup from happening during the download, but the app still freezes when it starts actually writing it to the directory.
Is there a way to prevent the lockup from happening at all, or am I going to have to write smaller chunks at a time?

You won't be able to store 200MB in memory before trying to write it to disk, but you can use downloadTaskWithURL method, it writes the file to a temporary folder and you can move it when it finishes to the documents folder as follow.
let documentsDirectoryURL = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as! NSURL
NSURLSession.sharedSession().downloadTaskWithURL(audioUrl, completionHandler: {
(location, response, error) -> Void in
if let error = error {
println(error.description)
}
else {
if NSFileManager().moveItemAtURL(location, toURL: documentsDirectoryURL.URLByAppendingPathComponent(response.suggestedFilename!), error: nil) {
println("saved")
} else {
println("not saved.")
}
}
}).resume()

Related

Listing all files in a directory on macOS Big Sur

In a different question I asked how to save files on a directory of the user's choosing. The reply was the following code, which works great.
func resolveURL(for key: String) throws -> URL {
if let data = UserDefaults.standard.data(forKey: key) {
var isStale = false
let url = try URL(resolvingBookmarkData: data, options:[.withSecurityScope], bookmarkDataIsStale: &isStale)
if isStale {
let newData = try url.bookmarkData(options: [.withSecurityScope])
UserDefaults.standard.set(newData, forKey: key)
}
return url
} else {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canCreateDirectories = true
panel.canChooseFiles = false
if panel.runModal() == .OK,
let url = panel.url {
let newData = try url.bookmarkData(options: [.withSecurityScope])
UserDefaults.standard.set(newData, forKey: key)
return url
} else {
throw ResolveError.cancelled
}
}
}
func saveFile(filename: String, contents: String) {
do {
let directoryURL = try resolveURL(for: "savedDirectory")
let documentURL = directoryURL.appendingPathComponent (filename + ".txt")
print("saving " + documentURL.absoluteString)
try directoryURL.accessSecurityScopedResource(at: documentURL) { url in
try contents.write (to: url, atomically: false, encoding: .utf8)
}
} catch let error as ResolveError {
print("Resolve error:", error)
} catch {
print(error)
}
}
Now, the next step is to go to the directory the user chose when the app loads, and if any files are there, ready each one and add the contents of those files to the struct I use to hold the data.
Googling a little bit I found that you can read all files in a directory using FileManager.default.contentsOfDirectory so I wrote:
func loadFiles() {
do {
let directoryURL = try resolveURL(for: "savedDirectory")
let contents = try FileManager.default.contentsOfDirectory(at: directoryURL,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles])
for file in contents {
print(file.absoluteString)
}
} catch let error as ResolveError {
print("Resolve error:", error)
return
} catch {
print(error)
return
}
}
But, I get the following error:
Error Domain=NSCocoaErrorDomain Code=257 "The file “myFiles” couldn’t be opened because you don’t have permission to view it." UserInfo={NSURL=file:///Users/aleph/myFiles, NSFilePath=/Users/aleph/myFiles, NSUnderlyingError=0x600000704ba0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
which looking at my code I would guess it's happening because I'm not using directoryURL.accessSecurityScopedResource. I tried to add that, or find any other way, but I'm running into a block and I don't know how to get to the directory saved in savedDirectory, and go through every file, reading its contents.
Thank you for any help
If I use:
directoryURL.startAccessingSecurityScopedResource()
// load the files
directoryURL.stopAccessingSecurityScopedResource()
Then it works.

Swift 3 Google Firebase Multi Upload

i am currently trying to upload multiple file from my device to google firebase, here is my setup:
func upload(completionHandler: #escaping (Bool) -> ()) {
let fileNames = try fileManager.contentsOfDirectory(atPath: documentsUrl.path)
for fileName in fileNames {
let url = URL(fileURLWithPath: "\(FileService.shared.documentsUrl.path)/\(fileName)")
let ref = storage.reference().child("path/\(fileName)")
let upload = ref.putFile(from: url, metadata: nil, completion: { (metadata, error) in
if error != nil {
completionHandler(true)
}
completionHandler(true)
})
}
}
How i call it:
self.upload { (success) in
print("operation \(success)")
}
What i see:
When i just have one file in the document directory, the loop runs just once and the file is being uploaded!
BUT: When there are multiple files, the loops also runs multiple times and the files are NOT being uploaded!
FIRStorageErrorDomain Code=-13000
Anybody could help me with this?
Thanks and Greetings!

Download multiple files containing data points one file per time

This code works perfectly when downloading one file. However when trying to download multiple files that contain data and caching the incoming data causes a mess. Since the download occurs non-stop, thus one file is done and the next starts. I can't cache them since I don't know which data belongs to which file.
lazy var downloadQueue: OperationQueue = {
var queue = OperationQueue()
queue.maxConcurrentOperationCount = 1000000
queue.name = "Files"
return queue
}()
func fetch(url: String, completionHandler: #escaping ([String:String]) -> (), completionHandlerQueue: OperationQueue?) {
let task = session.dataTask(with: URL(string: url)!, completionHandler: {
(data, response, error) in
guard let data = data, let type = String(data: data, encoding: String.Encoding.utf8), error == nil else {
print("Error with the data: \(error.debugDescription)")
return
}
guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
return
}
guard completionHandlerQueue != nil else {
return
}
completionHandlerQueue!.addOperation(BlockOperation(block: {
completionHandler(type)
}))
})
task.resume()
}
// Here is the links
let urls = [.....]
func start() {
for url in urls {
print("Start")
fetch(url: url, completionHandler: { type in
// The incoming data I don't know which belongs to which url.
}, completionHandlerQueue: downloadQueue)
}
print("End")
}
Since its async I will get
Start
End
Then the data will come. How can I overcome this?

Download audio from Parse and save it into your device

I am quite new to swift. I am trying to download an audio that I have stored in Parse as .caf and want to be able to save it in my iPhone. I have tried something similar I used with images but obviously it is not working. Here is the code:
func saveAudio(name: String, audio: PFFile) {
var documentsDirectory:String?
var audioSave: NSData = NSData()
let paths:[AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
if (paths.count > 0){
documentsDirectory = paths[0] as? String
audio.getDataInBackgroundWithBlock({ (data, error) -> Void in
if let data = data where error == nil{
audioSave = NSData(data: data)
}
})
let savePath = documentsDirectory! + "/" + name
NSFileManager.defaultManager().createFileAtPath(savePath, contents: audioSave, attributes: nil)
}
}
audio in this function is the object["audiophile"] stored in Parse.
Thank you very much!!
You're getting the data in the background, which means that your audioData variable may or, more likely, may not have valid data in it when your createFileAtPath method is being called.
Call createFileAtPath from WITHIN the background block, after the data has been successfully retrieved.
Something like this perhaps?
documentsDirectory = paths[0] as? String
audio.getDataInBackgroundWithBlock({ (data, error) -> Void in
if (error == nil) {
if let data = data {
audioSave = NSData(data: data)
let savePath = documentsDirectory! + "/" + name
NSFileManager.defaultManager().createFileAtPath(
savePath, contents: audioSave, attributes: nil)
}
} else {
print("got some kind of error - \(error.localizedDescription)")
}
})

Creating a UIImage from a remote LSR file

I'm trying to create a UIImage with one of Apple's new layered image files that's hosted on a remote server.
The sample code below downloads the lsr file correctly (the data var holds a value), but creating a new NSImage with it results in a nil value. Ignore the fact that this code is synchronous and inefficient.
if let url = NSURL(string: "http://path/to/my/layered/image.lsr") {
if let data = NSData(contentsOfURL: url) {
let image = UIImage(data: data) // `image` var is nil here
imageView?.image = image
}
}
Any thoughts on how to download an LSR and create a UIImage with it?
That's how i solved it:
Convert you .lsr file to a .lcr file doing this from console:
xcrun --sdk appletvos layerutil --c your_file.lsr
Upload your_file.lcr on your server
Put these two functions into an util class:
func getDataFromUrl(url:NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
completion(data: data, response: response, error: error)
}.resume()
}
func downloadImage(url: NSURL, imageView: UIImageView){
print("Started downloading \"\(url.URLByDeletingPathExtension!.lastPathComponent!)\".")
getDataFromUrl(url) { (data, response, error) in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let data = data where error == nil else { return }
print("Finished downloading \"\(url.URLByDeletingPathExtension!.lastPathComponent!)\".")
imageView.image = UIImage(data: data)
}
}
}
Use it like this:
if let checkedUrl = NSURL(string: "http://domain/path/to/your_file.lcr") {
self.my_ui_view.contentMode = .ScaleAspectFit
downloadImage(checkedUrl, imageView: self.my_ui_view.contentMode)
}
This will use the image without saving it into the document directory, if you need that solution, ask me and i'll share.