onedrive/api_v5.py
def put_bits( self, path_or_tuple,
			folder_id=None, folder_path=None, frag_bytes=None,
			raw_id=False, chunk_callback=None ):
		'''Upload a file (object) using BITS API (via several http requests), possibly
				overwriting (default behavior) a file with the same "name" attribute, if it exists.

			Unlike "put" method, uploads to "folder_path" (instead of folder_id) are
				supported here. Either folder path or id can be specified, but not both.

			Passed "chunk_callback" function (if any) will be called after each
				uploaded chunk with keyword parameters corresponding to
				upload state and BITS session info required to resume it, if necessary.

			Returns id of the uploaded file, as retured by the API
				if raw_id=True is passed, otherwise in a consistent (with other calls)
				"file.{user_id}.{file_id}" format (default).'''
		# XXX: overwrite/downsize are not documented/supported here (yet?)
		name, src = self._process_upload_source(path_or_tuple)

		if folder_id is not None and folder_path is not None:
			raise ValueError('Either "folder_id" or "folder_path" can be specified, but not both.')
		if folder_id is None and folder_path is None: folder_id = 'me/skydrive'
		if folder_id and re.search(r'^me(/.*)$', folder_id): folder_id = self.info(folder_id)['id']
		if not frag_bytes: frag_bytes = self.api_bits_default_frag_bytes

		user_id = self.get_user_id()
		if folder_id: # workaround for API-ids inconsistency between BITS and regular API
			match = re.search( r'^(?i)folder.[a-f0-9]+.'
				'(?P<user_id>[a-f0-9]+(?P<folder_n>!\d+)?)$', folder_id )
			if match and not match.group('folder_n'):
				# root folder is a special case and can't seem to be accessed by id
				folder_id, folder_path = None, ''
			else:
				if not match:
					raise ValueError('Failed to process folder_id for BITS API: {!r}'.format(folder_id))
				folder_id = match.group('user_id')

		if folder_id:
			url = self.api_bits_url_by_id.format(folder_id=folder_id, user_id=user_id, filename=name)
		else:
			url = self.api_bits_url_by_path.format(
				folder_id=folder_id, user_id=user_id, file_path=ujoin(folder_path, name).lstrip('/') )

		code, headers, body = self(
			url, method='post', auth_header=True, raw_all=True,
			headers={
				'X-Http-Method-Override': 'BITS_POST',
				'BITS-Packet-Type': 'Create-Session',
				'BITS-Supported-Protocols': self.api_bits_protocol_id })

		h = lambda k,hs=dict((k.lower(), v) for k,v in headers.viewitems()): hs.get(k, '')
		checks = [ code == 201,
			h('bits-packet-type').lower() == 'ack',
			h('bits-protocol').lower() == self.api_bits_protocol_id.lower(),
			h('bits-session-id') ]
		if not all(checks):
			raise ProtocolError(code, 'Invalid BITS Create-Session response', headers, body, checks)
		bits_sid = h('bits-session-id')

		src.seek(0, os.SEEK_END)
		c, src_len = 0, src.tell()
		cn = src_len / frag_bytes
		if frag_bytes * cn != src_len: cn += 1
		src.seek(0)
		for n in xrange(1, cn+1):
			log.debug( 'Uploading BITS fragment'
				' %s / %s (max-size: %.2f MiB)', n, cn, frag_bytes / float(2**20) )
			frag = BITSFragment(src, frag_bytes)
			c1 = c + frag_bytes
			self(
				url, method='post', raw=True, data=frag,
				headers={
					'X-Http-Method-Override': 'BITS_POST',
					'BITS-Packet-Type': 'Fragment',
					'BITS-Session-Id': bits_sid,
					'Content-Range': 'bytes {}-{}/{}'.format(c, min(c1, src_len)-1, src_len) })
			c = c1

			if chunk_callback:
				chunk_callback(
					bytes_transferred=c, bytes_total=src_len,
					chunks_transferred=n, chunks_total=cn,
					bits_session_id=bits_sid )

		if self.api_bits_auth_refresh_before_commit_hack:
			# As per #39 and comments under the gist with the spec,
			#  apparently this trick fixes occasional http-5XX errors from the API
			self.auth_get_token()

		code, headers, body = self(
			url, method='post', auth_header=True, raw_all=True,
			headers={
				'X-Http-Method-Override': 'BITS_POST',
				'BITS-Packet-Type': 'Close-Session',
				'BITS-Session-Id': bits_sid })
		h = lambda k,hs=dict((k.lower(), v) for k,v in headers.viewitems()): hs.get(k, '')
		checks = [code in [200, 201], h('bits-packet-type').lower() == 'ack' ]
			# int(h('bits-received-content-range') or 0) == src_len -- documented, but missing
			# h('bits-session-id') == bits_sid -- documented, but missing
		if not all(checks):
			raise ProtocolError(code, 'Invalid BITS Close-Session response', headers, body, checks)

		# Workaround for API-ids inconsistency between BITS and regular API
		file_id = h('x-resource-id')
		if not raw_id: file_id = 'file.{}.{}'.format(user_id, file_id)
		return file_id
