docs: def DownloadCollection(coll_path,
                         target_path,
                         token=None,
                         overwrite=False,
                         dump_client_info=False,
                         flatten=False,
                         max_threads=10):
    """Iterate through a Collection object downloading all files.
  
    Args:
      coll_path: Path to an AFF4 collection.
      target_path: Base directory to write to.
      token: Token for access.
      overwrite: If True, overwrite existing files.
      dump_client_info: If True, this will detect client paths, and dump a yaml
        version of the client object to the root path. This is useful for seeing
        the hostname/users of the machine the client id refers to.
      flatten: If True, produce a "files" flat folder with links to all the found
        files.
      max_threads: Use this many threads to do the downloads.
    """
    completed_clients = set()
    coll = _OpenCollectionPath(coll_path)
    if coll is None:
      logging.error(
          "%s is not a valid collection. Typo? "
          "Are you sure something was written to it?", coll_path)
      return
  
    thread_pool = threadpool.ThreadPool.Factory("Downloader", max_threads)
    thread_pool.Start()
  
    # Extract the client id from the source urn. This code makes me
    # quite sad but there is just no concept of passing a client id in
    # the export tool (and this would be unsafe anyways since the user
    # could then download files from arbitrary machines easily). The
    # export tool is on the way to deprecation so we decided to do this
    # instead of fixing the obsolete code.
    try:
      collection_urn = coll.collection_id
    except AttributeError:
      collection_urn = coll.urn
  
    try:
      original_client_id = rdf_client.ClientURN(collection_urn.Split()[0])
    except IOError:
      original_client_id = None
  
    logging.info("Expecting to download %s files", len(coll))
  
    # Collections can include anything they want, but we only handle RDFURN and
    # StatEntry entries in this function.
    for grr_message in coll:
      source = None
      # If a raw message, work out the type.
      if isinstance(grr_message, rdf_flows.GrrMessage):
        source = grr_message.source
        grr_message = grr_message.payload
  
      if isinstance(grr_message, rdfvalue.RDFURN):
        urn = grr_message
      elif isinstance(grr_message, rdf_client_fs.StatEntry):
        urn = rdfvalue.RDFURN(grr_message.AFF4Path(source or original_client_id))
      elif isinstance(grr_message, rdf_file_finder.FileFinderResult):
        urn = rdfvalue.RDFURN(
            grr_message.stat_entry.AFF4Path(source or original_client_id))
      elif isinstance(grr_message, collectors.ArtifactFilesDownloaderResult):
        if grr_message.HasField("downloaded_file"):
          urn = grr_message.downloaded_file.AFF4Path(source or original_client_id)
        else:
          continue
      elif isinstance(grr_message, rdfvalue.RDFBytes):
        try:
          os.makedirs(target_path)
        except OSError:
          pass
        try:
          # We just dump out bytes and carry on.
          client_id = source.Split()[0]
          with open(os.path.join(target_path, client_id), "wb") as fd:
            fd.write(str(grr_message))
        except AttributeError:
          pass
        continue
      else:
        continue
  
      # Handle dumping client info, but only once per client.
      if dump_client_info:
        client_id = urn.Split()[0]
        re_match = aff4_grr.VFSGRRClient.CLIENT_ID_RE.match(client_id)
        if re_match and client_id not in completed_clients:
          args = (rdf_client.ClientURN(client_id), target_path, token, overwrite)
          thread_pool.AddTask(
              target=DumpClientYaml, args=args, name="ClientYamlDownloader")
          completed_clients.add(client_id)
  
      # Now queue downloading the actual files.
      args = (urn, target_path, token, overwrite)
      if flatten:
        target = CopyAndSymlinkAFF4ToLocal
      else:
        target = CopyAFF4ToLocal
      thread_pool.AddTask(target=target, args=args, name="Downloader")
  
    # Join and stop the threadpool.
    thread_pool.Stop(join_timeout=THREADPOOL_JOIN_TIMEOUT)