diff --git a/examples/files_session.py b/examples/files_session.py new file mode 100644 index 0000000..cf42362 --- /dev/null +++ b/examples/files_session.py @@ -0,0 +1,53 @@ +import os +from msgraph import api, user, files + +if __name__ == '__main__': + import argparse + authority_host_uri = 'https://login.microsoftonline.com' + tenant = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' + resource_uri = 'https://graph.microsoft.com' + client_id = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' + client_thumbprint = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + + parser = argparse.ArgumentParser(description='Microsoft Graph Files Test') + parser.add_argument('-t', '--tenant', dest='tenant', type=str, help='Microsoft Graph Tenant', default=os.getenv('DATACENTER')) + parser.add_argument('-c', '--clientid', dest='client_id', type=str, help='Microsoft Graph Client ID', default=os.getenv('CLIENTID')) + parser.add_argument('--thumbprint', dest='thumbprint', type=str, help='Microsoft Graph Secret', default=os.getenv('CLIENTSECRET')) + parser.add_argument('--certificate', dest='certificate', type=str, help='Microsoft Graph Certificate', default=os.getenv('CERTIFICATE')) + parser.add_argument('--user', dest='user', type=str, required=True, help='Microsoft Graph Test User') + parser.add_argument('--source', dest='src', type=str, required=True, help='Source file to upload') + parser.add_argument('--filename', dest='filename', type=str, required=True, help='File name to save as') + + args = parser.parse_args() + + if not os.path.exists(args.src): + raise ValueErr('%r does not exist' % args.src) + + if os.path.exists(args.certificate): + with open(args.certificate, 'rb') as input_file: + client_certificate = input_file.read() + else: + client_certificate = args.certificate + api_instance = api.GraphAPI.from_certificate(authority_host_uri, args.tenant, resource_uri, args.client_id, client_certificate, args.thumbprint) + + test_user = user.User.get(api_instance, args.user) + # fetch a OneDrive of a given user + drive = files.Drive.get(api_instance, user=test_user) + # fetch the OneDrive for a given site + + # fetch drives accessible by a given user + accessible_drives = files.Drive.accessible(api_instance, user=args.user) + print(accessible_drives) + + # fetch the root folder of the drive + drive_root_folder = files.DriveItem.root_folder(api_instance, drive=drive) + + # create a new folder in the root folder + new_folder = files.DriveItem.create_folder(api_instance, "Test", drive_root_folder, drive=drive) + + session = files.Session.create(api, new_folder, file_name=args.filename) + with open(args.src, 'rb') as input_file: + session.upload(api, input_file) + + # delete newly created folder + new_folder.delete(api_instance, drive=drive) diff --git a/msgraph/files.py b/msgraph/files.py index be848e9..639ebba 100644 --- a/msgraph/files.py +++ b/msgraph/files.py @@ -1,5 +1,6 @@ import logging from msgraph import base +from mspgraph import exception logger = logging.getLogger(__name__) @@ -507,3 +508,103 @@ def root_folder(cls, api, **kwargs): uri += '/root' data = api.request(uri) return cls.from_api(data) + + +class Session(base.Base): + __slots__ = ('upload_url', 'expiration_datetime', 'file_size') + FAIL = 'fail' + REPLACE = 'replace' + RENAME = 'rename' + + def __init__(self, upload_url, expiration_datetime, file_size): + self.upload_url = upload_url + self.expiration_datetime = expiration_datetime + self.file_size = file_size + self.next_expected_ranges = None + self.is_complete = False + + def upload(self, api, file, chunk_size=327680): + """ + Uploads a file to Microsoft drive in chunks + """ + while self.is_incomplete: + chunk = file.read(chunk_size) + output = self.upload_part(api, chunk) + return output + + def upload_part(self, api, content): + content_size = len(content) + if self.next_expected_ranges: + lower_bound, upper_bound = self.next_expected_ranges + if content_size < lower_bound or content_size > upper_bound: + raise exception.MicrosoftException('UPLOAD', 'content size (%r) not in expected range (%r-%r)' % (content_size, lower_bound, upper_bound)) + + headers = { + 'Content-Length': content_size, + } + response = api.request(self.upload_url, data=content, headers=headers, method='PUT') + + return self._update_state(response) + + def _update_state(self, data): + is_incomplete = 'expirationDateTime' in data and 'nextExpectedRanges' in data + if is_incomplete: + raw_expiration_datetime = data['expirationDateTime'] + self.expiration_datetime = self.parse_date_time(raw_expiration_datetime) + raw_next_expected_ranges = data['nextExpectedRanges'][0].split('-') + + raw_lower_bound = raw_next_expected_ranges[0] + lower_bound = int(raw_lower_bound) + + raw_upper_bound = raw_next_expected_ranges[1] + upper_bound = int(raw_upper_bound) if raw_upper_bound else float('inf') + self.next_expected_ranges = (lower_bound, upper_bound) + else: + self.is_complete = True + return data + + @classmethod + def from_api(cls, data): + upload_url = data['uploadUrl'] + raw_expiration_datetime = data['expirationDateTime'] + file_size = data['fileSize'] + expiration_datetime = cls.parse_date_time(raw_expiration_datetime) + return cls(upload_url, expiration_datetime, file_size) + + @classmethod + def create(cls, api, item, **kwargs): + group = kwargs.get('group') + site = kwargs.get('site') + drive = kwargs.get('drive') + user = kwargs.get('user') + if drive: + uri = 'drives/%s/items/%s/createUploadSession' % (drive, item) + elif group: + uri = 'groups/%s/drive/items/%s/createUploadSession' % (group, item) + elif site: + uri = 'sites/%s/drive/items/%s/createUploadSession' % (site, item) + elif user: + uri = 'users/%s/drive/items/%s/createUploadSession' % (user, item) + else: + uri = 'me/drive/items/%s/createUploadSession' % item + + item_data = { + "@odata.type": "microsoft.graph.driveItemUploadableProperties", + } + item_parameters = { + 'conflict_behavior': 'conflictBehavior', + 'file_size': 'fileSize', + 'file_name': 'name', + 'description': 'description' + } + for key in item_parameters: + value = kwargs.get(key) + if value: + parameter = item_parameters[key] + item_data[parameter] = value + + defer_commit = kwargs.get('defer_commit', False) + data = dict(item=item_data, defer_commit=defer_commit) + response = api.request(uri, json=data, method='POST') + response['fileSize'] = item_data.get('fileSize') + return cls.from_api(response)