def _normalize_start_end(self, start, end=None): """Validate / fix up byte range. :type start: int :param start: start byte of the range: if negative, used as an offset from the end. :type end: int :param end: end byte of the range. :rtype: tuple, (start, end) :returns: the normalized start, end pair. :raises: :exc:`google.cloud.streaming.exceptions.TransferInvalidError` for invalid combinations of start, end. """ if end is not None: if start < 0: raise TransferInvalidError( 'Cannot have end index with negative start index') elif start >= self.total_size: raise TransferInvalidError( 'Cannot have start index greater than total size') end = min(end, self.total_size - 1) if end < start: raise TransferInvalidError( 'Range requested with end[%s] < start[%s]' % (end, start)) return start, end else: if start < 0: start = max(0, start + self.total_size) return start, self.total_size - 1
def _send_media_body(self, start): """Send the entire stream in a single request. Helper for :meth:`stream_file`: :type start: int :param start: start byte of the range. :rtype: :class:`google.cloud.streaming.http_wrapper.Response` :returns: The response from the media upload request. """ self._ensure_initialized() if self.total_size is None: raise TransferInvalidError( 'Total size must be known for SendMediaBody') body_stream = StreamSlice(self.stream, self.total_size - start) request = Request(url=self.url, http_method='PUT', body=body_stream) request.headers['Content-Type'] = self.mime_type if start == self.total_size: # End of an upload with 0 bytes left to send; just finalize. range_string = 'bytes */%s' % self.total_size else: range_string = 'bytes %s-%s/%s' % (start, self.total_size - 1, self.total_size) request.headers['Content-Range'] = range_string return self._send_media_request(request, self.total_size)
def _ensure_uninitialized(self): """Helper: assert that the instance is not initialized. :raises: :exc:`google.cloud.streaming.exceptions.TransferInvalidError` if the instance is already initialized. """ if self.initialized: raise TransferInvalidError('Cannot re-initialize %s', type(self).__name__)
def stream_file(self, use_chunks=True): """Upload the stream. :type use_chunks: boolean :param use_chunks: If False, send the stream in a single request. Otherwise, send it in chunks. :rtype: :class:`google.cloud.streaming.http_wrapper.Response` :returns: The response for the final request made. """ if self.strategy != RESUMABLE_UPLOAD: raise ValueError( 'Cannot stream non-resumable upload') # final_response is set if we resumed an already-completed upload. response = self._final_response send_func = self._send_chunk if use_chunks else self._send_media_body if use_chunks: self._validate_chunksize(self.chunksize) self._ensure_initialized() while not self.complete: response = send_func(self.stream.tell()) if response.status_code in (http_client.OK, http_client.CREATED): self._complete = True break self._progress = self._last_byte(response.info['range']) if self.progress + 1 != self.stream.tell(): raise CommunicationError( 'Failed to transfer all bytes in chunk, upload paused at ' 'byte %d' % self.progress) if self.complete and hasattr(self.stream, 'seek'): if not hasattr(self.stream, 'seekable') or self.stream.seekable(): current_pos = self.stream.tell() self.stream.seek(0, os.SEEK_END) end_pos = self.stream.tell() self.stream.seek(current_pos) if current_pos != end_pos: raise TransferInvalidError( 'Upload complete with %s ' 'additional bytes left in stream' % (int(end_pos) - int(current_pos))) return response