class_docstr: Manages downloading of products or parts of them.
  
  Intended for internal use, but may also be used directly if more fine-grained
  configuration or custom download logic is needed.
  
  Parameters
  ----------
  api : SentinelAPI
      A SentinelAPI instance.
  node_filter : callable, optional
      The callable is used to select which files of each product will be downloaded.
      If None (the default), the full products will be downloaded.
      See :mod:`sentinelsat.products` for sample node filters.
  verify_checksum : bool, default True
      If True, verify the downloaded files' integrity by checking its checksum.
      Throws InvalidChecksumError if the checksum does not match.
  fail_fast : bool, default False
      if True, all other downloads are cancelled when one of the downloads fails in :meth:`download_all()`.
  n_concurrent_dl : integer, optional
      Number of concurrent downloads.
      Defaults to the maximum allowed by :attr:`SentinelAPI.concurrent_dl_limit`.
  max_attempts : int, default 10
      Number of allowed retries before giving up downloading a product in :meth:`download_all()`.
  dl_retry_delay : float, default 10
      Number of seconds to wait between retrying of failed downloads.
  lta_retry_delay : float, default 60
      Number of seconds to wait between requests to the Long Term Archive.
  lta_timeout : float, optional
      Maximum number of seconds to wait for triggered products to come online.
      Defaults to no timeout.
docstr: Download a list of products.
  
  Parameters
  ----------
  products : list
      List of product IDs
  directory : string or Path, optional
      Directory where the files will be downloaded
  
  Notes
  ------
  By default, raises the most recent downloading exception if all downloads failed.
  If :attr:`Downloader.fail_fast` is set to True, raises the encountered exception on the first failed
  download instead.
  
  Returns
  -------
  dict[string, DownloadStatus]
      The status of all products.
  dict[string, Exception]
      Exception info for any failed products.
  dict[string, dict]
      A dictionary containing the product information for each product
      (unless the product was unavailable).
