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']))
class CloudDriveAddon: _DEFAULT_SIGNIN_TIMEOUT = 120 _addon = None _addon_handle = None _addonid = None _addon_name = None _addon_params = None _addon_url = None _addon_version = None _common_addon = None _cancel_operation = False _content_type = None _dialog = None _export_manager = None _child_count_supported = True _auto_refreshed_slideshow_supported = True _load_target = 0 _load_count = 0 _profile_path = None _progress_dialog = None _progress_dialog_bg = None _system_monitor = None _video_file_extensions = [ x for x in KodiUtils.get_supported_media("video") if x not in ('', 'zip') ] _audio_file_extensions = KodiUtils.get_supported_media("music") _image_file_extensions = KodiUtils.get_supported_media("picture") _account_manager = None _action = None _ip_before_pin = None def __init__(self): self._addon = KodiUtils.get_addon() self._addonid = self._addon.getAddonInfo('id') self._addon_name = self._addon.getAddonInfo('name') self._addon_url = sys.argv[0] self._addon_version = self._addon.getAddonInfo('version') self._common_addon_id = 'script.module.clouddrive.common' self._common_addon = KodiUtils.get_addon(self._common_addon_id) self._common_addon_version = self._common_addon.getAddonInfo('version') self._dialog = xbmcgui.Dialog() self._profile_path = Utils.unicode( KodiUtils.translate_path(self._addon.getAddonInfo('profile'))) self._progress_dialog = DialogProgress(self._addon_name) self._progress_dialog_bg = DialogProgressBG(self._addon_name) self._system_monitor = KodiUtils.get_system_monitor() self._account_manager = AccountManager(self._profile_path) self._pin_dialog = None self.iskrypton = KodiUtils.get_home_property('iskrypton') == 'true' if len(sys.argv) > 1: self._addon_handle = int(sys.argv[1]) self._addon_params = urllib.parse.parse_qs(sys.argv[2][1:]) for param in self._addon_params: self._addon_params[param] = self._addon_params.get(param)[0] self._content_type = Utils.get_safe_value(self._addon_params, 'content_type') if not self._content_type: wid = xbmcgui.getCurrentWindowId() if wid == 10005 or wid == 10500 or wid == 10501 or wid == 10502: self._content_type = 'audio' elif wid == 10002: self._content_type = 'image' else: self._content_type = 'video' xbmcplugin.addSortMethod(handle=self._addon_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.addSortMethod( handle=self._addon_handle, sortMethod=xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(handle=self._addon_handle, sortMethod=xbmcplugin.SORT_METHOD_SIZE) xbmcplugin.addSortMethod(handle=self._addon_handle, sortMethod=xbmcplugin.SORT_METHOD_DATE) xbmcplugin.addSortMethod( handle=self._addon_handle, sortMethod=xbmcplugin.SORT_METHOD_DURATION) def __del__(self): del self._addon del self._common_addon del self._dialog del self._progress_dialog del self._progress_dialog_bg del self._system_monitor del self._account_manager def get_provider(self): raise NotImplementedError() def get_my_files_menu_name(self): return self._common_addon.getLocalizedString(32052) def get_custom_drive_folders(self, driveid): return def cancel_operation(self): return self._system_monitor.abortRequested( ) or self._progress_dialog.iscanceled() or self._cancel_operation or ( self._pin_dialog and self._pin_dialog.iscanceled()) def _get_display_name(self, account, drive=None, with_format=False): return self._account_manager.get_account_display_name( account, drive, self.get_provider(), with_format) def get_accounts(self, with_format=False): accounts = self._account_manager.get_accounts() for account_id in accounts: account = accounts[account_id] for drive in account['drives']: drive['display_name'] = self._get_display_name( account, drive, with_format) return accounts def list_accounts(self): accounts = self.get_accounts(with_format=True) listing = [] for account_id in accounts: account = accounts[account_id] size = len(account['drives']) for drive in account['drives']: context_options = [] params = { 'action': '_search', 'content_type': self._content_type, 'driveid': drive['id'] } cmd = 'ActivateWindow(%d,%s?%s)' % (xbmcgui.getCurrentWindowId( ), self._addon_url, urllib.parse.urlencode(params)) context_options.append( (self._common_addon.getLocalizedString(32039), cmd)) params['action'] = '_remove_account' context_options.append( (self._common_addon.getLocalizedString(32006), 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode(params) + ')')) if size > 1: params['action'] = '_remove_drive' cmd = 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode( params) + ')' context_options.append( (self._common_addon.getLocalizedString(32007), cmd)) list_item = xbmcgui.ListItem(drive['display_name']) list_item.addContextMenuItems(context_options) params = { 'action': '_list_drive', 'content_type': self._content_type, 'driveid': drive['id'] } url = self._addon_url + '?' + urllib.parse.urlencode(params) listing.append((url, list_item, True)) list_item = xbmcgui.ListItem( self._common_addon.getLocalizedString(32005)) params = {'action': '_add_account', 'content_type': self._content_type} url = self._addon_url + '?' + urllib.parse.urlencode(params) listing.append((url, list_item)) xbmcplugin.addDirectoryItems(self._addon_handle, listing, len(listing)) xbmcplugin.endOfDirectory(self._addon_handle, True) def _add_account(self): request_params = { 'waiting_retry': lambda request, remaining: self._progress_dialog_bg. update(int((request.current_delay - remaining) / request. current_delay * 100), heading=self._common_addon.getLocalizedString(32043) % ('' if request.current_tries == 1 else ' again'), message=self._common_addon.getLocalizedString(32044) % str( int(remaining) ) + ' ' + self._common_addon.getLocalizedString(32045) % (str(request.current_tries + 1), str(request.tries))), 'on_complete': lambda request: (self._progress_dialog.close(), self._progress_dialog_bg.close()), 'cancel_operation': self.cancel_operation, 'wait': self._system_monitor.waitForAbort } provider = self.get_provider() self._progress_dialog.update( 0, self._common_addon.getLocalizedString(32008)) self._ip_before_pin = Request(KodiUtils.get_signin_server() + '/ip', None).request() pin_info = provider.create_pin(request_params) self._progress_dialog.close() if self.cancel_operation(): return if not pin_info: raise Exception('Unable to retrieve a pin code') tokens_info = {} request_params[ 'on_complete'] = lambda request: self._progress_dialog_bg.close() self._pin_dialog = QRDialogProgress.create( self._addon_name, KodiUtils.get_signin_server() + '/signin/%s' % pin_info['pin'], self._common_addon.getLocalizedString(32009), self._common_addon.getLocalizedString(32010) % ('[B]%s[/B]' % KodiUtils.get_signin_server(), '[B][COLOR lime]%s[/COLOR][/B]' % pin_info['pin'])) self._pin_dialog.show() max_waiting_time = time.time() + self._DEFAULT_SIGNIN_TIMEOUT while not self.cancel_operation() and max_waiting_time > time.time(): remaining = round(max_waiting_time - time.time()) percent = int(remaining / self._DEFAULT_SIGNIN_TIMEOUT * 100) self._pin_dialog.update( percent, line3='[CR]' + self._common_addon.getLocalizedString(32011) % str(int(remaining)) + '[CR][CR]Your source id is: %s' % Utils.get_source_id(Utils.unicode(self._ip_before_pin))) if int(remaining) % 5 == 0 or remaining == 1: tokens_info = provider.fetch_tokens_info( pin_info, request_params=request_params) if self.cancel_operation() or tokens_info: break if self._system_monitor.waitForAbort(1): break self._pin_dialog.close() if self.cancel_operation() or time.time() >= max_waiting_time: return if not tokens_info: raise Exception('Unable to retrieve the auth2 tokens') self._progress_dialog.update( 25, self._common_addon.getLocalizedString(32064), ' ', ' ') try: account = provider.get_account(request_params=request_params, access_tokens=tokens_info) except Exception as e: raise UIException(32065, e) if self.cancel_operation(): return self._progress_dialog.update( 50, self._common_addon.getLocalizedString(32017)) try: account['drives'] = provider.get_drives( request_params=request_params, access_tokens=tokens_info) except Exception as e: raise UIException(32018, e) if self.cancel_operation(): return self._progress_dialog.update( 75, self._common_addon.getLocalizedString(32020)) try: account['access_tokens'] = tokens_info self._account_manager.save_account(account) except Exception as e: raise UIException(32021, e) if self.cancel_operation(): return self._progress_dialog.update(90) try: accounts = self._account_manager.get_accounts() for drive in account['drives']: driveid = drive['id'] Logger.debug('Looking for account %s...' % driveid) if driveid in accounts: drive = accounts[driveid]['drives'][0] Logger.debug(drive) if drive['id'] == driveid and drive['type'] == 'migrated': Logger.debug('Account %s removed.' % driveid) self._account_manager.remove_account(driveid) except Exception as e: pass if self.cancel_operation(): return self._progress_dialog.close() KodiUtils.executebuiltin('Container.Refresh') def _remove_drive(self, driveid): account = self._account_manager.get_by_driveid('account', driveid) drive = self._account_manager.get_by_driveid('drive', driveid, account) if self._dialog.yesno( self._addon_name, self._common_addon.getLocalizedString(32023) % self._get_display_name(account, drive, True)): self._account_manager.remove_drive(driveid, account) KodiUtils.executebuiltin('Container.Refresh') def _remove_account(self, driveid): account = self._account_manager.get_by_driveid('account', driveid) if self._dialog.yesno( self._addon_name, self._common_addon.getLocalizedString(32022) % self._get_display_name(account, with_format=True)): self._account_manager.remove_account(account['id']) KodiUtils.executebuiltin('Container.Refresh') def _list_drive(self, driveid): drive_folders = self.get_custom_drive_folders(driveid) if self.cancel_operation(): return if drive_folders: listing = [] url = self._addon_url + '?' + urllib.parse.urlencode( { 'action': '_list_folder', 'path': '/', 'content_type': self._content_type, 'driveid': driveid }) listing.append( (url, xbmcgui.ListItem('[B]%s[/B]' % self.get_my_files_menu_name()), True)) for folder in drive_folders: params = { 'action': '_list_folder', 'path': folder['path'], 'content_type': self._content_type, 'driveid': driveid } if 'params' in folder: params.update(folder['params']) url = self._addon_url + '?' + urllib.parse.urlencode(params) list_item = xbmcgui.ListItem(Utils.unicode(folder['name'])) if 'context_options' in folder: list_item.addContextMenuItems(folder['context_options']) listing.append((url, list_item, True)) if self._content_type == 'video' or self._content_type == 'audio': url = self._addon_url + '?' + urllib.parse.urlencode( { 'action': '_list_exports', 'content_type': self._content_type, 'driveid': driveid }) listing.append( (url, xbmcgui.ListItem( self._common_addon.getLocalizedString(32000)), True)) url = self._addon_url + '?' + urllib.parse.urlencode( { 'action': '_search', 'content_type': self._content_type, 'driveid': driveid }) listing.append( (url, xbmcgui.ListItem( self._common_addon.getLocalizedString(32039)), True)) xbmcplugin.addDirectoryItems(self._addon_handle, listing, len(listing)) xbmcplugin.endOfDirectory(self._addon_handle, True) else: self._list_folder(driveid, path='/') def _list_exports(self, driveid): self._export_manager = ExportManager(self._profile_path) exports = self._export_manager.get_exports() listing = [] for exportid in exports: export = exports[exportid] if export['driveid'] == driveid and export[ 'content_type'] == self._content_type: item_name = Utils.unicode(export['name']) params = { 'action': '_open_export', 'content_type': self._content_type, 'driveid': driveid, 'item_driveid': export['item_driveid'], 'item_id': export['id'], 'name': urllib.parse.quote(Utils.str(item_name)) } url = self._addon_url + '?' + urllib.parse.urlencode(params) list_item = xbmcgui.ListItem(item_name) context_options = [] params['action'] = '_run_export' context_options.append((KodiUtils.localize(21479), 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode(params) + ')')) params['action'] = '_remove_export' context_options.append( (KodiUtils.localize(1210), 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode(params) + ')')) list_item.addContextMenuItems(context_options) listing.append((url, list_item, True)) xbmcplugin.addDirectoryItems(self._addon_handle, listing, len(listing)) xbmcplugin.endOfDirectory(self._addon_handle, True) def _run_export(self, driveid, item_id): self._export_manager = ExportManager(self._profile_path) export = self._export_manager.get_exports()[item_id] export['run_immediately'] = True self._export_manager.save_export(export) KodiUtils.show_notification( self._common_addon.getLocalizedString(32055) % '60') def _remove_export(self, driveid, item_id): self._export_manager = ExportManager(self._profile_path) exports = self._export_manager.get_exports() if item_id in exports: item = exports[item_id] remove_export = self._dialog.yesno( self._addon_name, self._common_addon.getLocalizedString(32001) % Utils.unicode(item['name'])) if remove_export: keep_locals = self._dialog.yesno( self._addon_name, self._common_addon.getLocalizedString(32086) % Utils.unicode(item['name'])) if not keep_locals: self._export_manager.remove_export(item_id, False) else: self._export_manager.remove_export(item_id) KodiUtils.executebuiltin('Container.Refresh') else: KodiUtils.executebuiltin('Container.Refresh') def _open_export(self, driveid, item_driveid, item_id, name): export_dialog = ExportMainDialog.create(self._content_type, driveid, item_driveid, item_id, name, self._account_manager, self.get_provider()) export_dialog.doModal() if export_dialog.run: KodiUtils.show_notification( self._common_addon.getLocalizedString(32055) % '60') def on_items_page_completed(self, items): self._load_count += len(items) if self._load_target > self._load_count: percent = int( round(float(self._load_count) / self._load_target * 100)) self._progress_dialog_bg.update( percent, self._addon_name, self._common_addon.getLocalizedString(32047) % (Utils.str(self._load_count), Utils.str(self._load_target))) else: self._progress_dialog_bg.update( 100, self._addon_name, self._common_addon.getLocalizedString(32048) % Utils.str(self._load_count)) def _list_folder(self, driveid, item_driveid=None, item_id=None, path=None): self.get_provider().configure(self._account_manager, driveid) if self._child_count_supported: item = self.get_provider().get_item(item_driveid, item_id, path) if item: self._load_target = item['folder']['child_count'] self._progress_dialog_bg.create( self._addon_name, self._common_addon.getLocalizedString(32049) % Utils.str(self._load_target)) items = self.get_provider().get_folder_items( item_driveid, item_id, path, on_items_page_completed=self.on_items_page_completed) if self.cancel_operation(): return self._process_items(items, driveid) def _process_items(self, items, driveid): listing = [] for item in items: Logger.debug(item) item_id = item['id'] item_name = Utils.unicode(item['name']) item_name_extension = item['name_extension'] item_driveid = Utils.default( Utils.get_safe_value(item, 'drive_id'), driveid) list_item = xbmcgui.ListItem(item_name) url = None is_folder = 'folder' in item params = { 'content_type': self._content_type, 'item_driveid': item_driveid, 'item_id': item_id, 'driveid': driveid } if 'extra_params' in item: params.update(item['extra_params']) context_options = [] info = { 'size': item['size'], 'date': KodiUtils.to_kodi_item_date_str( KodiUtils.to_datetime( Utils.get_safe_value(item, 'last_modified_date'))) } if is_folder: params['action'] = '_list_folder' url = self._addon_url + '?' + urllib.parse.urlencode(params) params['action'] = '_search' cmd = 'ActivateWindow(%d,%s?%s)' % (xbmcgui.getCurrentWindowId( ), self._addon_url, urllib.parse.urlencode(params)) context_options.append( (self._common_addon.getLocalizedString(32039), cmd)) if self._content_type == 'audio' or self._content_type == 'video': params['action'] = '_open_export' params['name'] = urllib.parse.quote(Utils.str(item_name)) context_options.append( (self._common_addon.getLocalizedString(32004), 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode(params) + ')')) del params['name'] elif self._content_type == 'image' and self._auto_refreshed_slideshow_supported: params['action'] = '_slideshow' context_options.append( (self._common_addon.getLocalizedString(32032), 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode(params) + ')')) elif (('video' in item or (item_name_extension and item_name_extension in self._video_file_extensions)) and self._content_type == 'video') or ( ('audio' in item or (item_name_extension and item_name_extension in self._audio_file_extensions)) and self._content_type == 'audio'): list_item.setProperty('IsPlayable', 'true') params['action'] = 'download' cmd = 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode( params) + ')' context_options.append( (self._common_addon.getLocalizedString(32051), cmd)) params['action'] = 'play' url = self._addon_url + '?' + urllib.parse.urlencode(params) info_type = self._content_type if 'audio' in item: info.update(item['audio']) info_type = 'music' elif 'video' in item: list_item.addStreamInfo('video', item['video']) list_item.setInfo(info_type, info) if 'thumbnail' in item: list_item.setArt({ 'icon': item['thumbnail'], 'thumb': item['thumbnail'] }) elif ('image' in item or (item_name_extension and item_name_extension in self._image_file_extensions) ) and self._content_type == 'image': Logger.debug( 'image in item: %s' % (Utils.str('image' in item)), ) Logger.debug( 'item_name_extension in self._image_file_extensions: %s' % (Utils.str( item_name_extension in self._image_file_extensions), )) params['action'] = 'download' cmd = 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode( params) + ')' context_options.append( (self._common_addon.getLocalizedString(32051), cmd)) if 'url' in item: url = item['url'] else: url = self._get_item_play_url( urllib.parse.quote(Utils.str(item_name)), driveid, item_driveid, item_id) if 'image' in item: info.update(item['image']) list_item.setInfo('pictures', info) if 'thumbnail' in item and item['thumbnail']: list_item.setArt({ 'icon': item['thumbnail'], 'thumb': item['thumbnail'] }) if url: context_options.extend( self.get_context_options(list_item, params, is_folder)) list_item.addContextMenuItems(context_options) mimetype = Utils.default( Utils.get_mimetype_by_extension(item_name_extension), Utils.get_safe_value(item, 'mimetype')) if mimetype: list_item.setProperty('mimetype', mimetype) listing.append((url, list_item, is_folder)) xbmcplugin.addDirectoryItems(self._addon_handle, listing, len(listing)) xbmcplugin.endOfDirectory(self._addon_handle, True) def get_context_options(self, list_item, params, is_folder): return [] def _search(self, driveid, item_driveid=None, item_id=None): self.get_provider().configure(self._account_manager, driveid) query = self._dialog.input( self._addon_name + ' - ' + self._common_addon.getLocalizedString(32042)) if query: self._progress_dialog_bg.create( self._addon_name, self._common_addon.getLocalizedString(32041)) items = self.get_provider().search( query, item_driveid, item_id, on_items_page_completed=self.on_items_page_completed) if self.cancel_operation(): return self._process_items(items, driveid) def download(self, driveid, item_driveid=None, item_id=None): dest_folder = self._dialog.browse( 0, self._common_addon.getLocalizedString(32002), 'files') if dest_folder: provider = self.get_provider() provider.configure(self._account_manager, driveid) item = provider.get_item(item_driveid, item_id, include_download_info=True) name = Utils.get_safe_value(item, 'name', item['id']) download_path = os.path.join(dest_folder, Utils.unicode(name)) download_size = Utils.get_safe_value(item, 'size', 0) on_update_download = lambda request: self._progress_dialog_bg.update( int(1.0 * request.download_progress / download_size * 100), self._addon_name, self._common_addon.getLocalizedString(32056) % name) if ExportManager.download(item, download_path, provider, on_update_download=on_update_download): msg = self._common_addon.getLocalizedString(32057) % name else: msg = self._common_addon.getLocalizedString(32087) % name KodiUtils.show_notification(msg) def new_change_token_slideshow(self, change_token, driveid, item_driveid=None, item_id=None, path=None): self.get_provider().configure(self._account_manager, driveid) item = self.get_provider().get_item(item_driveid, item_id, path) if self.cancel_operation(): return return item['folder']['child_count'] def _slideshow(self, driveid, item_driveid=None, item_id=None, path=None, change_token=None): new_change_token = self.new_change_token_slideshow( change_token, driveid, item_driveid, item_id, path) if self.cancel_operation(): return wait_for_slideshow = False if not change_token or change_token != new_change_token: Logger.notice( 'Slideshow will start. change_token: %s, new_change_token: %s' % (change_token, new_change_token)) params = { 'action': '_list_folder', 'content_type': self._content_type, 'item_driveid': Utils.default(item_driveid, ''), 'item_id': Utils.default(item_id, ''), 'driveid': driveid, 'path': Utils.default(path, '') } extra_params = ',recursive' if self._addon.getSetting( 'slideshow_recursive') == 'true' else '' KodiUtils.executebuiltin('SlideShow(' + self._addon_url + '?' + urllib.parse.urlencode(params) + extra_params + ')') wait_for_slideshow = True else: Logger.notice( 'Slideshow child count is the same, nothing to refresh...') t = threading.Thread(target=self._refresh_slideshow, args=( driveid, item_driveid, item_id, path, new_change_token, wait_for_slideshow, )) t.setDaemon(True) t.start() def _refresh_slideshow(self, driveid, item_driveid, item_id, path, change_token, wait_for_slideshow): if wait_for_slideshow: Logger.notice( 'Waiting up to 10 minutes until the slideshow for folder %s starts...' % Utils.default(item_id, path)) max_waiting_time = time.time() + 10 * 60 while not self.cancel_operation( ) and not KodiUtils.get_cond_visibility( 'Slideshow.IsActive') and max_waiting_time > time.time(): if self._system_monitor.waitForAbort(2): break self._print_slideshow_info() interval = self._addon.getSetting('slideshow_refresh_interval') Logger.notice( 'Waiting up to %s minute(s) to check if it is needed to refresh the slideshow of folder %s...' % (interval, Utils.default(item_id, path))) target_time = time.time() + int(interval) * 60 while not self.cancel_operation() and target_time > time.time( ) and KodiUtils.get_cond_visibility('Slideshow.IsActive'): if self._system_monitor.waitForAbort(10): break self._print_slideshow_info() if not self.cancel_operation() and KodiUtils.get_cond_visibility( 'Slideshow.IsActive'): try: self._slideshow(driveid, item_driveid, item_id, path, change_token) except Exception as e: Logger.error( 'Slideshow failed to auto refresh. Will be restarted when possible. Error: ' ) Logger.error(ExceptionUtils.full_stacktrace(e)) self._refresh_slideshow(driveid, item_driveid, item_id, path, None, wait_for_slideshow) else: Logger.notice( 'Slideshow is not running anymore or abort requested.') def _print_slideshow_info(self): if KodiUtils.get_cond_visibility('Slideshow.IsActive'): Logger.debug('Slideshow is there...') elif self.cancel_operation(): Logger.debug('Abort requested...') def _get_item_play_url(self, file_name, driveid, item_driveid=None, item_id=None, is_subtitle=False): return DownloadServiceUtil.build_download_url( driveid, item_driveid, item_id, urllib.parse.quote(Utils.str(file_name))) def play(self, driveid, item_driveid=None, item_id=None): self.get_provider().configure(self._account_manager, driveid) find_subtitles = self._addon.getSetting( 'set_subtitle') == 'true' and self._content_type == 'video' item = self.get_provider().get_item(item_driveid, item_id, find_subtitles=find_subtitles) file_name = Utils.unicode(item['name']) list_item = xbmcgui.ListItem(file_name) succeeded = True info = KodiUtils.get_current_library_info() if not info: info = KodiUtils.find_exported_video_in_library( item_id, file_name + ExportManager._strm_extension) if info and info['id']: Logger.debug('library info: %s' % Utils.str(info)) KodiUtils.set_home_property('dbid', Utils.str(info['id'])) KodiUtils.set_home_property('dbtype', info['type']) KodiUtils.set_home_property('addonid', self._addonid) details = KodiUtils.get_video_details(info['type'], info['id']) Logger.debug('library details: %s' % Utils.str(details)) if details and 'resume' in details: KodiUtils.set_home_property('playcount', Utils.str(details['playcount'])) resume = details['resume'] if resume['position'] > 0: play_resume = False if self.iskrypton: play_resume = KodiUtils.get_addon_setting( 'resume_playing') == 'true' elif KodiUtils.get_addon_setting('ask_resume') == 'true': d = datetime(1, 1, 1) + timedelta(seconds=resume['position']) t = '%02d:%02d:%02d' % (d.hour, d.minute, d.second) Logger.debug(t) option = self._dialog.contextmenu([ KodiUtils.localize(32054, addon=self._common_addon) % t, KodiUtils.localize(12021) ]) Logger.debug('selected option: %d' % option) if option == -1: succeeded = False elif option == 0: play_resume = True if play_resume: list_item.setProperty('resumetime', Utils.str(resume['position'])) list_item.setProperty('startoffset', Utils.str(resume['position'])) list_item.setProperty('totaltime', Utils.str(resume['total'])) else: from clouddrive.common.service.player import KodiPlayer KodiPlayer.cleanup() if 'audio' in item: list_item.setInfo('music', item['audio']) elif 'video' in item: list_item.addStreamInfo('video', item['video']) list_item.select(True) list_item.setPath( self._get_item_play_url(file_name, driveid, item_driveid, item_id)) list_item.setProperty('mimetype', Utils.get_safe_value(item, 'mimetype')) if find_subtitles and 'subtitles' in item: subtitles = [] for subtitle in item['subtitles']: subtitles.append( self._get_item_play_url( urllib.parse.quote(Utils.str(subtitle['name'])), driveid, Utils.default( Utils.get_safe_value(subtitle, 'drive_id'), driveid), subtitle['id'], True)) list_item.setSubtitles(subtitles) if not self.cancel_operation(): xbmcplugin.setResolvedUrl(self._addon_handle, succeeded, list_item) def _handle_exception(self, ex, show_error_dialog=True): stacktrace = ExceptionUtils.full_stacktrace(ex) rex = ExceptionUtils.extract_exception(ex, RequestException) uiex = ExceptionUtils.extract_exception(ex, UIException) httpex = ExceptionUtils.extract_exception(ex, HTTPError) urlex = ExceptionUtils.extract_exception(ex, URLError) line1 = self._common_addon.getLocalizedString(32027) line2 = Utils.unicode(ex) line3 = self._common_addon.getLocalizedString(32016) if uiex: line1 = self._common_addon.getLocalizedString(int(Utils.str(uiex))) line2 = Utils.unicode(uiex.root_exception) elif rex and rex.response: line1 += ' ' + Utils.unicode(rex) line2 = ExceptionUtils.extract_error_message(rex.response) send_report = True add_account_cmd = 'RunPlugin(' + self._addon_url + '?' + urllib.parse.urlencode( { 'action': '_add_account', 'content_type': self._content_type }) + ')' if isinstance(ex, AccountNotFoundException) or isinstance( ex, DriveNotFoundException): show_error_dialog = False if self._dialog.yesno( self._addon_name, self._common_addon.getLocalizedString(32063) % '\n'): KodiUtils.executebuiltin(add_account_cmd) elif rex and httpex: if httpex.code >= 500: line1 = self._common_addon.getLocalizedString(32035) line2 = None line3 = self._common_addon.getLocalizedString(32038) elif httpex.code >= 400: driveid = Utils.get_safe_value(self._addon_params, 'driveid') if driveid: account = self._account_manager.get_by_driveid( 'account', driveid) drive = self._account_manager.get_by_driveid( 'drive', driveid, account) if KodiUtils.get_signin_server( ) in rex.request or httpex.code == 401: send_report = False show_error_dialog = False if self._dialog.yesno( self._addon_name, self._common_addon.getLocalizedString(32046) % (self._get_display_name(account, drive, True) + '\n')): KodiUtils.executebuiltin(add_account_cmd) elif httpex.code == 403: line1 = self._common_addon.getLocalizedString(32019) line2 = line3 = None elif httpex.code == 404: send_report = False line1 = self._common_addon.getLocalizedString(32037) line2 = line2 = None else: line1 = self._common_addon.getLocalizedString(32036) line3 = self._common_addon.getLocalizedString(32038) else: if KodiUtils.get_signin_server( ) + '/pin/' in rex.request and httpex.code == 404 and self._ip_before_pin: ip_after_pin = Request( KodiUtils.get_signin_server() + '/ip', None).request() if self._ip_before_pin != ip_after_pin: send_report = False line1 = self._common_addon.getLocalizedString( 32072) line2 = self._common_addon.getLocalizedString( 32073) % ( self._ip_before_pin, ip_after_pin, ) elif urlex: reason = Utils.str(urlex.reason) line3 = self._common_addon.getLocalizedString(32074) if '[Errno 101]' in reason: line1 = self._common_addon.getLocalizedString(32076) elif '[Errno 11001]' in reason: line1 = self._common_addon.getLocalizedString(32077) elif 'CERTIFICATE_VERIFY_FAILED' in reason: line1 = self._common_addon.getLocalizedString(32078) else: line1 = self._common_addon.getLocalizedString(32075) report = '[%s] [%s]/[%s]\n\n%s\n%s\n%s\n\n%s' % ( self._addonid, self._addon_version, self._common_addon_version, line1, line2, line3, stacktrace) if rex: report += '\n\n%s\nResponse:\n%s' % (rex.request, rex.response) report += '\n\nshow_error_dialog: %s' % show_error_dialog Logger.error(report) if show_error_dialog: if line2: line1 += '\n' + line2 if line3: line1 += '\n' + line3 self._dialog.ok(self._addon_name, line1) if send_report: report_error = KodiUtils.get_addon_setting( 'report_error', self._common_addon_id) == 'true' report_error_invite = KodiUtils.get_addon_setting( 'report_error_invite', self._common_addon_id) == 'true' if not report_error and not report_error_invite: if not self._dialog.yesno( self._addon_name, self._common_addon.getLocalizedString(32050), self._common_addon.getLocalizedString(32012), self._common_addon.getLocalizedString(32013)): KodiUtils.set_addon_setting('report_error', 'true', self._common_addon_id) KodiUtils.set_addon_setting('report_error_invite', 'true', self._common_addon_id) ErrorReport.send_report(report) def _open_common_settings(self): self._common_addon.openSettings() def _clear_cache(self): Cache(self._addonid, 'page', 0).clear() Cache(self._addonid, 'children', 0).clear() Cache(self._addonid, 'items', 0).clear() def _rename_action(self): pass def route(self): try: Logger.debug(self._addon_params) self._action = Utils.get_safe_value(self._addon_params, 'action') if self._action: self._rename_action() method = getattr(self, self._action) arguments = {} for name in inspect.getargspec(method)[0]: if name in self._addon_params: arguments[name] = self._addon_params[name] method(**arguments) else: self.list_accounts() except Exception as ex: self._handle_exception(ex) finally: self._progress_dialog.close() self._progress_dialog_bg.close() if self._pin_dialog: self._pin_dialog.close()