def read_or_create_download_tracker_file(source_object_resource,
                                         destination_url,
                                         slice_start_byte=0,
                                         existing_file_size=0,
                                         component_number=None,
                                         create=True):
    """Checks for a download tracker file and creates one if it does not exist.

  For normal downloads, if the tracker file exists, the existing_file_size
  in bytes is presumed to downloaded from the server. Therefore,
  existing_file_size becomes the download start point.

  For sliced downloads, the number of bytes previously retrieved from the server
  cannot be determined from existing_file_size. Therefore, it is retrieved
  from the tracker file.

  Args:
    source_object_resource (resource_reference.ObjectResource): Needed for
      object etag and generation.
    destination_url (storage_url.StorageUrl): Destination URL for tracker file.
    slice_start_byte (int): Start byte to use if we cannot find a
      matching tracker file for a download slice.
    existing_file_size (int): Amount of file on disk that already exists.
    component_number (int?): The download component number to find the start
      point for.
    create (bool): Creates tracker file if one could not be found.

  Returns:
    tracker_file_path (str?): The path to the tracker file, if one was used.
    download_start_byte (int): The first byte that still needs to be downloaded.

  Raises:
    ValueCannotBeDeterminedError: Source object resource does not have
      necessary metadata to decide on download start byte.
  """
    if not source_object_resource.etag:
        raise errors.ValueCannotBeDeterminedError(
            'Source object resource is missing etag.')

    tracker_file_path = None
    if (not source_object_resource.size
            or (source_object_resource.size <
                properties.VALUES.storage.resumable_threshold.GetInt())):
        # There is no tracker file for small downloads, so start from scratch.
        return tracker_file_path, slice_start_byte

    if component_number is None:
        tracker_file_type = TrackerFileType.DOWNLOAD
        download_name_for_logger = destination_url.object_name
    else:
        tracker_file_type = TrackerFileType.DOWNLOAD_COMPONENT
        download_name_for_logger = '{} component {}'.format(
            destination_url.object_name, component_number)

    tracker_file_path = get_tracker_file_path(
        destination_url, tracker_file_type, component_number=component_number)
    tracker_file = None
    # Check to see if we already have a matching tracker file.
    try:
        tracker_file = files.FileReader(tracker_file_path)
        if tracker_file_type is TrackerFileType.DOWNLOAD:
            etag_value = tracker_file.readline().rstrip('\n')
            if etag_value == source_object_resource.etag:
                log.debug(
                    'Found tracker file starting at byte {} for {}.'.format(
                        existing_file_size, download_name_for_logger))
                return tracker_file_path, existing_file_size
        elif tracker_file_type is TrackerFileType.DOWNLOAD_COMPONENT:
            component_data = json.loads(tracker_file.read())
            if (component_data['etag'] == source_object_resource.etag
                    and component_data['generation']
                    == source_object_resource.generation):
                start_byte = int(component_data['download_start_byte'])
                log.debug(
                    'Found tracker file starting at byte {} for {}.'.format(
                        start_byte, download_name_for_logger))
                return tracker_file_path, start_byte

    except files.MissingFileError:
        # Cannot read from file.
        pass

    finally:
        if tracker_file:
            tracker_file.close()

    if create:
        log.debug('No matching tracker file for {}.'.format(
            download_name_for_logger))
        if tracker_file_type is TrackerFileType.DOWNLOAD:
            _write_tracker_file(tracker_file_path,
                                source_object_resource.etag + '\n')
        elif tracker_file_type is TrackerFileType.DOWNLOAD_COMPONENT:
            write_download_component_tracker_file(tracker_file_path,
                                                  source_object_resource,
                                                  slice_start_byte)

    # No matching tracker file, so starting point is slice_start_byte.
    return tracker_file_path, slice_start_byte
 def is_container(self):
   raise errors.ValueCannotBeDeterminedError(
       'Unknown whether or not UnknownResource is a container.')
