def upload(self, fp, size=None, progress_callback=None): """ Upload file contents. fp can be any file-like object, but if you don't specify it's size in advance it must support tell and seek methods. Progress callback is optional - if provided, it should match signature of ProgressCallbacks.upload_progress """ if isinstance(fp, six.binary_type): fp = six.BytesIO(fp) if size is None: size = base.get_file_size(fp) if size < self._upload_chunk_size: # simple, one request upload retries = max(self._upload_retries, 1) while retries > 0: url = self._client.get_url(self._url_template_content, path=self.path) chunk = base._FileChunk(fp, 0, size) r = self._client.POST(url, data=chunk, headers={'Content-length': str(size)}) exc.default.check_response(r) server_sha = r.headers['X-Sha512-Checksum'] our_sha = chunk.sha.hexdigest() if server_sha == our_sha: break retries -= 1 # TODO: retry network errors too if retries == 0: raise exc.ChecksumError("Failed to upload file", {}) else: # chunked upload return self._chunked_upload(fp, size, progress_callback)
def _chunked_upload(self, fp, size, progress_callback): url = self._client.get_url(self._url_template_content_chunked, path=self.path) chunks = list( base.split_file_into_chunks( fp, size, self._upload_chunk_size)) # need count of chunks chunk_count = len(chunks) headers = {} for chunk_number, chunk in enumerate(chunks, 1): # count from 1 not 0 headers['x-egnyte-chunk-num'] = "%d" % chunk_number headers['content-length'] = str(chunk.size) if chunk_number == chunk_count: # last chunk headers['x-egnyte-last-chunk'] = "true" retries = max(self._upload_retries, 1) while retries > 0: r = self._client.POST(url, data=chunk, headers=headers) server_sha = r.headers['x-egnyte-chunk-sha512-checksum'] our_sha = chunk.sha.hexdigest() if server_sha == our_sha: break retries -= 1 # TODO: retry network errors too # TODO: refactor common parts of chunked and standard upload if retries == 0: raise exc.ChecksumError("Failed to upload file chunk", { "chunk_number": chunk_number, "start_position": chunk.position }) exc.default.check_response(r) if chunk_number == 1: headers['x-egnyte-upload-id'] = r.headers['x-egnyte-upload-id'] if progress_callback is not None: progress_callback(self, size, chunk_number * self._upload_chunk_size)
def _upload_single_chunk(self, url, chunk_number, chunk, size, chunksize_mb, progress_callback, is_last_chunk=False, upload_id=None): headers = {} headers['x-egnyte-chunk-num'] = "%d" % chunk_number headers['content-length'] = chunk.size if is_last_chunk: headers['x-egnyte-last-chunk'] = "true" if upload_id: headers['x-egnyte-upload-id'] = upload_id retries = max(self._upload_retries, 1) while retries > 0: r = self._client.POST(url, data=chunk, headers=headers) server_sha = r.headers['x-egnyte-chunk-sha512-checksum'] our_sha = chunk.sha.hexdigest() if server_sha == our_sha: break retries -= 1 # TODO: retry network errors too # TODO: refactor common parts of chunked and standard upload if retries == 0: raise exc.ChecksumError("Failed to upload file chunk", { "chunk_number": chunk_number, "start_position": chunk.position }) exc.default.check_response(r) if progress_callback is not None: progress_callback(self, size, chunk_number * chunksize_mb * MEGABYTES) return r.headers