1

I'm struggling to understand what I thought would be easy.

I have a URLSession.downloadTask. I have set my downloading object as the URLSession delegate and the following delegate methods do receive calls, so I know my delegate is set correctly.

func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?)
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)

The case I can't trap is when the downloadTask fills up the disk space on the iPad. None of those delegate methods get called.

How should I catch this error?

Here's my download object:

import Foundation
import Zip
import SwiftyUserDefaults

extension DownloadArchiveTask: URLSessionDownloadDelegate {

    // Updates progress info
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
                    didWriteData bytesWritten: Int64, totalBytesWritten: Int64,
                    totalBytesExpectedToWrite: Int64) {


        let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
        self.delegate?.updateProgress(param: progress)
    }

    // Stores downloaded file
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {

        print("DownloadArchiveTask: In didFinishDownloadingTo")

    }
}

extension DownloadArchiveTask: URLSessionTaskDelegate {

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        print("DownloadArchiveTask: In didCompleteWithError")
        if error != nil {
            print("DownloadArchiveTask: has error")
            self.delegate?.hasDiskSpaceIssue()
        }
    }
}

extension DownloadArchiveTask: URLSessionDelegate {
    // Background task handling
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print("DownloadArchiveTask: In handler for downloading archive")
        DispatchQueue.main.async {
            let sessionIdentifier = session.configuration.identifier
            if let sessionId = sessionIdentifier, let app = UIApplication.shared.delegate as? AppDelegate, let handler = app.completionHandlers.removeValue(forKey: sessionId) {
                handler()
            }
        }
    }
    func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
        print("DownloadArchiveTask: didBecomeInvalidWithError")
        if error != nil {
            print("DownloadArchiveTask: has error")
            self.delegate?.hasDiskSpaceIssue()
        }
    }
}

class DownloadArchiveTask: NSObject {
    var delegate: UIProgressDelegate?
    var archiveUrl:String = "http://someurl.com/archive.zip"

    var task: URLSessionDownloadTask?

    static var shared = DownloadArchiveTask()

    // Create downloadsSession here, to set self as delegate
    lazy var session: URLSession = {
        let configuration = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background_archive")
        return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    }()

    func initialDownload() {
        // Create URL to the source file you want to download
        let fileURL = URL(string: archiveUrl)

        let request = URLRequest(url:fileURL!)

        self.task = self.session.downloadTask(with: request)
        task?.resume()

    }
}

Anyone done this before? I can't believe it's this hard - I must be approaching the problem in the wrong way...

Cuespeak
  • 149
  • 1
  • 13
  • If this is a concern, maybe you could check disk space before the download, or maybe you should use a data task instead of a download task so you can check the disk space during the course of the download. You would then have to save the data yourself of course (space permitting). It sounds like this must be an unusually large download, though, so perhaps you should be explaining what you're trying to do. – matt Mar 22 '19 at 17:40
  • I think you will never face this issue as apple manage memory well, apple will delete temp files and then cache file. – Ravi Panchal Mar 22 '19 at 17:42
  • I'll check out the data task. The download is a zipped file containing the apps initial image content. As the content grows over time, so does the size of the initial zip file. We have had cases where the zip file can't be downloaded. What I want to do is catch this situation and flip the app into a backup 'online' mode. It's for use in hospitals where network connectivity is very poor, so the preferred solution is to download and store all image assets on the device. – Cuespeak Mar 22 '19 at 18:21

1 Answers1

0

I had to solve this problem for my company not that long ago. Now my solution is in Objective C so you'll have to convert it over to Swift, which shouldn't be that hard. I created a method that got the available storage space left on the device and then checked that against the size of the file we're downloading. My solution assumes you know the size of the file you download, in your case you can use the totalBytesExpectedToWrite parameter in the didWriteData method.

Here's what I did:

+ (unsigned long long)availableStorage
{
     unsigned long long totalFreeSpace = 0;
     NSError* error = nil;

     NSArray* paths = NSSearchPathForDirectoriesInDomain(NSDocumentDirectory, NSUserDomainMask, YES);
     NSDictionary* dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error:&error];
     if (dictionary)
     {
          NSNumber* freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
          totalFreeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue];
     }
     return totalFreeSpace;
}

Make sure you leave some room for error with these numbers, since iTunes, the settings app on the device, and this number never match up. The number we get here is the smallest of the three and is in MB. I hope this helps and let me know if you need help converting it to Swift.

Seth Kurkowski
  • 223
  • 2
  • 11
  • Thanks, this was one of my ideas - ask the filesystem for amount of free space left on the device in the didWriteData delegate method. Seemed like it might give the file system a hammering though, so I decided not to explore it yet. Might go back and profile that – Cuespeak Mar 22 '19 at 18:24
  • You can always have bool value and set it to false by default and when it's checked the first time in the `didWriteData` method switch it to true and not have to check it again for the rest of the download and switch it back to false when the download finishes. – Seth Kurkowski Mar 22 '19 at 18:32
  • Thanks, I think I'll use the `totalBytesExpectedToWrite` and on the first `didWriteData` check the available disk space. I've been thinking I had to wait for the disk full error, but maybe the better option is just to skip the download if it's not going to fit and do something else. I can cancel the downloadTask from this method if required – Cuespeak Mar 22 '19 at 19:02
  • Yeah, this is kinda an interesting situation, as a developer you have no control over how much your users download onto their device but you have to handle what happens if they do. Not much you can do other than to stop the download and tell the user to clear some space. – Seth Kurkowski Mar 22 '19 at 19:07