def read_or_create_download_tracker_file(source_object_resource,
                                         destination_url,
                                         slice_start_byte=None,
                                         component_number=None,
                                         total_components=None):
    """Checks for a download tracker file and creates one if it does not exist.

  Args:
    source_object_resource (resource_reference.ObjectResource): Needed for
      object etag and generation.
    destination_url (storage_url.StorageUrl): Destination URL for tracker file.
    slice_start_byte (int|None): Start byte to use if we cannot find a
      matching tracker file for a download slice.
    component_number (int|None): The download component number to find the start
      point for. Indicates part of a multi-component download.
    total_components (int|None): The number of components in a sliced download.
      Indicates this is the parent tracker for a multi-component operation.

  Returns:
    tracker_file_path (str): The path to the tracker file (found or created).
    found_tracker_file (bool): False if tracker file had to be created.

  Raises:
    ValueCannotBeDeterminedError: Source object resource does not have
      necessary metadata to decide on download start byte.
  """
    if not source_object_resource.etag:
        raise errors.ValueCannotBeDeterminedError(
            'Source object resource is missing etag.')
    if total_components and (slice_start_byte is not None
                             or component_number is not None):
        raise ValueError(
            'total_components indicates this is the parent tracker file for a'
            ' multi-component operation. slice_start_byte and component_number'
            ' cannot be present since this is not for an individual component.'
        )

    if component_number is not None:
        download_name_for_logger = '{} component {}'.format(
            destination_url.object_name, component_number)
        tracker_file_type = TrackerFileType.DOWNLOAD_COMPONENT
    else:
        download_name_for_logger = destination_url.object_name
        if total_components is not None:
            tracker_file_type = TrackerFileType.SLICED_DOWNLOAD
        else:
            tracker_file_type = TrackerFileType.DOWNLOAD

    tracker_file_path = get_tracker_file_path(
        destination_url, tracker_file_type, component_number=component_number)
    log.debug('Searching for tracker file at {}.'.format(tracker_file_path))
    tracker_file = None
    does_tracker_file_match = False
    # Check to see if we already have a matching tracker file.
    try:
        tracker_file = files.FileReader(tracker_file_path)
        if tracker_file_type is TrackerFileType.DOWNLOAD:
            etag_value = tracker_file.readline().rstrip('\n')
            if etag_value == source_object_resource.etag:
                does_tracker_file_match = True
        else:
            component_data = json.loads(tracker_file.read())
            if (component_data['etag'] == source_object_resource.etag
                    and component_data['generation']
                    == source_object_resource.generation):
                if (tracker_file_type is TrackerFileType.SLICED_DOWNLOAD
                        and component_data['total_components']
                        == total_components):
                    does_tracker_file_match = True
                elif tracker_file_type is TrackerFileType.DOWNLOAD_COMPONENT and component_data[
                        'slice_start_byte'] == slice_start_byte:
                    does_tracker_file_match = True

        if does_tracker_file_match:
            log.debug(
                'Found tracker file for {}.'.format(download_name_for_logger))
            return tracker_file_path, True

    except files.MissingFileError:
        # Cannot read from file.
        pass

    finally:
        if tracker_file:
            tracker_file.close()

    if tracker_file:
        # The tracker file exists, but it's not valid.
        delete_download_tracker_files(destination_url)

    log.debug(
        'No matching tracker file for {}.'.format(download_name_for_logger))
    if tracker_file_type is TrackerFileType.DOWNLOAD:
        _write_tracker_file(tracker_file_path,
                            source_object_resource.etag + '\n')
    elif tracker_file_type is TrackerFileType.DOWNLOAD_COMPONENT:
        write_tracker_file_with_component_data(
            tracker_file_path,
            source_object_resource,
            slice_start_byte=slice_start_byte)
    elif tracker_file_type is TrackerFileType.SLICED_DOWNLOAD:
        write_tracker_file_with_component_data(
            tracker_file_path,
            source_object_resource,
            total_components=total_components)
    return tracker_file_path, False