docstr_tok: ["Download","a","list","of","products",".","Parameters","--","--","--","--","--","products",":","list","List","of","product","IDs","directory",":","string","or","Path",",","optional","Directory","where","the","files","will","be","downloaded","Notes","--","--","--","By","default",",","raises","the","most","recent","downloading","exception","if","all","downloads","failed",".","If",":","attr",":","`","Downloader.fail_fast","`","is","set","to","True",",","raises","the","encountered","exception","on","the","first","failed","download","instead",".","Returns","--","--","--","-","dict","[","string",",","DownloadStatus","]","The","status","of","all","products",".","dict","[","string",",","Exception","]","Exception","info","for","any","failed","products",".","dict","[","string",",","dict","]","A","dictionary","containing","the","product","information","for","each","product","(","unless","the","product","was","unavailable",")","."]
code: def download_all(self, products, directory="."):
      """Download a list of products.
  
      Parameters
      ----------
      products : list
          List of product IDs
      directory : string or Path, optional
          Directory where the files will be downloaded
  
      Notes
      ------
      By default, raises the most recent downloading exception if all downloads failed.
      If :attr:`Downloader.fail_fast` is set to True, raises the encountered exception on the first failed
      download instead.
  
      Returns
      -------
      dict[string, DownloadStatus]
          The status of all products.
      dict[string, Exception]
          Exception info for any failed products.
      dict[string, dict]
          A dictionary containing the product information for each product
          (unless the product was unavailable).
      """
  
      ResultTuple = namedtuple(
          "ResultTuple", ["statuses", "exceptions", "product_infos"]
      )
      product_ids = list(set(products))
      assert self.n_concurrent_dl > 0
      if len(product_ids) == 0:
          return ResultTuple({}, {}, {})
      self.logger.info(
          "Will download %d products using %d workers",
          len(product_ids),
          self.n_concurrent_dl,
      )
  
      (
          statuses,
          online_prods,
          offline_prods,
          product_infos,
          exceptions,
      ) = self._init_statuses(product_ids)
  
      # Skip already downloaded files.
      # Although the download method also checks, we do not need to retrieve such
      # products from the LTA and use up our quota.
      self._skip_existing_products(
          directory, offline_prods, product_infos, statuses, exceptions
      )
  
      stop_event = threading.Event()
      dl_tasks = {}
      trigger_tasks = {}
  
      # Two separate threadpools for downloading and triggering of retrieval.
      # Otherwise triggering might take up all threads and nothing is downloaded.
      dl_count = len(online_prods) + len(offline_prods)
      dl_executor = ThreadPoolExecutor(
          max_workers=max(1, min(self.n_concurrent_dl, dl_count)),
          thread_name_prefix="dl",
      )
      dl_progress = self._tqdm(
          total=dl_count,
          desc="Downloading products",
          unit="product",
      )
      if offline_prods:
          trigger_executor = ThreadPoolExecutor(
              max_workers=min(
                  self.api.concurrent_lta_trigger_limit,
                  len(offline_prods),
              ),
              thread_name_prefix="trigger",
          )
          trigger_progress = self._tqdm(
              total=len(offline_prods),
              desc="LTA retrieval",
              unit="product",
              leave=True,
          )
      try:
          # First all online products are downloaded. Subsequently, offline products that might
          # have become available in the meantime are requested.
          for pid in itertools.chain(online_prods, offline_prods):
              future = dl_executor.submit(
                  self._download_online_retry,
                  product_infos[pid],
                  directory,
                  statuses,
                  exceptions,
                  stop_event,
              )
              dl_tasks[future] = pid
  
          for pid in offline_prods:
              future = trigger_executor.submit(
                  self._trigger_and_wait,
                  pid,
                  stop_event,
                  statuses,
              )
              trigger_tasks[future] = pid
  
          for task in concurrent.futures.as_completed(
              list(trigger_tasks) + list(dl_tasks)
          ):
              pid = trigger_tasks.get(task) or dl_tasks[task]
              exception = exceptions.get(pid)
              if task.cancelled():
                  exception = concurrent.futures.CancelledError()
              if task.exception():
                  exception = task.exception()
  
              if task in dl_tasks:
                  if not exception:
                      product_infos[pid] = task.result()
                      statuses[pid] = DownloadStatus.DOWNLOADED
                  dl_progress.update()
                  # Keep the LTA progress fresh
                  if offline_prods:
                      trigger_progress.update(0)
              else:
                  trigger_progress.update()
                  if all(t.done() for t in trigger_tasks):
                      trigger_progress.close()
  
              if exception:
                  exceptions[pid] = exception
                  if self.fail_fast:
                      raise exception from None
                  else:
                      self.logger.error(
                          "%s failed: %s",
                          pid,
                          _format_exception(exception),
                      )
      except:
          stop_event.set()
          for t in list(trigger_tasks) + list(dl_tasks):
              t.cancel()
          raise
      finally:
          dl_executor.shutdown()
          dl_progress.close()
          if offline_prods:
              trigger_executor.shutdown()
              trigger_progress.close()
  
      if not any(statuses):
          if not exceptions:
              raise SentinelAPIError(
                  "Downloading all products failed for an unknown reason"
              )
          exception = list(exceptions)[0]
          raise exception
  
      # Update Online status in product_infos
      for pid, status in statuses.items():
          if status in [
              DownloadStatus.OFFLINE,
              DownloadStatus.TRIGGERED,
          ]:
              product_infos[pid]["Online"] = False
          elif status != DownloadStatus.UNAVAILABLE:
              product_infos[pid]["Online"] = True
  
      return ResultTuple(statuses, exceptions, product_infos)
  
imports: import concurrent.futures
  import enum
  import itertools
  import shutil
  import threading
  import time
  import traceback
  from collections import namedtuple
  from concurrent.futures import ThreadPoolExecutor
  from contextlib import closing
  from pathlib import Path
  from typing import Any, Dict
  from xml.etree import ElementTree
  from sentinelsat.exceptions import InvalidChecksumError, LTAError, LTATriggered, SentinelAPIError, ServerError, UnauthorizedError
label_desc: Use image node_id 5 for calling the Downloader obj's underlying member method code with example usage: obj.download_all(products, directory) and returns: ResultTuple, ResultTuple