def put_bits( self, path_or_tuple,
			folder_id=None, folder_path=None, frag_bytes=None,
			raw_id=False, chunk_callback=None ):
		'''Upload a file (object) using BITS API (via several http requests), possibly
				overwriting (default behavior) a file with the same "name" attribute, if it exists.

			Unlike "put" method, uploads to "folder_path" (instead of folder_id) are
				supported here. Either folder path or id can be specified, but not both.

			Passed "chunk_callback" function (if any) will be called after each
				uploaded chunk with keyword parameters corresponding to
				upload state and BITS session info required to resume it, if necessary.

			Returns id of the uploaded file, as retured by the API
				if raw_id=True is passed, otherwise in a consistent (with other calls)
				"file.{user_id}.{file_id}" format (default).'''
		# XXX: overwrite/downsize are not documented/supported here (yet?)
		name, src = self._process_upload_source(path_or_tuple)

		if folder_id is not None and folder_path is not None:
			raise ValueError('Either "folder_id" or "folder_path" can be specified, but not both.')
		if folder_id is None and folder_path is None: folder_id = 'me/skydrive'
		if folder_id and re.search(r'^me(/.*)$', folder_id): folder_id = self.info(folder_id)['id']
		if not frag_bytes: frag_bytes = self.api_bits_default_frag_bytes

		user_id = self.get_user_id()
		if folder_id: # workaround for API-ids inconsistency between BITS and regular API
			match = re.search( r'^(?i)folder.[a-f0-9]+.'
				'(?P<user_id>[a-f0-9]+(?P<folder_n>!\d+)?)$', folder_id )
			if match and not match.group('folder_n'):
				# root folder is a special case and can't seem to be accessed by id
				folder_id, folder_path = None, ''
			else:
				if not match:
					raise ValueError('Failed to process folder_id for BITS API: {!r}'.format(folder_id))
				folder_id = match.group('user_id')

		if folder_id:
			url = self.api_bits_url_by_id.format(folder_id=folder_id, user_id=user_id, filename=name)
		else:
			url = self.api_bits_url_by_path.format(
				folder_id=folder_id, user_id=user_id, file_path=ujoin(folder_path, name).lstrip('/') )

		code, headers, body = self(
			url, method='post', auth_header=True, raw_all=True,
			headers={
				'X-Http-Method-Override': 'BITS_POST',
				'BITS-Packet-Type': 'Create-Session',
				'BITS-Supported-Protocols': self.api_bits_protocol_id })

		h = lambda k,hs=dict((k.lower(), v) for k,v in headers.viewitems()): hs.get(k, '')
		checks = [ code == 201,
			h('bits-packet-type').lower() == 'ack',
			h('bits-protocol').lower() == self.api_bits_protocol_id.lower(),
			h('bits-session-id') ]
		if not all(checks):
			raise ProtocolError(code, 'Invalid BITS Create-Session response', headers, body, checks)
		bits_sid = h('bits-session-id')

		src.seek(0, os.SEEK_END)
		c, src_len = 0, src.tell()
		cn = src_len / frag_bytes
		if frag_bytes * cn != src_len: cn += 1
		src.seek(0)
		for n in xrange(1, cn+1):
			log.debug( 'Uploading BITS fragment'
				' %s / %s (max-size: %.2f MiB)', n, cn, frag_bytes / float(2**20) )
			frag = BITSFragment(src, frag_bytes)
			c1 = c + frag_bytes
			self(
				url, method='post', raw=True, data=frag,
				headers={
					'X-Http-Method-Override': 'BITS_POST',
					'BITS-Packet-Type': 'Fragment',
					'BITS-Session-Id': bits_sid,
					'Content-Range': 'bytes {}-{}/{}'.format(c, min(c1, src_len)-1, src_len) })
			c = c1

			if chunk_callback:
				chunk_callback(
					bytes_transferred=c, bytes_total=src_len,
					chunks_transferred=n, chunks_total=cn,
					bits_session_id=bits_sid )

		if self.api_bits_auth_refresh_before_commit_hack:
			# As per #39 and comments under the gist with the spec,
			#  apparently this trick fixes occasional http-5XX errors from the API
			self.auth_get_token()

		code, headers, body = self(
			url, method='post', auth_header=True, raw_all=True,
			headers={
				'X-Http-Method-Override': 'BITS_POST',
				'BITS-Packet-Type': 'Close-Session',
				'BITS-Session-Id': bits_sid })
		h = lambda k,hs=dict((k.lower(), v) for k,v in headers.viewitems()): hs.get(k, '')
		checks = [code in [200, 201], h('bits-packet-type').lower() == 'ack' ]
			# int(h('bits-received-content-range') or 0) == src_len -- documented, but missing
			# h('bits-session-id') == bits_sid -- documented, but missing
		if not all(checks):
			raise ProtocolError(code, 'Invalid BITS Close-Session response', headers, body, checks)

		# Workaround for API-ids inconsistency between BITS and regular API
		file_id = h('x-resource-id')
		if not raw_id: file_id = 'file.{}.{}'.format(user_id, file_id)
		return file_id
Upload a file (object) using BITS API (via several http requests), possibly
				overwriting (default behavior) a file with the same "name" attribute, if it exists.

			Unlike "put" method, uploads to "folder_path" (instead of folder_id) are
				supported here. Either folder path or id can be specified, but not both.

			Passed "chunk_callback" function (if any) will be called after each
				uploaded chunk with keyword parameters corresponding to
				upload state and BITS session info required to resume it, if necessary.

			Returns id of the uploaded file, as retured by the API
				if raw_id=True is passed, otherwise in a consistent (with other calls)
				"file.{user_id}.{file_id}" format (default).