Example #1
0
 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 Utils.remove_folder(item_info_path,
                                        self._system_monitor):
                 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
Example #2
0
 def _remove_export(self, driveid, item_id):
     self._export_manager = ExportManager(self._profile_path)
     item = self._export_manager.get_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')
Example #3
0
 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()
Example #4
0
 def __init__(self, *args, **kwargs):
     self.content_type = urllib.unquote(kwargs["content_type"])
     self.driveid = kwargs["driveid"]
     self.item_driveid = kwargs["item_driveid"]
     self.item_id = kwargs["item_id"]
     self.name = urllib.unquote(kwargs["name"])
     self.account_manager = kwargs["account_manager"]
     self.provider = kwargs["provider"]
     self.provider.configure(self.account_manager, self.driveid)
     self.export_manager = ExportManager(
         self.account_manager._addon_data_path)
     self._addon_name = KodiUtils.get_addon_info('name')
     self._common_addon = KodiUtils.get_common_addon()
     self._dialog = xbmcgui.Dialog()
     self.editing = False
     self.canceled = False
     self.run = False
     self.schedules = []
Example #5
0
 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)
Example #6
0
 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
Example #7
0
 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
Example #8
0
 def __export_folder(self, driveid, folder, export_folder, export,
                     items_info):
     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)
             self.__export_folder(driveid, item, folder_path, export,
                                  items_info)
         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
             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)
         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']):])
Example #9
0
 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)
Example #10
0
 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)
Example #11
0
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()
Example #12
0
 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 = Utils.get_safe_value(change, 'name', '')
         deleted = 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_type = item_info['type']
             is_folder = item_type == 'folder'
             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,
                             Utils.unicode(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,
                                 Utils.unicode(changed_item_name), new_path,
                                 parent_id, item_type)
                         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:
             is_folder = 'application/vnd.google-apps.folder' in change.get(
                 'mimetype')
             content_type = export['content_type']
             item_name_extension = change['name_extension']
             is_stream_file = (
                 ('video' in change
                  or item_name_extension in self._video_file_extensions)
                 and content_type == 'video') or ('audio' in change and
                                                  content_type == 'audio')
             item_type = 'folder' if is_folder else 'file'
             if is_folder or is_stream_file or (
                     export['nfo_export'] and
                 ('nfo' in item_name_extension
                  or 'text/x-nfo' in change.get("mimetype"))):
                 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,
                                         Utils.unicode(changed_item_name))
                 if is_folder:
                     new_path = os.path.join(new_path, '')
                     if not KodiUtils.mkdirs(new_path):
                         change_type = 'retry'
                 elif is_stream_file:
                     new_path += '.strm'
                     ExportManager.create_strm(
                         export['driveid'], change, new_path, content_type,
                         'plugin://%s/' % self.addonid)
                 else:
                     ExportManager.create_nfo(
                         export['driveid'], change,
                         Utils.unicode(changed_item_name), new_path)
                 if change_type != 'retry':
                     ExportManager.add_item_info(
                         items_info, changed_item_id,
                         Utils.unicode(changed_item_name), new_path,
                         parent_id, item_type)
     Logger.debug('change type: %s ' % Utils.str(change_type))
     return change_type
Example #13
0
 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
