<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 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)