tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

output_manager.py (5432B)


      1 # Copyright 2017 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 
      6 import contextlib
      7 import logging
      8 import os
      9 import tempfile
     10 
     11 from devil.utils import reraiser_thread
     12 
     13 
     14 class Datatype:
     15  HTML = 'text/html'
     16  JSON = 'application/json'
     17  PNG = 'image/png'
     18  TEXT = 'text/plain'
     19 
     20 
     21 class OutputManager:
     22 
     23  def __init__(self):
     24    """OutputManager Constructor.
     25 
     26    This class provides a simple interface to save test output. Subclasses
     27    of this will allow users to save test results in the cloud or locally.
     28    """
     29    self._allow_upload = False
     30    self._thread_group = None
     31 
     32  @contextlib.contextmanager
     33  def ArchivedTempfile(
     34      self, out_filename, out_subdir, datatype=Datatype.TEXT):
     35    """Archive file contents asynchronously and then deletes file.
     36 
     37    Args:
     38      out_filename: Name for saved file.
     39      out_subdir: Directory to save |out_filename| to.
     40      datatype: Datatype of file.
     41 
     42    Returns:
     43      An ArchivedFile file. This file will be uploaded async when the context
     44      manager exits. AFTER the context manager exits, you can get the link to
     45      where the file will be stored using the Link() API. You can use typical
     46      file APIs to write and flish the ArchivedFile. You can also use file.name
     47      to get the local filepath to where the underlying file exists. If you do
     48      this, you are responsible of flushing the file before exiting the context
     49      manager.
     50    """
     51    if not self._allow_upload:
     52      raise Exception('Must run |SetUp| before attempting to upload!')
     53 
     54    f = self.CreateArchivedFile(out_filename, out_subdir, datatype)
     55    try:
     56      yield f
     57    finally:
     58      self.ArchiveArchivedFile(f, delete=True)
     59 
     60  def CreateArchivedFile(self, out_filename, out_subdir,
     61                         datatype=Datatype.TEXT):
     62    """Returns an instance of ArchivedFile."""
     63    return self._CreateArchivedFile(out_filename, out_subdir, datatype)
     64 
     65  def _CreateArchivedFile(self, out_filename, out_subdir, datatype):
     66    raise NotImplementedError
     67 
     68  def ArchiveArchivedFile(self, archived_file, delete=False):
     69    """Archive an ArchivedFile instance and optionally delete it."""
     70    if not isinstance(archived_file, ArchivedFile):
     71      raise Exception('Excepting an instance of ArchivedFile, got %s.' %
     72                      type(archived_file))
     73    archived_file.PrepareArchive()
     74 
     75    def archive():
     76      try:
     77        archived_file.Archive()
     78      finally:
     79        if delete:
     80          archived_file.Delete()
     81 
     82    thread = reraiser_thread.ReraiserThread(func=archive)
     83    thread.start()
     84    self._thread_group.Add(thread)
     85 
     86  def SetUp(self):
     87    self._allow_upload = True
     88    self._thread_group = reraiser_thread.ReraiserThreadGroup()
     89 
     90  def TearDown(self):
     91    self._allow_upload = False
     92    logging.info('Finishing archiving output.')
     93    self._thread_group.JoinAll()
     94 
     95  def __enter__(self):
     96    self.SetUp()
     97    return self
     98 
     99  def __exit__(self, _exc_type, _exc_val, _exc_tb):
    100    self.TearDown()
    101 
    102 
    103 class ArchivedFile:
    104 
    105  def __init__(self, out_filename, out_subdir, datatype):
    106    self._out_filename = out_filename
    107    self._out_subdir = out_subdir
    108    self._datatype = datatype
    109 
    110    mode = 'w+'
    111    if datatype == Datatype.PNG:
    112      mode = 'w+b'
    113    self._f = tempfile.NamedTemporaryFile(mode=mode, delete=False)
    114    self._ready_to_archive = False
    115 
    116  @property
    117  def name(self):
    118    return self._f.name
    119 
    120  def fileno(self, *args, **kwargs):
    121    if self._ready_to_archive:
    122      raise Exception('Cannot retrieve the integer file descriptor '
    123                      'after archiving has begun!')
    124    return self._f.fileno(*args, **kwargs)
    125 
    126  def write(self, *args, **kwargs):
    127    if self._ready_to_archive:
    128      raise Exception('Cannot write to file after archiving has begun!')
    129    self._f.write(*args, **kwargs)
    130 
    131  def flush(self, *args, **kwargs):
    132    if self._ready_to_archive:
    133      raise Exception('Cannot flush file after archiving has begun!')
    134    self._f.flush(*args, **kwargs)
    135 
    136  def Link(self):
    137    """Returns location of archived file."""
    138    if not self._ready_to_archive:
    139      raise Exception('Cannot get link to archived file before archiving '
    140                      'has begun')
    141    return self._Link()
    142 
    143  def _Link(self):
    144    """Note for when overriding this function.
    145 
    146    This function will certainly be called before the file
    147    has finished being archived. Therefore, this needs to be able to know the
    148    exact location of the archived file before it is finished being archived.
    149    """
    150    raise NotImplementedError
    151 
    152  def PrepareArchive(self):
    153    """Meant to be called synchronously to prepare file for async archiving."""
    154    self.flush()
    155    self._ready_to_archive = True
    156    self._PrepareArchive()
    157 
    158  def _PrepareArchive(self):
    159    """Note for when overriding this function.
    160 
    161    This function is needed for things such as computing the location of
    162    content addressed files. This is called after the file is written but
    163    before archiving has begun.
    164    """
    165 
    166  def Archive(self):
    167    """Archives file."""
    168    if not self._ready_to_archive:
    169      raise Exception('File is not ready to archive. Be sure you are not '
    170                      'writing to the file and PrepareArchive has been called')
    171    self._Archive()
    172 
    173  def _Archive(self):
    174    raise NotImplementedError
    175 
    176  def Delete(self):
    177    """Deletes the backing file."""
    178    self._f.close()
    179    os.remove(self.name)