Example #14
0
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
Example #15
0
class ExportMainDialog(xbmcgui.WindowXMLDialog):
    def __init__(self, *args, **kwargs):
        self.content_type = urllib.unquote(kwargs["content_type"])
        self.driveid = kwargs["driveid"]
        self.item_driveid = kwargs["item_driveid"]
        self.item_id = kwargs["item_id"]
        self.name = urllib.unquote(kwargs["name"])
        self.account_manager = kwargs["account_manager"]
        self.provider = kwargs["provider"]
        self.provider.configure(self.account_manager, self.driveid)
        self.export_manager = ExportManager(
            self.account_manager._addon_data_path)
        self._addon_name = KodiUtils.get_addon_info('name')
        self._common_addon = KodiUtils.get_common_addon()
        self._dialog = xbmcgui.Dialog()
        self.editing = False
        self.canceled = False
        self.run = False
        self.schedules = []

    def __del__(self):
        del self._common_addon
        del self._dialog

    @staticmethod
    def create(content_type, driveid, item_driveid, item_id, name,
               account_manager, provider):
        return ExportMainDialog("export-main-dialog.xml",
                                KodiUtils.get_common_addon_path(),
                                "default",
                                content_type=content_type,
                                driveid=driveid,
                                item_driveid=item_driveid,
                                item_id=item_id,
                                name=name,
                                account_manager=account_manager,
                                provider=provider)

    def iscanceled(self):
        return self.canceled

    def onInit(self):
        self.title_label = self.getControl(1000)
        self.cancel_button = self.getControl(999)
        self.save_button = self.getControl(1001)
        self.save_export_button = self.getControl(1002)

        self.drive_name_label = self.getControl(1003)
        self.drive_folder_label = self.getControl(1004)
        self.dest_folder_label = self.getControl(1005)
        self.dest_folder_button = self.getControl(1006)

        self.update_library_sw = self.getControl(1007)
        self.watch_drive_sw = self.getControl(1008)
        self.schedule_sw = self.getControl(1009)

        self.schedule_label = self.getControl(10100)
        self.schedule_list = self.getControl(1010)
        self.add_schedule_button = self.getControl(1011)
        self.setFocus(self.dest_folder_button)

        self.schedule_label.setLabel(
            self._common_addon.getLocalizedString(32083))

        self.title_label.setLabel(self._addon_name + ' - ' +
                                  self._common_addon.getLocalizedString(32004))
        self.account_manager.load()
        account = self.account_manager.get_account_by_driveid(self.driveid)
        drive = self.account_manager.get_drive_by_driveid(self.driveid)
        drive_name = self.account_manager.get_account_display_name(
            account, drive, self.provider, True)
        self.drive_name_label.setLabel(drive_name)
        self.drive_folder_label.setLabel(self.name)

        exports = self.export_manager.load()
        export = Utils.get_safe_value(exports, self.item_id, {})
        if export:
            self.editing = True
            self.watch_drive_sw.setSelected(
                Utils.get_safe_value(export, 'watch', False))
            self.schedule_sw.setSelected(
                Utils.get_safe_value(export, 'schedule', False))
            self.update_library_sw.setSelected(
                Utils.get_safe_value(export, 'update_library', False))
            self.schedules = Utils.get_safe_value(export, 'schedules', [])
            for schedule in self.schedules:
                self.add_schedule_item(schedule)

        if not self.editing:
            self.select_detination()
        else:
            self.dest_folder_label.setLabel(
                Utils.get_safe_value(export, 'destination_folder', ''))
        self.schedule_enabled(self.schedule_sw.isSelected())

    def is_valid_export(self):
        if not self.dest_folder_label.getLabel():
            self._dialog.ok(
                self._addon_name,
                KodiUtils.localize(32084, addon=self._common_addon) %
                KodiUtils.localize(32085, addon=self._common_addon))
            return False
        return True

    def save_export(self):
        self.export_manager.add_export({
            'id':
            self.item_id,
            'item_driveid':
            self.item_driveid,
            'driveid':
            self.driveid,
            'name':
            self.name,
            'content_type':
            self.content_type,
            'destination_folder':
            self.dest_folder_label.getLabel(),
            'watch':
            self.watch_drive_sw.isSelected(),
            'schedule':
            self.schedule_sw.isSelected(),
            'update_library':
            self.update_library_sw.isSelected(),
            'schedules':
            self.schedules
        })

    def schedule_enabled(self, enabled):
        self.schedule_label.setEnabled(enabled)
        self.schedule_list.setEnabled(enabled)
        self.add_schedule_button.setEnabled(enabled)

    def select_detination(self, default=''):
        dest_folder = self._dialog.browse(
            0,
            self._common_addon.getLocalizedString(32002),
            'files',
            defaultt=default)
        self.dest_folder_label.setLabel(dest_folder)

    def add_schedule_item(self, schedule):
        list_item = xbmcgui.ListItem(self.get_schedule_statement(schedule))
        self.schedule_list.addItem(list_item)

    def get_schedule_statement(self, schedule):
        statement = self._common_addon.getLocalizedString(
            32079) + ' ' + KodiUtils.localize(schedule['type'],
                                              addon=self._common_addon)
        if schedule['type'] != ExportScheduleDialog._startup_type:
            statement = statement + ' ' + self._common_addon.getLocalizedString(
                32080) + ' ' + schedule['at']
        return statement

    def edit_selected_schedule(self):
        schedule = None
        editing = -1
        if self.getFocusId() == self.schedule_list.getId():
            editing = self.schedule_list.getSelectedPosition()
            schedule = self.schedules[editing]
        schedule_dialog = ExportScheduleDialog.create(schedule)
        schedule_dialog.doModal()
        if not schedule_dialog.iscanceled():
            valid = True
            if editing >= 0:
                schedule = schedule_dialog.schedule
                self.schedules[editing] = schedule
                self.schedule_list.getListItem(editing).setLabel(
                    self.get_schedule_statement(schedule))
            else:
                for schedule in self.schedules:
                    if schedule['type'] == schedule_dialog.schedule[
                            'type'] and schedule[
                                'at'] == schedule_dialog.schedule['at']:
                        valid = False
                        break
                if valid:
                    self.schedules.append(schedule_dialog.schedule)
                    self.add_schedule_item(schedule_dialog.schedule)

    def delete_selected_schedule(self):
        if self.getFocusId() == self.schedule_list.getId():
            index = self.schedule_list.getSelectedPosition()
            self.schedule_list.removeItem(index)
            self.schedules.remove(self.schedules[index])

    def onClick(self, control_id):
        if control_id == self.cancel_button.getId():
            self.canceled = True
            self.close()
        elif control_id == self.save_button.getId(
        ) or control_id == self.save_export_button.getId():
            if self.is_valid_export():
                self.save_export()
                self.run = control_id == self.save_export_button.getId()
                self.close()

        elif control_id == self.dest_folder_button.getId():
            self.select_detination(self.dest_folder_label.getLabel())
        elif control_id == self.schedule_sw.getId():
            self.schedule_enabled(self.schedule_sw.isSelected())
        elif control_id == self.schedule_list.getId(
        ) or control_id == self.add_schedule_button.getId():
            self.edit_selected_schedule()

    def onAction(self, action):

        if action.getId() == xbmcgui.ACTION_PREVIOUS_MENU or action.getId(
        ) == xbmcgui.ACTION_NAV_BACK:
            self.canceled = True
        elif action.getId() == xbmcgui.ACTION_CONTEXT_MENU:
            if self.getFocusId() == self.schedule_list.getId():
                index = self._dialog.contextmenu(['Edit...', 'Delete'])
                if index == 0:
                    self.edit_selected_schedule()
                elif index == 1:
                    self.delete_selected_schedule()
        super(ExportMainDialog, self).onAction(action)
Example #16
0
 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
Example #17
0
    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))
Example #18
0
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