Exemple #4
0
def read_or_create_download_tracker_file(source_object_resource,
                                         destination_url,
                                         existing_file_size=None,
                                         slice_start_byte=None,
                                         component_number=None,
                                         total_components=None,
                                         create=True):
    """Checks for a download tracker file and creates one if it does not exist.

  For normal downloads, if the tracker file exists, the existing_file_size
  in bytes is presumed to downloaded from the server. Therefore,
  existing_file_size becomes the download start point.

  For sliced downloads, the number of bytes previously retrieved from the server
  cannot be determined from existing_file_size. Therefore, it is retrieved
  from the tracker file.

  Args:
    source_object_resource (resource_reference.ObjectResource): Needed for
      object etag and generation.
    destination_url (storage_url.StorageUrl): Destination URL for tracker file.
    existing_file_size (int): Amount of file on disk that already exists.
    slice_start_byte (int|None): Start byte to use if we cannot find a
      matching tracker file for a download slice.
    component_number (int|None): The download component number to find the start
      point for. Indicates part of a multi-component download.
    total_components (int|None): The number of components in a sliced download.
      Indicates this is the master tracker for a multi-component operation.
    create (bool): Creates tracker file if one could not be found.

  Returns:
    tracker_file_path (str|None): The path to the tracker file, if one was used.
    download_start_byte (int|None): The first byte that still needs to be
      downloaded, if not a sliced download.

  Raises:
    ValueCannotBeDeterminedError: Source object resource does not have
      necessary metadata to decide on download start byte.
  """
    if not source_object_resource.etag:
        raise errors.ValueCannotBeDeterminedError(
            'Source object resource is missing etag.')
    if total_components and (slice_start_byte is not None
                             or component_number is not None):
        raise ValueError(
            'total_components indicates this is the master tracker file for a'
            ' multi-component operation. slice_start_byte and component_number'
            ' cannot be present since this is not for an individual component.'
        )

    if component_number:
        download_name_for_logger = '{} component {}'.format(
            destination_url.object_name, component_number)
        tracker_file_type = TrackerFileType.DOWNLOAD_COMPONENT
    else:
        download_name_for_logger = destination_url.object_name
        if total_components:
            tracker_file_type = TrackerFileType.SLICED_DOWNLOAD
        else:
            tracker_file_type = TrackerFileType.DOWNLOAD

    tracker_file_path = get_tracker_file_path(
        destination_url, tracker_file_type, component_number=component_number)
    tracker_file = None
    # Check to see if we already have a matching tracker file.
    try:
        tracker_file = files.FileReader(tracker_file_path)
        if tracker_file_type is TrackerFileType.DOWNLOAD:
            etag_value = tracker_file.readline().rstrip('\n')
            if etag_value == source_object_resource.etag:
                log.debug(
                    'Found tracker file starting at byte {} for {}.'.format(
                        existing_file_size, download_name_for_logger))
                return tracker_file_path, existing_file_size
        else:
            component_data = json.loads(tracker_file.read())
            if (component_data['etag'] == source_object_resource.etag
                    and component_data['generation']
                    == source_object_resource.generation):
                if (tracker_file_type is TrackerFileType.SLICED_DOWNLOAD
                        and component_data['total_components']
                        == total_components):
                    log.debug(
                        'Found tracker file for sliced download {}.'.format(
                            download_name_for_logger))
                    return tracker_file_path, None
                elif tracker_file_type is TrackerFileType.DOWNLOAD_COMPONENT:
                    # Normal resumable download.
                    start_byte = int(component_data['download_start_byte'])
                    log.debug('Found tracker file starting at byte {} for {}.'.
                              format(start_byte, download_name_for_logger))
                    return tracker_file_path, start_byte

    except files.MissingFileError:
        # Cannot read from file.
        pass

    finally:
        if tracker_file:
            tracker_file.close()

    log.debug(
        'No matching tracker file for {}.'.format(download_name_for_logger))

    start_byte = 0
    if create:
        if tracker_file_type is TrackerFileType.DOWNLOAD:
            _write_tracker_file(tracker_file_path,
                                source_object_resource.etag + '\n')
        elif tracker_file_type is TrackerFileType.DOWNLOAD_COMPONENT:
            write_tracker_file_with_component_data(
                tracker_file_path,
                source_object_resource,
                download_start_byte=slice_start_byte)
            start_byte = slice_start_byte
        elif tracker_file_type is TrackerFileType.SLICED_DOWNLOAD:
            # Delete component tracker files to reset full sliced download.
            delete_download_tracker_files(destination_url)
            write_tracker_file_with_component_data(
                tracker_file_path,
                source_object_resource,
                total_components=total_components)
            start_byte = None

    return tracker_file_path, start_byte