DownloadManager

The ZDCDownloadManager is your one-stop-shop for downloading data from the cloud.

Recall that ZeroDark will automatically fetch the treesystem outline for you. That is, it will automatically fetch the metadata for each node, which includes information such as the node name, permissions, etc. However, it doesn't automatically download any node data (the content your app creates).

Thus you have a complete picture of the treesystem outline (i.e. a general idea of what the cloud looks like). But you’re in complete control when it comes to downloading the actual data (i.e. the node content, generated by your app). This allows you to optimize. For example:

  • speed up new app logins by not downloading old content
  • save disk space by deleting local copies of node data that are no longer being used
  • optimize per-device by downloading certain content on demand

When you’re ready to download the node content, the DownloadManager simplifies the process for you. It will automatically coalesce multiple requests to download the same item. And it supports optional background downloads on iOS, so that downloads can continue while the app is backgrounded (or even quit).

It also provides an NSProgress instance for all downloads, allowing you to display progress items in your UI. (Plus the framework injects additional information into the NSProgress, including "estimated time remaining" and bandwidth calculations.) And the DownloadManager works in concert with the ProgressManager to simplify UI development.

 


Full Downloads

You can download a node's data with just a few lines of code.

let options = ZDCDownloadOptions()
options.cacheToDiskManager = true
options.canDownloadWhileInBackground = true

zdc.downloadManager?.downloadNodeData( node,
                              options: options,
                      completionQueue: DispatchQueue.global())
{(cloudDataInfo: ZDCCloudDataInfo?, cryptoFile: ZDCCryptoFile?, error: Error?) in

   if let cloudDataInfo = cloudDataInfo,
      let cryptoFile = cryptoFile
    {
      // download succeeded
   } else {
      // download failed (maybe network disconnection ?)
   }
}

Stepping thru the code:

ZDCDownloadOptions

Allows you to pass in various options for the download. On iOS this includes whether or not to use a background download. And you also have the option of automatically caching the resulting download to the DiskManager using either of these options:

// Add to DiskManager's temporary storage pool.
// The max size of the storage pool is maintained by the DiskManager.
options.cacheToDiskManager = true

// Or add to DiskManager's permanent storage.
// File won't be deleted unless we manually delete it.
// Or unless the corresponding node is deleted.
options.savePersistentlyToDiskManager = true

 

ZDCCloudDataInfo

Provides various metadata about the downloaded file, such as its eTag & lastModified timestamp (as recorded in cloud storage). Also tells you whether or not the cloud file includes optional sections (i.e. a metadata or thumbnail section).

 

ZDCCryptoFile

Node data is always stored in the cloud in an encrypted format. So when we download it, we receive an encrypted file. The ZDCCryptoFile gives us everything we need to decrypt the file. And ZDCFileConversion contains a bunch of tools to decrypt & read the data.

If the file is small, we can simply read & decrypt it into memory:

zdc.downloadManager?.downloadNodeData(node,
                              options: options,
                      completionQueue: DispatchQueue.global())
{(cloudDataInfo: ZDCCloudDataInfo?, cryptoFile: ZDCCryptoFile?, error: Error?) in

  if let cryptoFile = cryptoFile {

    do {
      // Decrypt the file (into memory since it's small)
      //
      // We're already executing in a background thread (DispatchQueue.global).
      // So it's fine to perform synchronous disk IO here.

      let cleartext = try ZDCFileConversion.decryptCryptoFile(intoMemory: cryptoFile)

      // Process it!
      self.processDownloadedTask(cleartext, forNodeID: node.uuid)
    }
    catch {
      print("Error reading cryptoFile: \(error)")
    }

    // File cleanup.
    // Delete the file, unless the DiskManager is managing it.
    zdc.diskManager?.deleteFileIfUnmanaged(cryptoFile.fileURL)
  }
}

If the downloade file is large, then you've got several different options:

// You could decrypt it to file
ZDCFileConversion.decryptCryptoFile(cryptoFile, completionQueue: DispatchQueue.global())
{ (fileURL: URL?, error: Error?) in

  // Read decrypted file stored in fileURL
}

// Or you could open it as a stream
let stream = CloudFile2CleartextInputStream(cryptoFile: cryptoFile)

// Or you could open it for random-access
let reader = ZDCFileReader(cryptoFile: cryptoFile)

 


Metadata & Thumbnail Downloads

As discussed in the push article, you can include additional sections when uploading a node. This includes a metadata section and a thumbnail section. The primary benefit being that you can download these sections individually (without needing to download the entire node data).

let comps: ZDCNodeMetaComponents = .metadata

zdc.downloadManager!.downloadNodeMeta( node,
                           components: comps,
                              options: options,
                      completionQueue: DispatchQueue.global())
{(cloudDataInfo: ZDCCloudDataInfo?, metadata: Data?, thumbnail: Data?, error: Error?) in

  // data downloaded & decrypted for you
}

You can specify exactly which components you want to download:

// Download only the header section (ZDCCloudDataInfo)
let comps: ZDCNodeMetaComponents = .header

// Download only the metadata
let comps: ZDCNodeMetaComponents = .metadata

// Download only the thumbnail
let comps: ZDCNodeMetaComponents = .thumbnail

// Or any combination
let comps: ZDCNodeMetaComponents = [.metadata, .thumbnail]

 


Download Thumbnail via ImageManager

A common task is to display an image's thumbnail in the user interface somewhere. The ImageManager can help make this task simple:

let preFetch = {(image: UIImage?, willFetch: Bool) in

  // This closure is invoked BEFORE the fetchNodeThumbnail() function returns.

  cell.thumbnail.image = image ?? defaultImage
}

let postFetch = {(image: UIImage?, error: Error?) in

  // This closure is invoked LATER, after the download or disk-read has completed.

  if cell.taskID == taskID {
    cell.thumbnail.image = image ?? defaultImage
  } else {
    // The cell has been recycled. Ignore.
  }
}

zdc.imageManager?.fetchNodeThumbnail(imageNode, preFetch: preFetch, postFetch: postFetch)

The fetchNodeThumbnail function combines a lot of boiler-plate stuff, so you can focus on other stuff. Here's what it does:

  • First it checks to see if the requested thumbnail is sitting in the ImageManager's cache
  • If so, the cached image is returned via the preFetch closure (with its willFetch parameter set to false)
  • Otherwise, it checks to see if the requested resource is available in the DiskManager. If so, it will load it from disk asynchronously, and deliver it to you via the postFetch closure. (fetched from disk)
  • If not available in the DiskManager, then it will use the DownloadManager to download it.
  • After download, the resource gets cached in the DiskManager, and the downloaded image is delivered to you via the postFetch closure.