def _run_export(self, driveid, item_id=None): export_manager = ExportManager(self._account_manager._addon_data_path) export = export_manager.load()[item_id] Logger.debug('Running export:') Logger.debug(export) if Utils.get_safe_value(export, 'exporting', False): self._dialog.ok( self._addon_name, self._common_addon.getLocalizedString(32059) + ' ' + self._common_addon.getLocalizedString(32038)) else: export['exporting'] = True export_manager.save() export_folder = export['destination_folder'] if xbmcvfs.exists(export_folder): self.get_provider().configure(self._account_manager, driveid) self._export_progress_dialog_bg.create( self._addon_name + ' ' + self._common_addon.getLocalizedString(32024), self._common_addon.getLocalizedString(32025)) self._export_progress_dialog_bg.update(0) item = self.get_provider().get_item(export['item_driveid'], item_id) if self.cancel_operation(): return if self._child_count_supported: self._exporting_target = int(item['folder']['child_count']) self._exporting_target += 1 folder_name = Utils.unicode(item['name']) folder_path = os.path.join( os.path.join(export_folder, folder_name), '') if self._addon.getSetting( 'clean_folder') != 'true' or not xbmcvfs.exists( folder_path) or self._remove_folder(folder_path): self._exporting = item_id export_items_info = {} ExportManager.add_item_info(export_items_info, item_id, folder_name, folder_path, None) self.__export_folder(driveid, item, export_folder, export, export_items_info) export_manager.save_items_info(item_id, export_items_info) if Utils.get_safe_value(export, 'update_library', False) and self._content_type: database = self._content_type if database == 'audio': database = 'music' KodiUtils.update_library(database) else: error = self._common_addon.getLocalizedString( 32066) % folder_path Logger.debug(error) self._dialog.ok(self._addon_name, error) self._export_progress_dialog_bg.close() else: error = self._common_addon.getLocalizedString( 32026) % export_folder Logger.debug(error) self._dialog.ok(self._addon_name, error) export['exporting'] = False export_manager.save()
class CloudDriveAddon(RemoteProcessCallable): _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 _exporting = None _export_manager = None _exporting_target = 0 _exporting_percent = 0 _exporting_count = 0 _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 _export_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._export_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 = urlparse.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._export_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.load() 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.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.urlencode(params)+')')) if size > 1: params['action'] = '_remove_drive' cmd = 'RunPlugin('+self._addon_url + '?' + urllib.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.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.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(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.add_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.load() 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): self._account_manager.load() account = self._account_manager.get_account_by_driveid(driveid) drive = self._account_manager.get_drive_by_driveid(driveid) if self._dialog.yesno(self._addon_name, self._common_addon.getLocalizedString(32023) % self._get_display_name(account, drive, True), None): self._account_manager.remove_drive(driveid) KodiUtils.executebuiltin('Container.Refresh') def _remove_account(self, driveid): self._account_manager.load() account = self._account_manager.get_account_by_driveid(driveid) if self._dialog.yesno(self._addon_name, self._common_addon.getLocalizedString(32022) % self._get_display_name(account, with_format=True), None): 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.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.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.urlencode({'action':'_list_exports', 'content_type': self._content_type, 'driveid': driveid}) listing.append((url, xbmcgui.ListItem(self._common_addon.getLocalizedString(32000)), 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._account_manager._addon_data_path) exports = self._export_manager.load() 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.quote(Utils.str(item_name))} url = self._addon_url + '?' + urllib.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.urlencode(params)+')')) params['action'] = '_remove_export' context_options.append((KodiUtils.localize(1210), 'RunPlugin('+self._addon_url + '?' + urllib.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 _remove_export(self, driveid, item_id): self._export_manager = ExportManager(self._account_manager._addon_data_path) item = self._export_manager.load()[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') 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: t = threading.Thread(target=self._run_export, args=(driveid, item_id,)) t.setDaemon(True) t.start() def _run_export(self, driveid, item_id=None): self._export_manager = ExportManager(self._account_manager._addon_data_path) export = self._export_manager.load()[item_id] Logger.debug('Running export:') Logger.debug(export) if Utils.get_safe_value(export, 'exporting', False): self._dialog.ok(self._addon_name, self._common_addon.getLocalizedString(32059) + ' ' + self._common_addon.getLocalizedString(32038)) else: export['exporting'] = True self._export_manager.save() export_folder = export['destination_folder'] if xbmcvfs.exists(export_folder): self.get_provider().configure(self._account_manager, driveid) self._export_progress_dialog_bg.create(self._addon_name + ' ' + self._common_addon.getLocalizedString(32024), self._common_addon.getLocalizedString(32025)) self._export_progress_dialog_bg.update(0) item = self.get_provider().get_item(export['item_driveid'], item_id) if self.cancel_operation(): return if self._child_count_supported: self._exporting_target = int(item['folder']['child_count']) self._exporting_target += 1 folder_name = Utils.unicode(item['name']) folder_path = os.path.join(os.path.join(export_folder, folder_name), '') if self._addon.getSetting('clean_folder') != 'true' or not xbmcvfs.exists(folder_path) or Utils.remove_folder(folder_path): self._exporting = item_id export_items_info = {} ExportManager.add_item_info(export_items_info, item_id, folder_name, folder_path, None,'root') self.__export_folder(driveid, item, export_folder, export, export_items_info, item_id) self._export_manager.save_items_info(item_id, export_items_info) if Utils.get_safe_value(export, 'update_library', False) and self._content_type: database = self._content_type if database == 'audio': database = 'music' KodiUtils.update_library(database) else: error = self._common_addon.getLocalizedString(32066) % folder_path Logger.debug(error) self._dialog.ok(self._addon_name, error) self._export_progress_dialog_bg.close() else: error = self._common_addon.getLocalizedString(32026) % export_folder Logger.debug(error) self._dialog.ok(self._addon_name, error) export['exporting'] = False self._export_manager.save() def __export_folder(self, driveid, folder, export_folder, export, items_info, root_id): folder_id = Utils.str(folder['id']) folder_name = Utils.unicode(folder['name']) folder_path = os.path.join(os.path.join(export_folder, folder_name), '') if not xbmcvfs.exists(folder_path): try: xbmcvfs.mkdirs(folder_path) except: if self._system_monitor.waitForAbort(3): return xbmcvfs.mkdirs(folder_path) items = self.get_provider().get_folder_items(Utils.default(Utils.get_safe_value(folder, 'drive_id'), driveid), folder['id']) if self.cancel_operation(): return for item in items: if 'folder' in item: if self._child_count_supported: self._exporting_target += int(item['folder']['child_count']) else: self._exporting_target += 1 for item in items: is_folder = 'folder' in item item_id = Utils.str(item['id']) item_name = Utils.unicode(item['name']) item_name_extension = item['name_extension'] file_path = os.path.join(folder_path, item_name) if is_folder: ExportManager.add_item_info(items_info, item_id, item_name, os.path.join(file_path, ''), folder_id,'folder') self.__export_folder(driveid, item, folder_path, export, items_info, root_id) elif (('video' in item or item_name_extension in self._video_file_extensions) and export['content_type'] == 'video') or ('audio' in item and export['content_type'] == 'audio'): item_name += ExportManager._strm_extension file_path += ExportManager._strm_extension if self._addon.getSetting('skip_unmodified') and KodiUtils.file_exists(file_path) and KodiUtils.file(file_path).size() == item["size"]: continue ExportManager.create_strm(driveid, item, file_path, export['content_type'], self._addon_url) ExportManager.add_item_info(items_info, item_id, item_name, file_path, folder_id,'file') elif 'nfo_export' in export and export['nfo_export'] and ('nfo' in item_name_extension or 'text/x-nfo' in item.get("mimetype")): nfo_path = os.path.join(folder_path, Utils.unicode(item_name)) if self._addon.getSetting('skip_unmodified') and KodiUtils.file_exists(nfo_path) and KodiUtils.file(nfo_path).size() == item["size"]: continue ExportManager.create_nfo(item_id, export["item_driveid"], nfo_path, self.get_provider()) ExportManager.add_item_info(items_info, item_id, item_name, nfo_path, folder_id,'file') self._export_manager.save_items_info(root_id, items_info) self._exporting_count += 1 p = int(self._exporting_count/float(self._exporting_target)*100) if self._exporting_percent < p: self._exporting_percent = p self._export_progress_dialog_bg.update(self._exporting_percent, self._addon_name + ' ' + self._common_addon.getLocalizedString(32024), file_path[len(export['destination_folder']):]) 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: 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.urlencode(params) params['action'] = '_search' cmd = 'ActivateWindow(%d,%s?%s)' % (xbmcgui.getCurrentWindowId(), self._addon_url, urllib.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.quote(Utils.str(item_name)) context_options.append((self._common_addon.getLocalizedString(32004), 'RunPlugin('+self._addon_url + '?' + urllib.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.urlencode(params)+')')) elif (('video' in item or item_name_extension in self._video_file_extensions) and self._content_type == 'video') or (('audio' in item or item_name_extension in self._audio_file_extensions) and self._content_type == 'audio'): list_item.setProperty('IsPlayable', 'true') params['action'] = 'play' url = self._addon_url + '?' + urllib.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 in self._image_file_extensions) and self._content_type == 'image' and item_name_extension != 'mp4': if 'url' in item: url = item['url'] else: url = self._get_item_play_url(urllib.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 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.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.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.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.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: self._account_manager.load() account = self._account_manager.get_account_by_driveid(driveid) drive = self._account_manager.get_drive_by_driveid(driveid) 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: self._dialog.ok(self._addon_name, line1, line2, line3) 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), None, None, 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() self._export_progress_dialog_bg.close() if self._pin_dialog: self._pin_dialog.close() if self._exporting: self._export_manager = ExportManager(self._account_manager._addon_data_path) export = self._export_manager.load()[self._exporting] export['exporting'] = False self._export_manager.save()
class ExportService(object): name = 'export' def __init__(self, provider_class): self.abort = False self._system_monitor = KodiUtils.get_system_monitor() self.provider = provider_class() self.addonid = KodiUtils.get_addon_info('id') self._addon_name = KodiUtils.get_addon_info('name') self._common_addon_id = 'script.module.clouddrive.common' self._common_addon = KodiUtils.get_addon(self._common_addon_id) self._profile_path = Utils.unicode( KodiUtils.translate_path(KodiUtils.get_addon_info('profile'))) self._startup_type = Utils.str(ExportScheduleDialog._startup_type) self.export_manager = ExportManager(self._profile_path) self._account_manager = AccountManager(self._profile_path) self._video_file_extensions = [ x for x in KodiUtils.get_supported_media("video") if x not in ('', 'zip') ] self._audio_file_extensions = KodiUtils.get_supported_media("music") self._artwork_file_extensions = [ 'back', 'banner', 'characterart', 'clearart', 'clearlogo', 'discart', 'fanart', 'keyart', 'landscape', 'poster', 'spine', 'thumb', 'folder', 'cover', 'animatedposter', 'animatedfanart' ] self._export_progress_dialog_bg = DialogProgressBG(self._addon_name) def __del__(self): del self._system_monitor del self.export_manager del self._account_manager del self._common_addon del self._export_progress_dialog_bg def cleanup_export_map(self): exports = self.export_manager.get_exports() for exportid in exports: export = exports[exportid] export['exporting'] = False self.export_manager.save_export(export) def get_scheduled_export_map(self): exports = self.export_manager.get_exports() export_map = {} for exportid in exports: export = exports[exportid] schedules = Utils.get_safe_value(export, 'schedules', []) exporting = Utils.get_safe_value(export, 'exporting', False) if not exporting: if Utils.get_safe_value(export, 'schedule', False) and schedules: for schedule in schedules: key = Utils.str( Utils.get_safe_value(schedule, 'type', '')) if key != self._startup_type: key += Utils.get_safe_value(schedule, 'at', '') export_map[key] = Utils.get_safe_value( export_map, key, []) export_map[key].append(export) key = 'run_immediately' if Utils.get_safe_value(export, key, False): export_map[key] = Utils.get_safe_value(export_map, key, []) export_map[key].append(export) export[key] = False self.export_manager.save_export(export) Logger.debug('scheduled export_map: %s' % Utils.str(export_map)) return export_map def start(self): Logger.notice('Service \'%s\' started.' % self.name) self.cleanup_export_map() monitor = KodiUtils.get_system_monitor() startup = True while not self.abort: try: now = datetime.datetime.now() export_map = self.get_scheduled_export_map() if export_map: self.process_schedules(export_map, now, startup) self.process_watch() except Exception as e: ErrorReport.handle_exception(e) startup = False if monitor.waitForAbort(60): break del monitor del self.provider Logger.notice('Service stopped.') def process_schedules(self, export_map, now, startup=False): Logger.debug('now: %s, startup: %s' % (Utils.str(now), Utils.str(startup))) export_list = [] if startup: export_list.extend( Utils.get_safe_value(export_map, self._startup_type, [])) else: key = 'run_immediately' run_immediately_list = Utils.get_safe_value(export_map, key, []) export_list.extend(run_immediately_list) at = '%02d:%02d' % ( now.hour, now.minute, ) Logger.debug('at: %s' % Utils.str(at)) daily_list = Utils.get_safe_value( export_map, Utils.str(ExportScheduleDialog._daily_type) + at, []) export_list.extend(daily_list) Logger.debug('daily_list: %s' % Utils.str(daily_list)) weekday = now.weekday() + 11 weekday_list = Utils.get_safe_value(export_map, Utils.str(weekday) + at, []) export_list.extend(weekday_list) Logger.debug('weekday_list: %s' % Utils.str(weekday_list)) Logger.debug('export_list: %s' % Utils.str(export_list)) for export in export_list: self.run_export(export) def _get_progress_header(self, export): sid = 32024 if Utils.get_safe_value(export, 'origin', '') == 'watch': sid = 32088 return self._common_addon.getLocalizedString( sid) + ': ' + Utils.unicode(export['name']) def _get_percent(self, completed, target): if target > 0: return completed * 100 / target return 0 def _show_progress_before_change(self, change, pending_changes, changes_done, retry_changes, ignored, export): completed = len(changes_done) + len(retry_changes) + ignored target = len(pending_changes) + completed + 1 self._export_progress_dialog_bg.update( self._get_percent(completed, target), self._addon_name + ' ' + self._get_progress_header(export), Utils.get_safe_value(change, 'name', 'n/a')) def _show_progress_after_change(self, change, change_type, pending_changes, changes_done, retry_changes, ignored, export): completed = len(changes_done) + len(retry_changes) + ignored target = len(pending_changes) + completed msg = self._common_addon.getLocalizedString(32041) if change_type: msg = Utils.get_safe_value(change, 'name', 'n/a') self._export_progress_dialog_bg.update( self._get_percent(completed, target), self._addon_name + ' ' + self._get_progress_header(export), msg) def run_export(self, export): exporting = Utils.get_safe_value(export, 'exporting', False) Logger.debug('Run export requested. Exporting = %s' % (exporting, )) if not exporting: export['exporting'] = True export['origin'] = 'schedule' self.export_manager.save_export(export) try: show_export_progress = KodiUtils.get_addon_setting( 'hide_export_progress') != 'true' if show_export_progress: self._export_progress_dialog_bg.create( self._addon_name + ' ' + self._common_addon.getLocalizedString(32024), self._common_addon.getLocalizedString(32025)) export_folder = export['destination_folder'] if not KodiUtils.file_exists(export_folder): Logger.debug('creating folder: %s' % (export_folder, )) if not KodiUtils.mkdirs(export_folder): Logger.debug('unable to create folder %s' % (export_folder, )) if KodiUtils.file_exists(export_folder): driveid = export['driveid'] self.provider.configure(self._account_manager, driveid) exportid = export['id'] items_info = {} ExportManager.add_item_info(items_info, 'root-folder', None, export_folder, None, 'folder') self.export_manager.save_items_info(exportid, items_info) item = self.provider.get_item(export['item_driveid'], exportid) item.update({ 'parent': 'root-folder', 'origin': 'schedule' }) self.export_manager.save_pending_changes( exportid, deque([item])) self.export_manager.save_retry_changes(exportid, deque([])) if show_export_progress: progress_listener = self._show_progress_before_change else: progress_listener = None changes_done = self.process_pending_changes( exportid, on_before_change=progress_listener) if changes_done: if Utils.get_safe_value(export, 'update_library', False): if Utils.get_safe_value(export, 'content_type', '') == 'audio': KodiUtils.update_library('music') else: KodiUtils.update_library('video') except Exception as e: ErrorReport.handle_exception(e) KodiUtils.show_notification( self._common_addon.getLocalizedString(32027) + ' ' + Utils.unicode(e)) finally: export['exporting'] = False del export['origin'] self.export_manager.save_export(export) self._export_progress_dialog_bg.close() else: KodiUtils.show_notification( self._common_addon.getLocalizedString(32059) + ' ' + self._common_addon.getLocalizedString(32038)) def get_folder_changes(self, driveid, folder, on_before_add_item=None): return self.provider.get_folder_items( Utils.default(Utils.get_safe_value(folder, 'drive_id'), driveid), folder['id'], include_download_info=True, on_before_add_item=on_before_add_item) def on_before_add_item(self, export, item): item['origin'] = Utils.get_safe_value(export, 'origin', '') def process_pending_changes(self, exportid, on_after_change=None, on_before_change=None): changes_done = [] pending_changes = self.export_manager.get_pending_changes(exportid) if pending_changes: export = self.export_manager.get_exports()[exportid] Logger.debug('*** Processing all changes for export id: %s' % exportid) try: Logger.debug(' Exporting "%s" in %s' % (Utils.unicode(export['name']), Utils.unicode(export['destination_folder']))) except Exception: Logger.debug(' Export name: %s' % Utils.str(export['name'])) Logger.debug(' Export destination_folder: %s' % Utils.str(export['destination_folder'])) items_info = Utils.default( self.export_manager.get_items_info(exportid), {}) retry_changes = [] processed_changes = set() ignored = 0 while len(pending_changes) > 0: change = pending_changes.popleft() change_id = change['id'] if change_id in processed_changes: continue processed_changes.add(change_id) if on_before_change: on_before_change(change, pending_changes, changes_done, retry_changes, ignored, export) change_type = self.process_change(change, items_info, export) self.export_manager.save_items_info(exportid, items_info) self.export_manager.save_pending_changes( exportid, pending_changes) is_retry = False if change_type: if change_type[-6:] == "_retry": is_retry = True retry_changes.append(change) Logger.debug('change marked for retry') else: changes_done.append(change) if change_type == 'create_folder' or ( change_type == 'create_folder_ignored' and Utils.get_safe_value(change, 'origin', '') == 'schedule'): before_add_item = lambda item: self.on_before_add_item( change, item) pending_changes.extendleft( self.get_folder_changes( export['driveid'], change, before_add_item)) self.export_manager.save_pending_changes( exportid, pending_changes) else: ignored += 1 if on_after_change: on_after_change(change, change_type, pending_changes, changes_done, retry_changes, ignored, export) if is_retry: self.export_manager.save_retry_changes( exportid, deque(retry_changes)) return changes_done def process_watch(self): exports = self.export_manager.get_exports() update_library = {} changes_by_drive = {} for exportid in exports: export = exports[exportid] watch = Utils.get_safe_value(export, 'watch', False) exporting = Utils.get_safe_value(export, 'exporting', False) retry_changes = self.export_manager.get_retry_changes(exportid) if (watch or len(retry_changes) > 0) and not exporting: items_info = self.export_manager.get_items_info(exportid) if items_info: export['exporting'] = True export['origin'] = 'watch' self.export_manager.save_export(export) try: driveid = export['driveid'] if driveid in changes_by_drive: changes = changes_by_drive[driveid] else: self.provider.configure(self._account_manager, export['driveid']) changes = self.provider.changes() changes_by_drive[driveid] = [] changes_by_drive[driveid].extend(changes) pending_changes = self.export_manager.get_pending_changes( exportid) pending_changes.extend(retry_changes) pending_changes.extend(changes) if len(changes) > 0 or len(retry_changes) > 0: self.export_manager.save_pending_changes( exportid, pending_changes) if len(retry_changes) > 0: self.export_manager.save_retry_changes( exportid, deque([])) show_export_progress = KodiUtils.get_addon_setting( 'hide_export_progress') != 'true' if pending_changes and show_export_progress: self._export_progress_dialog_bg.update( 0, self._addon_name + ' ' + self._common_addon.getLocalizedString(32088), self._common_addon.getLocalizedString(32025)) if show_export_progress: progress_listener = self._show_progress_after_change else: progress_listener = None changes_done = self.process_pending_changes( exportid, on_after_change=progress_listener) if changes_done: if Utils.get_safe_value(export, 'update_library', False): update_library[Utils.get_safe_value( export, 'content_type', 'None')] = True for change in changes_done: if change in changes_by_drive[driveid]: changes_by_drive[driveid].remove(change) except Exception as e: ErrorReport.handle_exception(e) KodiUtils.show_notification( self._common_addon.getLocalizedString(32027) + ' ' + Utils.unicode(e)) finally: export['exporting'] = False del export['origin'] self.export_manager.save_export(export) else: self.run_export(export) self._export_progress_dialog_bg.close() if update_library: if Utils.get_safe_value(update_library, 'video', False): KodiUtils.update_library('video') if Utils.get_safe_value(update_library, 'audio', False): KodiUtils.update_library('music') @timeit def process_change(self, change, items_info, export): change_type = None changed_item_id = change['id'] Logger.debug('change_object: %s' % Utils.str(change)) changed_item_name = Utils.get_safe_value(change, 'name', '') deleted = Utils.get_safe_value( change, 'deleted') or Utils.get_safe_value(change, 'removed') parent_id = Utils.get_safe_value(change, 'parent', '') if changed_item_id in items_info: item_info = items_info[changed_item_id] item_info_path = item_info['full_local_path'] if KodiUtils.file_exists(item_info_path): if deleted: change_type = self.process_change_delete( change, items_info) elif parent_id != item_info[ 'parent'] or changed_item_name != item_info['name']: if parent_id in items_info: change_type = self.process_change_move( change, items_info) elif changed_item_id != export['id']: Logger.debug( 'change is move to a parent not in item list. deleting from current export info and ignoring (could be moved to another export info)' ) self.process_change_delete(change, items_info) else: change_type = self.process_change_create( change, items_info, export) elif not deleted: Logger.debug( 'changed item not found in its location: %s. creating...' % item_info_path) change_type = self.process_change_create( change, items_info, export) elif parent_id in items_info and not deleted: change_type = self.process_change_create(change, items_info, export) Logger.debug('change_type: %s ' % Utils.str(change_type)) return change_type def process_change_delete(self, change, items_info): change_type = 'delete' changed_item_id = change['id'] item_info = items_info[changed_item_id] item_info_path = item_info['full_local_path'] item_type = item_info['type'] is_folder = item_type == 'folder' Logger.debug('deleting: %s' % item_info_path) if KodiUtils.file_exists(item_info_path): if is_folder: change_type += '_folder' if not Utils.remove_folder(item_info_path, self._system_monitor): change_type += '_retry' else: change_type += '_file' if not KodiUtils.file_delete(item_info_path): change_type += '_retry' else: Logger.debug('file already deleted: %s' % item_info_path) change_type += '_ignored' ExportManager.remove_item_info(items_info, changed_item_id) return change_type def process_change_move(self, change, items_info): change_type = 'move' parent_id = Utils.get_safe_value(change, 'parent', '') parent_item_info = items_info[parent_id] parent_item_path = parent_item_info['full_local_path'] changed_item_id = change['id'] changed_item_name = Utils.get_safe_value(change, 'name', '') changed_item_extension = Utils.get_safe_value(change, 'name_extension', '') changed_item_mimetype = Utils.get_safe_value(change, 'mimetype', '') item_info = items_info[changed_item_id] item_type = item_info['type'] is_folder = item_type == 'folder' item_info_path = item_info['full_local_path'] new_path = os.path.join(Utils.unicode(parent_item_path), Utils.unicode(changed_item_name)) if is_folder: change_type += '_folder' new_path = os.path.join(new_path, '') else: change_type += '_file' if changed_item_extension in self._video_file_extensions or 'video' in changed_item_mimetype or 'video' in change: new_path += '.strm' Logger.debug('%s from: %s to: %s' % ( change_type, item_info_path, new_path, )) if KodiUtils.file_exists(new_path): Logger.debug('location already exists: %s. removing...' % (new_path, )) if is_folder: Utils.remove_folder(item_info_path, self._system_monitor) else: KodiUtils.file_delete(item_info_path) if not KodiUtils.file_rename(item_info_path, new_path): change_type += '_retry' ExportManager.add_item_info(items_info, changed_item_id, Utils.unicode(changed_item_name), new_path, parent_id, item_type) return change_type def process_change_create(self, change, items_info, export): content_type = export['content_type'] changed_item_id = change['id'] changed_item_name = Utils.get_safe_value(change, 'name', '') changed_item_extension = Utils.get_safe_value(change, 'name_extension', '') parent_id = Utils.get_safe_value(change, 'parent', '') is_folder = 'folder' in change item_type = 'folder' if is_folder else 'file' parent_item_info = Utils.get_safe_value(items_info, parent_id) if parent_item_info: parent_item_path = parent_item_info['full_local_path'] new_path = os.path.join(Utils.unicode(parent_item_path), Utils.unicode(changed_item_name)) change_type = 'create' if is_folder: change_type += '_folder' new_path = os.path.join(new_path, '') if parent_id == 'root-folder' and KodiUtils.get_addon_setting( 'clean_folder') == 'true' and KodiUtils.file_exists( new_path): if not Utils.remove_folder(new_path): error = self._common_addon.getLocalizedString( 32066) % new_path KodiUtils.show_notification(error) Logger.debug(error) if not KodiUtils.file_exists(new_path): Logger.debug('creating folder: %s' % (new_path, )) if not KodiUtils.mkdir(new_path): change_type += '_retry' Logger.debug('unable to create folder %s' % (new_path, )) else: change_type += '_ignored' Logger.debug('folder %s already exists' % (new_path, )) else: download_artwork = 'download_artwork' in export and export[ 'download_artwork'] is_download = changed_item_extension \ and ( changed_item_extension in ['strm', 'nomedia'] or ( download_artwork and ( changed_item_extension in ['nfo'] or ( changed_item_extension in ['jpg', 'png'] and ( any(s in changed_item_name for s in self._artwork_file_extensions) or parent_item_info['name'] in ['.actors', 'extrafanart'] ) ) ) ) ) if is_download: Logger.debug('downloading file: %s' % (new_path, )) change_type = 'download_file' cloud_size = Utils.get_safe_value(change, 'size', 0) local_size = KodiUtils.file(new_path).size() if cloud_size != local_size: Logger.debug( 'Download requested. File changed: Local file size (%s) - cloud file size (%s)' % ( Utils.str(local_size), Utils.str(cloud_size), )) if not ExportManager.download(change, new_path, self.provider): change_type += "_retry" Logger.debug('Unable to download file: %s' % (new_path, )) else: change_type += '_ignored' Logger.debug( 'Download ignored: Local file size (%s) is equal to cloud file size (%s)' % ( Utils.str(local_size), Utils.str(cloud_size), )) else: is_stream_file = (('video' in change or (changed_item_extension and changed_item_extension in self._video_file_extensions)) and content_type == 'video') \ or (('audio' in change or (changed_item_extension and changed_item_extension in self._audio_file_extensions)) and content_type == 'audio') if is_stream_file: change_type += '_file' if KodiUtils.get_addon_setting( 'no_extension_strm') == 'true': new_path = Utils.remove_extension(new_path) new_path += ExportManager._strm_extension strm_content = ExportManager.get_strm_link( export['driveid'], change, content_type, 'plugin://%s/' % self.addonid) Logger.debug('creating strm file: %s' % (new_path, )) if not KodiUtils.file_exists( new_path) or KodiUtils.file( new_path).size() != len(strm_content): if not ExportManager.create_text_file( new_path, strm_content): change_type += '_retry' else: change_type += '_ignored' Logger.debug( 'ignoring strm creation: %s, strm file already exists. same expected size.' % (new_path, )) else: change_type = None Logger.debug('ignoring file: %s' % (new_path, )) if change_type: ExportManager.add_item_info(items_info, changed_item_id, Utils.unicode(changed_item_name), new_path, parent_id, item_type) else: Logger.debug('invalid state. no parent info found') change_type = None return change_type def stop(self): self.abort = True
class ExportService(object): name = 'export' def __init__(self, provider_class): self.abort = False self._system_monitor = KodiUtils.get_system_monitor() self.provider = provider_class() self.addonid = KodiUtils.get_addon_info('id') self._profile_path = Utils.unicode( KodiUtils.translate_path(KodiUtils.get_addon_info('profile'))) self._startup_type = Utils.str(ExportScheduleDialog._startup_type) self.export_manager = ExportManager(self._profile_path) self._account_manager = AccountManager(self._profile_path) self._video_file_extensions = KodiUtils.get_supported_media("video") self._audio_file_extensions = KodiUtils.get_supported_media("music") def __del__(self): del self._system_monitor del self.export_manager del self._account_manager def cleanup_export_map(self): exports = self.export_manager.load() for exportid in exports: export = exports[exportid] export['exporting'] = False self.export_manager.save() def get_export_map(self): exports = self.export_manager.load() export_map = {} for exportid in exports: export = exports[exportid] schedules = Utils.get_safe_value(export, 'schedules', []) exporting = Utils.get_safe_value(export, 'exporting', False) if not exporting: if Utils.get_safe_value(export, 'schedule', False) and schedules: for schedule in schedules: key = Utils.str( Utils.get_safe_value(schedule, 'type', '')) if key != self._startup_type: key += Utils.get_safe_value(schedule, 'at', '') export_map[key] = Utils.get_safe_value( export_map, key, []) export_map[key].append(export) if Utils.get_safe_value(export, 'watch', False): export_map['watch'] = Utils.get_safe_value( export_map, 'watch', []) export_map['watch'].append(export) Logger.debug('export_map: %s' % Utils.str(export_map)) return export_map def start(self): Logger.notice('Service \'%s\' started.' % self.name) self.cleanup_export_map() monitor = KodiUtils.get_system_monitor() startup = True while not self.abort: try: now = datetime.datetime.now() export_map = self.get_export_map() if export_map: self.process_schedules(export_map, now, startup) self.process_watch(export_map) except Exception as e: ErrorReport.handle_exception(e) startup = False if monitor.waitForAbort(60): break del monitor del self.provider Logger.notice('Service stopped.') def process_schedules(self, export_map, now, startup=False): Logger.debug('now: %s, startup: %s' % (Utils.str(now), Utils.str(startup))) export_list = [] if startup: export_list.extend( Utils.get_safe_value(export_map, self._startup_type, [])) else: at = '%02d:%02d' % ( now.hour, now.minute, ) Logger.debug('at: %s' % Utils.str(at)) daily_list = Utils.get_safe_value( export_map, Utils.str(ExportScheduleDialog._daily_type) + at, []) export_list.extend(daily_list) Logger.debug('daily_list: %s' % Utils.str(daily_list)) weekday = now.weekday() + 11 weekday_list = Utils.get_safe_value(export_map, Utils.str(weekday) + at, []) export_list.extend(weekday_list) Logger.debug('weekday_list: %s' % Utils.str(weekday_list)) Logger.debug('export_list: %s' % Utils.str(export_list)) for export in export_list: self.run_export(export) def run_export(self, export): export['exporting'] = True params = { 'action': '_run_export', 'content_type': Utils.get_safe_value(export, 'content_type', ''), 'driveid': Utils.get_safe_value(export, 'driveid', ''), 'item_id': export['id'] } KodiUtils.run_plugin(self.addonid, params, False) def process_watch(self, export_map): exports = Utils.get_safe_value(export_map, 'watch', []) update_library = {} changes_by_drive = {} for export in exports: item_id = export['id'] driveid = export['driveid'] if driveid in changes_by_drive: changes = changes_by_drive[driveid] else: self.provider.configure(self._account_manager, export['driveid']) changes = self.provider.changes() changes_by_drive[driveid] = changes items_info = self.export_manager.get_items_info(item_id) if items_info: if changes and not Utils.get_safe_value( export, 'exporting', False): Logger.debug( '*** Processing changes for export "%s" in %s' % (export['name'], export['destination_folder'])) while True: changes_retry = [] changes_done = [] for change in changes: change_type = self.process_change( change, items_info, export) if change_type and change_type != 'retry': changes_done.append(change) self.export_manager.save_items_info( item_id, items_info) if Utils.get_safe_value( export, 'update_library', False): update_library[Utils.get_safe_value( export, 'content_type', 'None')] = True elif change_type and change_type == 'retry': changes_retry.append(change) for change in changes_done: changes_by_drive[driveid].remove(change) if changes_done and changes_retry: changes = changes_retry Logger.debug('Retrying pending changes...') else: break else: self.run_export(export) if update_library: if Utils.get_safe_value(update_library, 'video', False): KodiUtils.update_library('video') if Utils.get_safe_value(update_library, 'audio', False): KodiUtils.update_library('music') def process_change_delete(self, items_info, item_id, is_folder): change_type = 'delete' item_info = items_info[item_id] item_info_path = item_info['full_local_path'] if KodiUtils.file_exists(item_info_path): if is_folder: Logger.debug('Change is delete folder: %s' % item_info_path) if not self.remove_folder(item_info_path): change_type = 'retry' else: Logger.debug('Change is delete file') if not KodiUtils.file_delete(item_info_path): change_type = 'retry' if change_type != 'retry': ExportManager.remove_item_info(items_info, item_id) return change_type def remove_folder(self, folder_path): if not KodiUtils.rmdir(folder_path, True): if self._system_monitor.waitForAbort(3): return False return KodiUtils.rmdir(folder_path, True) return True def process_change(self, change, items_info, export): change_type = None changed_item_id = change['id'] Logger.debug('Change: %s' % Utils.str(change)) if changed_item_id != export['id']: changed_item_name = change['name'] deleted = Utils.get_safe_value(change, 'deleted') parent_id = change['parent'] is_folder = 'folder' in change if not is_folder: changed_item_name += ExportManager._strm_extension if changed_item_id in items_info: item_info = items_info[changed_item_id] Logger.debug('item_info: %s' % Utils.str(item_info)) item_info_path = item_info['full_local_path'] if KodiUtils.file_exists(item_info_path): if deleted: change_type = self.process_change_delete( items_info, changed_item_id, is_folder) elif parent_id != item_info[ 'parent'] or changed_item_name != item_info['name']: if parent_id in items_info: change_type = 'move' Logger.debug('Change is move') parent_item_info = items_info[parent_id] parent_item_path = parent_item_info[ 'full_local_path'] new_path = os.path.join(parent_item_path, changed_item_name) if is_folder: new_path = os.path.join(new_path, '') if KodiUtils.file_rename(item_info_path, new_path): ExportManager.remove_item_info( items_info, changed_item_id) ExportManager.add_item_info( items_info, changed_item_id, changed_item_name, new_path, parent_id) else: change_type = 'retry' else: Logger.debug( 'Change is move but parent not in item list. Change is delete' ) change_type = self.process_change_delete( items_info, changed_item_id, is_folder) else: Logger.debug( 'Invalid state. Changed item not found: %s. Deleting from item list.' % item_info_path) change_type = self.process_change_delete( items_info, changed_item_id, is_folder) elif parent_id in items_info and not deleted: content_type = export['content_type'] item_name_extension = change['name_extension'] if is_folder or ( ('video' in change or item_name_extension in self._video_file_extensions) and content_type == 'video') or ( 'audio' in change and content_type == 'audio'): change_type = 'add' Logger.debug('Change is new item') parent_item_info = items_info[parent_id] parent_item_path = parent_item_info['full_local_path'] new_path = os.path.join(parent_item_path, changed_item_name) if is_folder: new_path = os.path.join(new_path, '') if not KodiUtils.mkdirs(new_path): change_type = 'retry' else: ExportManager.create_strm( export['driveid'], change, new_path, content_type, 'plugin://%s/' % self.addonid) if change_type != 'retry': ExportManager.add_item_info(items_info, changed_item_id, changed_item_name, new_path, parent_id) Logger.debug('change type: %s ' % Utils.str(change_type)) return change_type def stop(self): self.abort = True