def __init__(self, provider_class, handler=Source): super(SourceService, self).__init__(provider_class) self._handler = handler addonid = KodiUtils.get_addon_info('id') Cache(addonid, 'page', 0).clear() Cache(addonid, 'children', 0).clear() Cache(addonid, 'items', 0).clear()
def __init__(self, request, client_address, server): self._system_monitor = KodiUtils.get_system_monitor() self._account_manager = AccountManager(server.service.profile_path) self._addonid = KodiUtils.get_addon_info('id') expiration = datetime.timedelta(minutes=KodiUtils.get_cache_expiration_time()) self._page_cache = Cache(self._addonid, 'page', expiration) self._children_cache = Cache(self._addonid, 'children', expiration) self._items_cache = Cache(self._addonid, 'items', expiration) BaseHandler.__init__(self, request, client_address, server)
def _clear_cache(self): Cache(self._addonid, 'page', 0).clear() Cache(self._addonid, 'children', 0).clear() Cache(self._addonid, 'items', 0).clear()
class Source(BaseHandler): _system_monitor = None _account_manager = None kilobyte = 1024.0 megabyte = kilobyte*kilobyte gigabyte = megabyte*kilobyte def __init__(self, request, client_address, server): self._system_monitor = KodiUtils.get_system_monitor() self._account_manager = AccountManager(server.service.profile_path) self._addonid = KodiUtils.get_addon_info('id') expiration = datetime.timedelta(minutes=KodiUtils.get_cache_expiration_time()) self._page_cache = Cache(self._addonid, 'page', expiration) self._children_cache = Cache(self._addonid, 'children', expiration) self._items_cache = Cache(self._addonid, 'items', expiration) BaseHandler.__init__(self, request, client_address, server) def __del__(self): del self._system_monitor del self._account_manager Logger.debug('Request destroyed.') def _get_provider(self): return self.server.data(source_mode = True) def open_table(self, title): title = urllib.unquote(title) html = XHTML('html') html.head.title(title) body = html.body body.h1(title) table = body.table() row = table.tr row.th.a('Name') row.th.a('Last modified') row.th.a('Size') row.th.a('Description') row = table.tr row.th(colspan='4').hr() return html, table def add_row(self, table, file_name, date=' - ', size=' - ', description=' '): row = table.tr row.td.a(file_name, href=urllib.quote(file_name)) row.td(date, align='right') row.td(size, align='right') row.td(description, escape=False) def close_table(self, table): table.tr.th(colspan='4').hr() def get_size(self, size): unit = '' if size > self.gigabyte: size = size / self.gigabyte unit = 'G' elif size > self.megabyte: size = size / self.megabyte unit = 'M' elif size > self.kilobyte: size = size / self.kilobyte unit = 'K' elif size < 0: return '-' return ("%.2f" % size) + unit def get_cloud_drive_addons(self): addons = [] response = KodiUtils.execute_json_rpc('Addons.GetAddons', {'type':'xbmc.python.pluginsource', 'enabled': True, 'properties': ['dependencies', 'name']}) for addon in Utils.get_safe_value(Utils.get_safe_value(response, 'result', {}), 'addons', []): for dependency in addon['dependencies']: if dependency['addonid'] == self._addonid: addons.append(addon) break return addons def get_addonid(self, addon_name): addons = self.get_cloud_drive_addons() addonid = None for addon in addons: if urllib.quote(Utils.str(addon['name'])) == addon_name: addonid = addon['addonid'] break return addonid def show_addon_list(self): html, table = self.open_table('Index of /') addons = self.get_cloud_drive_addons() if addons: for addon in addons: self.add_row(table, Utils.str(addon['name']) + '/') else: self.add_row(table, KodiUtils.get_addon_info('name') + '/') self.close_table(table) response = Utils.get_file_buffer() response.write(str(html)) return {'response_code': 200, 'content': response} def get_drive_list(self): drives = [] accounts = self._account_manager.get_accounts() provider = self._get_provider() for account_id in accounts: account = accounts[account_id] for drive in account['drives']: drive['display_name'] = self._account_manager.get_account_display_name(account, drive, provider) drives.append(drive) return drives def get_driveid(self, drive_name): driveid = None drives = self.get_drive_list() for drive in drives: if urllib.quote(Utils.str(drive['display_name'])) == drive_name: driveid = drive['id'] break return driveid def show_drives(self, addon_name): html, table = self.open_table('Index of /'+addon_name+'/') self.add_row(table, '../') drives = self.get_drive_list() for drive in drives: self.add_row(table, Utils.str(drive['display_name']) + '/') self.close_table(table) response = Utils.get_file_buffer() response.write(str(html)) return {'response_code': 200, 'content': response} def process_path(self, addon_name, drive_name, path): headers = {} response = Utils.get_file_buffer() driveid = self.get_driveid(drive_name) if driveid: parts = self.path.split('/') filename = parts[len(parts)-1] if filename: response_code = 303 if path: u = urlparse(path) path = u.path Logger.debug('query: %s' % u.query) if u.query == 'subtitles': response_code = 200 response.write(json.dumps({'driveid': driveid, 'subtitles': self.get_subtitles(driveid, path)})) else: key = '%s%s:children' % (driveid, path[0:path.rfind('/')],) Logger.debug('reading cache key: ' + key) children = self._children_cache.get(key) if not children and type(children) is NoneType: self.get_folder_items(driveid, path[0:path.rfind('/')+1]) url = self.get_download_url(driveid, path) headers['location'] = url Logger.debug('redirect to: ' + url) else: url = self.path + '/' headers['location'] = url else: response_code = 200 response.write(str(self.show_folder(driveid, path))) else: response_code = 404 response.write('Drive "%s" does not exist for addon "%s"' % (drive_name, addon_name)) return {'response_code': response_code, 'content': response, 'headers': headers} def get_folder_items(self, driveid, path): provider = self._get_provider() provider.configure(self._account_manager, driveid) cache_path = path[:len(path)-1] request_path = cache_path if len(path) > 1 else path self.is_path_possible(driveid, request_path) key = '%s%s:items' % (driveid, cache_path,) items = self._items_cache.get(key) if not items and type(items) is NoneType: items = provider.get_folder_items(path=request_path, include_download_info=True) self._items_cache.set(key, items) children_names = [] cache_items = [] for item in items: quoted_name = urllib.quote(Utils.str(item['name'])) children_names.append(quoted_name) key = '%s%s%s' % (driveid, path, quoted_name,) Logger.debug('Adding item in cache for bulk: %s' % key) cache_items.append([key, item]) self._items_cache.setmany(cache_items) Logger.debug('Cache in bulk saved') key = '%s%s:children' % (driveid, cache_path,) Logger.debug('saving children names for: ' + key) self._children_cache.set(key, children_names) else: Logger.debug('items for %s served from cache' % path) return items def show_folder(self, driveid, path): items = self.get_folder_items(driveid, path) html, table = self.open_table('Index of ' + self.path) self.add_row(table, '../') for item in items: file_name = Utils.str(item['name']) if 'folder' in item: file_name += '/' date = Utils.default(self.date_time_string(KodiUtils.to_timestamp(Utils.get_safe_value(item, 'last_modified_date'))), ' - ') size = self.get_size(Utils.default(Utils.get_safe_value(item, 'size'), -1)) description = Utils.default(Utils.get_safe_value(item, 'description'), ' ') self.add_row(table, file_name, date, size, description) self.close_table(table) return html def is_path_possible(self, driveid, path): index = path.rfind('/') while index >= 0: filename = path[index+1:] path = path[0:index] key = '%s%s:children' % (driveid, path,) Logger.debug('testing possible path key: ' + key) children = self._children_cache.get(key) if children or type(children) is list: if filename and not filename in children: Logger.debug('Not found. From cache.') raise RequestException('Not found. From cache.', HTTPError(self.path, 404, 'Not found.', None, None), 'Request URL: %s' % self.path, None) return True index = path.rfind('/') return True def get_item(self, driveid, path): key = '%s%s' % (driveid, path,) Logger.debug('Testing item from cache: %s' % key) item = self._items_cache.get(key) if not item: provider = self._get_provider() provider.configure(self._account_manager, driveid) self.is_path_possible(driveid, path) item = provider.get_item(path=path, include_download_info = True) Logger.debug('Saving item in cache: %s' % key) self._items_cache.set(key, item) return item def get_download_url(self, driveid, path): item = self.get_item(driveid, path) if 'folder' in item: return self.path + '/' return item['download_info']['url'] def get_subtitles(self, driveid, path): item = self.get_item(driveid, path) key = '%s%s-subtitles' % (driveid, path,) Logger.debug('Testing subtitles from cache: %s' % key) subtitles = self._items_cache.get(key) if not subtitles: provider = self._get_provider() provider.configure(self._account_manager, driveid) self.is_path_possible(driveid, path) item_driveid = Utils.default(Utils.get_safe_value(item, 'drive_id'), driveid) subtitles = provider.get_subtitles(item['parent'], item['name'], item_driveid) Logger.debug('Saving subtitles in cache: %s' % key) self._items_cache.set(key, item) return subtitles def handle_resource_request(self, data): addon_name = data[2] size = len(data) cached_page = {} if size == 3: cached_page['response_code'] = 303 cached_page['headers'] = {'location': self.path + '/'} elif size == 4 and not data[3]: cached_page = self.show_drives(addon_name) else: drive_name = data[3] path = self.path[len(self.server.service.name)+len(addon_name)+len(drive_name)+3:] cached_page = self.process_path(addon_name, drive_name, path) return cached_page def do_GET(self): Logger.debug(self.path + ': Requested') if self._system_monitor.abortRequested(): Logger.debug(self.path + ': abort requested') return data = self.path.split('/') size = len(data) cached_page = self._page_cache.get(self.path) if cached_page: if cached_page['pending']: Logger.debug(self.path + ': Already requested. Waiting for original request...') max_waiting_time = time.time() + 30 while not self._system_monitor.abortRequested() and max_waiting_time > time.time() and cached_page['pending']: if self._system_monitor.waitForAbort(1): break cached_page = self._page_cache.get(self.path) if not self._system_monitor.abortRequested(): if cached_page['pending']: self.write_response(504) Logger.debug(self.path + ': 504 - Gateway timeout') self._page_cache.remove(self.path) else: if 'content' in cached_page and cached_page['content']: content = Utils.get_file_buffer() content.write(cached_page['content']) cached_page['content'] = content self.write_response(cached_page['response_code'], content=Utils.get_safe_value(cached_page, 'content'), headers=Utils.get_safe_value(cached_page, 'headers', {})) Logger.debug(self.path + ': %d - Served from cache' % cached_page['response_code']) else: cached_page = {'pending': True} self._page_cache.set(self.path, cached_page) if size > 1 and data[1] == self.server.service.name: try: if size == 2: cached_page['response_code'] = 303 cached_page['headers'] = {'location': self.path + '/'} elif size > 2 and data[2]: cached_page = self.handle_resource_request(data) else: cached_page = self.show_addon_list() except Exception as e: httpex = ExceptionUtils.extract_exception(e, HTTPError) if httpex: cached_page['response_code'] = httpex.code else: cached_page['response_code'] = 500 ErrorReport.handle_exception(e) content = Utils.get_file_buffer() content.write(ExceptionUtils.full_stacktrace(e)) cached_page['content'] = content else: cached_page['response_code'] = 404 cached_page['pending'] = False content_value = None if 'content' in cached_page: content_value = cached_page['content'].getvalue() self.write_response(cached_page['response_code'], content=Utils.get_safe_value(cached_page, 'content'), headers=Utils.get_safe_value(cached_page, 'headers', {})) cached_page['content'] = content_value if Utils.get_safe_value(cached_page, 'response_code', 0) >= 500: self._page_cache.remove(self.path) else: self._page_cache.set(self.path, cached_page) Logger.debug(self.path + ': Response code ' + Utils.str(cached_page['response_code']))
def __init__(self, source_mode = False): super(GoogleDrive, self).__init__('googledrive', source_mode) self._items_cache = Cache(KodiUtils.get_addon_info('id'), 'items', datetime.timedelta(minutes=KodiUtils.get_cache_expiration_time()))
class GoogleDrive(Provider): _default_parameters = {'spaces': 'drive', 'prettyPrint': 'false'} _is_team_drive = False _team_drive_parameters = {'includeTeamDriveItems': 'true', 'supportsTeamDrives': 'true', 'corpora': 'teamDrive', 'teamDriveId': ''} _user = None def __init__(self, source_mode = False): super(GoogleDrive, self).__init__('googledrive', source_mode) self._items_cache = Cache(KodiUtils.get_addon_info('id'), 'items', datetime.timedelta(minutes=KodiUtils.get_cache_expiration_time())) def configure(self, account_manager, driveid): super(GoogleDrive, self).configure(account_manager, driveid) self._account_manager.load() drive = account_manager.get_drive_by_driveid(driveid) self._is_team_drive = drive and 'type' in drive and drive['type'] == 'drive#teamDrive' def _get_api_url(self): return 'https://www.googleapis.com/drive/v3' def _get_request_headers(self): return None def get_account(self, request_params=None, access_tokens=None): me = self.get('/about', parameters={'fields':'user'}, request_params=request_params, access_tokens=access_tokens) if not me or not 'user' in me: raise Exception('NoAccountInfo') self._user = me['user'] return { 'id' : self._user['permissionId'], 'name' : self._user['displayName']} def get_drives(self, request_params=None, access_tokens=None): drives = [{ 'id' : self._user['permissionId'], 'name' : '', 'type' : '' }] try: all_teamdrives_fetch = False page_token = None parameters = {'pageSize': 100} while not all_teamdrives_fetch: if page_token: parameters['nextPageToken'] = page_token response = self.get('/teamdrives', parameters=parameters, request_params=request_params, access_tokens=access_tokens) if response and 'teamDrives' in response: for drive in response['teamDrives']: drives.append({ 'id' : drive['id'], 'name' : Utils.get_safe_value(drive, 'name', drive['id']), 'type' : drive['kind'] }) if response and 'nextPageToken' in response: page_token = response['nextPageToken'] else: all_teamdrives_fetch = True except RequestException as ex: httpex = ExceptionUtils.extract_exception(ex, HTTPError) if not httpex or httpex.code != 403: raise ex return drives def get_drive_type_name(self, drive_type): if drive_type == 'drive#teamDrive': return 'Team Drive' return drive_type def prepare_parameters(self): parameters = copy.deepcopy(self._default_parameters) if self._is_team_drive: parameters.update(self._team_drive_parameters) parameters['teamDriveId'] = self._driveid return parameters def _get_field_parameters(self): file_fileds = 'id,name,modifiedTime,size,mimeType' if not self.source_mode: file_fileds = file_fileds + ',description,hasThumbnail,thumbnailLink,owners(permissionId),parents,trashed,imageMediaMetadata(width),videoMediaMetadata' return file_fileds def get_folder_items(self, item_driveid=None, item_id=None, path=None, on_items_page_completed=None, include_download_info=False): item_driveid = Utils.default(item_driveid, self._driveid) is_album = item_id and item_id[:6] == 'album-' if is_album: item_id = item_id[6:] parameters = self.prepare_parameters() if item_id: parameters['q'] = '\'%s\' in parents' % item_id elif path == 'sharedWithMe' or path == 'starred': parameters['q'] = path elif path != 'photos': if path == '/': parent = self._driveid if self._is_team_drive else 'root' parameters['q'] = '\'%s\' in parents' % parent elif not is_album: item = self.get_item_by_path(path, include_download_info) parameters['q'] = '\'%s\' in parents' % item['id'] parameters['fields'] = 'files(%s),kind,nextPageToken' % self._get_field_parameters() if 'q' in parameters: parameters['q'] += ' and not trashed' self.configure(self._account_manager, self._driveid) provider_method = self.get url = '/files' if path == 'photos': self._photos_provider = GooglePhotos() self._photos_provider.configure(self._account_manager, self._driveid) parameters = {} provider_method = self._photos_provider.get url = '/albums' elif is_album: self._photos_provider = GooglePhotos() self._photos_provider.configure(self._account_manager, self._driveid) parameters = {'albumId': item_id} provider_method = self._photos_provider.post url = '/mediaItems:search' files = provider_method(url, parameters = parameters) if self.cancel_operation(): return return self.process_files(files, parameters, on_items_page_completed, include_download_info) def search(self, query, item_driveid=None, item_id=None, on_items_page_completed=None): item_driveid = Utils.default(item_driveid, self._driveid) parameters = self.prepare_parameters() parameters['fields'] = 'files(%s)' % self._get_field_parameters() query = 'fullText contains \'%s\'' % Utils.str(query) if item_id: query += ' and \'%s\' in parents' % item_id parameters['q'] = query + ' and not trashed' files = self.get('/files', parameters = parameters) if self.cancel_operation(): return return self.process_files(files, parameters, on_items_page_completed) def process_files(self, files, parameters, on_items_page_completed=None, include_download_info=False, extra_info=None): items = [] if files: kind = Utils.get_safe_value(files, 'kind', '') collection = [] if kind == 'drive#fileList': collection = files['files'] elif kind == 'drive#changeList': collection = files['changes'] elif 'albums' in files: kind = 'album' collection = files['albums'] elif 'mediaItems' in files: kind = 'media_item' collection = files['mediaItems'] if collection: for f in collection: f['kind'] = Utils.get_safe_value(f, 'kind', kind) item = self._extract_item(f, include_download_info) if item: items.append(item) if on_items_page_completed: on_items_page_completed(items) if type(extra_info) is dict: if 'newStartPageToken' in files: extra_info['change_token'] = files['newStartPageToken'] if 'nextPageToken' in files: parameters['pageToken'] = files['nextPageToken'] url = '/files' provider_method = self.get if kind == 'drive#changeList': url = '/changes' elif kind == 'album': url = '/albums' provider_method = self._photos_provider.get elif kind == 'media_item': url = '/mediaItems:search' provider_method = self._photos_provider.post next_files = provider_method(url, parameters = parameters) if self.cancel_operation(): return items.extend(self.process_files(next_files, parameters, on_items_page_completed, include_download_info, extra_info)) return items def _extract_item(self, f, include_download_info=False): kind = Utils.get_safe_value(f, 'kind', '') if kind == 'drive#change': if 'file' in f: f = f['file'] else: return {} size = long('%s' % Utils.get_safe_value(f, 'size', 0)) is_album = kind == 'album' is_media_items = kind == 'media_item' item_id = f['id'] if is_album: mimetype = 'application/vnd.google-apps.folder' name = Utils.get_safe_value(f, 'title', item_id) else: mimetype = Utils.get_safe_value(f, 'mimeType', '') name = Utils.get_safe_value(f, 'name', '') if is_media_items: name = Utils.get_safe_value(f, 'filename', item_id) item = { 'id': item_id, 'name': name, 'name_extension' : Utils.get_extension(name), 'parent': Utils.get_safe_value(f, 'parents', ['root'])[0], 'drive_id' : Utils.get_safe_value(Utils.get_safe_value(f, 'owners', [{}])[0], 'permissionId'), 'mimetype' : mimetype, 'last_modified_date' : Utils.get_safe_value(f,'modifiedTime'), 'size': size, 'description': Utils.get_safe_value(f, 'description', ''), 'deleted' : Utils.get_safe_value(f, 'trashed', False) } if item['mimetype'] == 'application/vnd.google-apps.folder': item['folder'] = { 'child_count' : 0 } if is_media_items: item['url'] = f['baseUrl'] + '=d' if 'mediaMetadata' in f: metadata = f['mediaMetadata'] item['video'] = { 'width' : Utils.get_safe_value(metadata, 'width'), 'height' : Utils.get_safe_value(metadata, 'height') } item['last_modified_date'] = Utils.get_safe_value(metadata, 'creationTime') if 'videoMediaMetadata' in f: video = f['videoMediaMetadata'] item['video'] = { 'width' : Utils.get_safe_value(video, 'width'), 'height' : Utils.get_safe_value(video, 'height'), 'duration' : long('%s' % Utils.get_safe_value(video, 'durationMillis', 0)) / 1000 } if 'imageMediaMetadata' in f or 'mediaMetadata' in f: item['image'] = { 'size' : size } if 'hasThumbnail' in f and f['hasThumbnail']: item['thumbnail'] = Utils.get_safe_value(f, 'thumbnailLink') if is_album: item['thumbnail'] = Utils.get_safe_value(f, 'coverPhotoBaseUrl') item['id'] = 'album-' + item['id'] if include_download_info: if is_media_items: item['download_info'] = { 'url' : item['url'] } else: parameters = { 'alt': 'media', 'access_token': self.get_access_tokens()['access_token'] } url = self._get_api_url() + '/files/%s' % item['id'] if 'size' not in f and item['mimetype'] == 'application/vnd.google-apps.document': url += '/export' parameters['mimeType'] = Utils.default(Utils.get_mimetype_by_extension(item['name_extension']), Utils.get_mimetype_by_extension('pdf')) item['download_info'] = { 'url' : url + '?%s' % urllib.urlencode(parameters) } return item def get_item_by_path(self, path, include_download_info=False): parameters = self.prepare_parameters() if path[-1:] == '/': path = path[:-1] Logger.debug(path + ' <- Target') key = '%s%s' % (self._driveid, path,) Logger.debug('Testing item from cache: %s' % key) item = self._items_cache.get(key) if not item: parameters['fields'] = 'files(%s)' % self._get_field_parameters() index = path.rfind('/') filename = urllib.unquote(path[index+1:]) parent = path[0:index] if not parent: parent = 'root' else: parent = self.get_item_by_path(parent, include_download_info)['id'] item = None parameters['q'] = '\'%s\' in parents and name = \'%s\'' % (Utils.str(parent), Utils.str(filename).replace("'","\\'")) files = self.get('/files', parameters = parameters) if (len(files['files']) > 0): for f in files['files']: item = self._extract_item(f, include_download_info) break else: Logger.debug('Found in cache.') if not item: raise RequestException('Not found by path', HTTPError(path, 404, 'Not found', None, None), 'Request URL: %s' % path, None) else: self._items_cache.set(key, item) return item def get_subtitles(self, parent, name, item_driveid=None, include_download_info=False): parameters = self.prepare_parameters() item_driveid = Utils.default(item_driveid, self._driveid) subtitles = [] parameters['fields'] = 'files(' + self._get_field_parameters() + ')' parameters['q'] = 'name contains \'%s\'' % Utils.str(Utils.remove_extension(name)).replace("'","\\'") files = self.get('/files', parameters = parameters) for f in files['files']: subtitle = self._extract_item(f, include_download_info) if subtitle['name_extension'].lower() in ('srt','idx','sub','sbv','ass','ssa'): subtitles.append(subtitle) return subtitles def get_item(self, item_driveid=None, item_id=None, path=None, find_subtitles=False, include_download_info=False): parameters = self.prepare_parameters() item_driveid = Utils.default(item_driveid, self._driveid) parameters['fields'] = self._get_field_parameters() if not item_id and path == '/': item_id = 'root' if item_id: f = self.get('/files/%s' % item_id, parameters = parameters) item = self._extract_item(f, include_download_info) else: item = self.get_item_by_path(path, include_download_info) if find_subtitles: subtitles = self.get_subtitles(item['parent'], item['name'], item_driveid, include_download_info) if subtitles: item['subtitles'] = subtitles return item def changes(self): change_token = self.get_change_token() if not change_token: change_token = Utils.get_safe_value(self.get('/changes/startPageToken', parameters = self.prepare_parameters()), 'startPageToken') extra_info = {} parameters = self.prepare_parameters() parameters['pageToken'] = change_token parameters['fields'] = 'kind,nextPageToken,newStartPageToken,changes(kind,type,removed,file(%s))' % self._get_field_parameters() f = self.get('/changes', parameters = parameters) changes = self.process_files(f, parameters, include_download_info=True, extra_info=extra_info) self.persist_change_token(Utils.get_safe_value(extra_info, 'change_token')) return changes
class GoogleDrive(Provider): _default_parameters = {'spaces': 'drive', 'prettyPrint': 'false'} _file_fileds = 'id,name,mimeType,description,hasThumbnail,thumbnailLink,modifiedTime,owners(permissionId),parents,size,imageMediaMetadata(width),videoMediaMetadata' _extension_map = { 'html': 'text/html', 'htm': 'text/html', 'txt': 'text/plain', 'rtf': 'application/rtf', 'odf': 'application/vnd.oasis.opendocument.text', 'pdf': 'application/pdf', 'doc': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'epub': 'application/epub+zip', 'xls': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'sxc': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'csv': 'text/csv', 'ppt': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'sxi': 'application/vnd.oasis.opendocument.presentation', 'json': 'application/vnd.google-apps.script+json' } _user = None def __init__(self): super(GoogleDrive, self).__init__('googledrive') self._items_cache = Cache( KodiUtils.get_addon_info('id'), 'items', datetime.timedelta(minutes=KodiUtils.get_cache_expiration_time())) def _get_api_url(self): return 'https://www.googleapis.com/drive/v3' def _get_request_headers(self): return None def get_account(self, request_params=None, access_tokens=None): me = self.get('/about', parameters={'fields': 'user'}, request_params=request_params, access_tokens=access_tokens) if not me or not 'user' in me: raise Exception('NoAccountInfo') self._user = me['user'] return { 'id': self._user['permissionId'], 'name': self._user['displayName'] } def get_drives(self, request_params=None, access_tokens=None): drives = [{'id': self._user['permissionId'], 'name': '', 'type': ''}] return drives def get_folder_items(self, item_driveid=None, item_id=None, path=None, on_items_page_completed=None, include_download_info=False): item_driveid = Utils.default(item_driveid, self._driveid) is_album = item_id and item_id[:6] == 'album-' if is_album: Logger.notice(item_id) item_id = item_id[6:] Logger.notice(item_id) parameters = copy.deepcopy(self._default_parameters) if item_id: parameters['q'] = '\'%s\' in parents' % item_id elif path == 'sharedWithMe' or path == 'starred': parameters['q'] = path elif path != 'photos': if path == '/': parameters['q'] = '\'root\' in parents' elif not is_album: item = self.get_item_by_path(path, include_download_info) parameters['q'] = '\'%s\' in parents' % item['id'] parameters['fields'] = 'files(%s),nextPageToken' % self._file_fileds if 'q' in parameters: parameters['q'] += ' and not trashed' if path == 'photos': self._photos_provider = GooglePhotos() Logger.notice(self._get_api_url()) self._photos_provider.configure(self._account_manager, self._driveid) files = self._photos_provider.get('/albums') files['is_album'] = True elif is_album: self._photos_provider = GooglePhotos() self._photos_provider.configure(self._account_manager, self._driveid) files = self._photos_provider.post('/mediaItems:search', parameters={'albumId': item_id}) files['is_media_items'] = True else: self.configure(self._account_manager, self._driveid) files = self.get('/files', parameters=parameters) files['is_album'] = False if self.cancel_operation(): return return self.process_files(files, parameters, on_items_page_completed, include_download_info) def search(self, query, item_driveid=None, item_id=None, on_items_page_completed=None): item_driveid = Utils.default(item_driveid, self._driveid) parameters = copy.deepcopy(self._default_parameters) parameters['fields'] = 'files(%s)' % self._file_fileds query = 'fullText contains \'%s\'' % Utils.str(query) if item_id: query += ' and \'%s\' in parents' % item_id parameters['q'] = query + ' and not trashed' files = self.get('/files', parameters=parameters) if self.cancel_operation(): return return self.process_files(files, parameters, on_items_page_completed) def process_files(self, files, parameters, on_items_page_completed=None, include_download_info=False): items = [] if files: is_album = Utils.get_safe_value(files, 'is_album', False) is_media_items = Utils.get_safe_value(files, 'is_media_items', False) if is_album: collection = 'albums' elif is_media_items: collection = 'mediaItems' else: collection = 'files' if collection in files: for f in files[collection]: f['is_album'] = is_album f['is_media_items'] = is_media_items item = self._extract_item(f, include_download_info) items.append(item) if on_items_page_completed: on_items_page_completed(items) if 'nextPageToken' in files: parameters['pageToken'] = files['nextPageToken'] next_files = self.get('/files', parameters=parameters) if self.cancel_operation(): return next_files['is_album'] = is_album items.extend( self.process_files(next_files, parameters, on_items_page_completed, include_download_info)) return items def _extract_item(self, f, include_download_info=False): size = long('%s' % Utils.get_safe_value(f, 'size', 0)) is_album = Utils.get_safe_value(f, 'is_album', False) is_media_items = Utils.get_safe_value(f, 'is_media_items', False) if is_album: mimetype = 'application/vnd.google-apps.folder' name = f['title'] else: mimetype = Utils.get_safe_value(f, 'mimeType', '') name = Utils.get_safe_value(f, 'name', '') if is_media_items: name = Utils.get_safe_value(f, 'id', '') item = { 'id': f['id'], 'name': name, 'name_extension': Utils.get_extension(name), 'drive_id': Utils.get_safe_value( Utils.get_safe_value(f, 'owners', [{}])[0], 'permissionId'), 'mimetype': mimetype, 'last_modified_date': Utils.get_safe_value(f, 'modifiedTime'), 'size': size, 'description': Utils.get_safe_value(f, 'description', '') } if item['mimetype'] == 'application/vnd.google-apps.folder': item['folder'] = {'child_count': 0} if is_media_items: item['url'] = f['baseUrl'] if 'mediaMetadata' in f: metadata = f['mediaMetadata'] item['video'] = { 'width': Utils.get_safe_value(metadata, 'width'), 'height': Utils.get_safe_value(metadata, 'height') } if 'videoMediaMetadata' in f: video = f['videoMediaMetadata'] item['video'] = { 'width': Utils.get_safe_value(video, 'width'), 'height': Utils.get_safe_value(video, 'height'), 'duration': long('%s' % Utils.get_safe_value(video, 'durationMillis', 0)) / 1000 } if 'imageMediaMetadata' in f or 'mediaMetadata' in f: item['image'] = {'size': size} if 'hasThumbnail' in f and f['hasThumbnail']: item['thumbnail'] = Utils.get_safe_value(f, 'thumbnailLink') if is_album: item['thumbnail'] = Utils.get_safe_value(f, 'coverPhotoBaseUrl') item['id'] = 'album-' + item['id'] if include_download_info: if is_media_items: item['download_info'] = {'url': f['baseUrl']} else: parameters = { 'alt': 'media', 'access_token': self.get_access_tokens()['access_token'] } url = self._get_api_url() + '/files/%s' % item['id'] if 'size' not in f and item[ 'mimetype'] == 'application/vnd.google-apps.document': url += '/export' parameters['mimeType'] = self.get_mimetype_by_extension( item['name_extension']) item['download_info'] = { 'url': url + '?%s' % urllib.urlencode(parameters) } return item def get_mimetype_by_extension(self, extension): if extension and extension in self._extension_map: return self._extension_map[extension] return self._extension_map['pdf'] def get_item_by_path(self, path, include_download_info=False): parameters = copy.deepcopy(self._default_parameters) if path[-1:] == '/': path = path[:-1] Logger.debug(path + ' <- Target') key = '%s%s' % ( self._driveid, path, ) Logger.debug('Testing item from cache: %s' % key) item = self._items_cache.get(key) if not item: parameters['fields'] = 'files(%s)' % self._file_fileds index = path.rfind('/') filename = urllib.unquote(path[index + 1:]) parent = path[0:index] if not parent: parent = 'root' else: parent = self.get_item_by_path(parent, include_download_info)['id'] item = None parameters['q'] = '\'%s\' in parents and name = \'%s\'' % ( Utils.str(parent), Utils.str(filename)) files = self.get('/files', parameters=parameters) if (len(files['files']) > 0): for f in files['files']: item = self._extract_item(f, include_download_info) break else: Logger.debug('Found in cache.') if not item: raise RequestException( 'Not found by path', HTTPError(path, 404, 'Not found', None, None), 'Request URL: %s' % path, None) else: self._items_cache.set(key, item) return item def get_item(self, item_driveid=None, item_id=None, path=None, find_subtitles=False, include_download_info=False): parameters = copy.deepcopy(self._default_parameters) item_driveid = Utils.default(item_driveid, self._driveid) parameters['fields'] = self._file_fileds if not item_id and path == '/': item_id = 'root' if item_id: f = self.get('/files/%s' % item_id, parameters=parameters) item = self._extract_item(f, include_download_info) else: item = self.get_item_by_path(path, include_download_info) if find_subtitles: subtitles = [] parameters['fields'] = 'files(' + self._file_fileds + ')' parameters['q'] = 'name contains \'%s\'' % Utils.str( Utils.remove_extension(item['name'])).replace("'", "\\'") files = self.get('/files', parameters=parameters) for f in files['files']: subtitle = self._extract_item(f, include_download_info) if subtitle['name_extension'] == 'srt' or subtitle[ 'name_extension'] == 'sub' or subtitle[ 'name_extension'] == 'sbv': subtitles.append(subtitle) if subtitles: item['subtitles'] = subtitles return item