class Drawers: def __init__(self, addon): self.addon = addon self.xml_file_location = xbmc.translatePath(addon['obj'].getSetting('xml_file_location')) self.root = ET.parse( self.xml_file_location ).getroot() if addon['obj'].getSetting('debug_update_xml'): self.xml_file_modified = int(xbmcvfs.Stat(self.xml_file_location).st_mtime()) else: self.xml_file_modified = 0 self.stream_folder = xbmc.translatePath(addon['obj'].getSetting('stream_folder_location')) xbmcvfs.mkdirs(self.stream_folder) self.url_replace = [ ('art\:\/\/', xbmc.translatePath(addon['obj'].getSetting('art_folder_location'))) ] self.force_refresh = False self.db = DBHandler(xbmc.translatePath('special://profile/addon_data/plugin.video.drawers/data/')) def get_drawer(self, path=None, force_refresh=False, cached=True): if path is None: path = self._get_path() self.force_refresh = force_refresh if cached: return self._get_cache_drawer(path) else: return self._get_drawer(path) def _get_drawer(self, path): (fids, sids, url) = self._parse_path(path) (items, settings) = self._create_items(fids, sids, url) return items, settings def update_drawers(self, force=False): wid = xbmcgui.getCurrentWindowId() if wid in WINDOW_UPDATE_IDS and not force: return 5 data = self.db.select('drawers', values=['path'], params={}, filter="nextupdate>0 AND nextupdate<%d" % int(time.time())) if len(data) > 0: self._get_cache_drawer(data[0]['path'], update=True) print '>>>> %s updated' % data[0]['path'] if len(data) > 1: return random.randint(2, 5) data = self.db.select('drawers', values=['nextupdate'], params={}, filter="nextupdate>0 AND nextupdate>%d" % int(time.time())) return int( min([int(d['nextupdate']- time.time()) for d in data] + [60]) + 2 ) def refresh_drawer(self): items, settings = self.get_drawer(force_refresh=True) xbmc.executebuiltin("Container.Refresh()") return items, settings def _get_cache_drawer(self, path, update=False): self.log( path ) handle = _get_handle(path, URL_FILTERS) db_data = self.db.select('drawers', values=['timeout','lastupdate'], params={'handle': handle}) # no cache available or refresh forced if self.force_refresh or update or db_data == [] or db_data[0]['lastupdate'] == 0: (fids, sids, url) = self._parse_path(path) (items, settings) = self._create_items(fids, sids, url) if self.force_refresh or update or db_data == [] \ or (settings['timeout'] > time.time() and settings['updated']): if settings.get('refresh', -1) > 0: nextupdate = int(time.time()) + settings['refresh'] else: nextupdate = settings['refresh'] self.db.upsert('drawers', values={'data': repr( (items, settings) ), 'lastupdate': int(time.time()), 'timeout': int(settings['timeout'] - time.time()), 'nextupdate': nextupdate, 'path': path}, params={'handle': handle}) #self.db.delete('drawer_source', params={'drawer': handle}) for source_handle in settings['sources']: self.db.insert('drawer_source', values={'drawer': handle, 'source': source_handle}) # cache available, but needs update elif int( db_data[0]['timeout'] + db_data[0]['lastupdate']) < time.time() \ or int( db_data[0]['lastupdate']) < self.xml_file_modified: (items, settings) = eval( self.db.select('drawers', values=['data'], params={'handle': handle})[0]['data'] ) settings['update'] = True if settings.get('refresh', -1) > 0: nextupdate = db_data[0]['lastupdate'] + settings['refresh'] else: nextupdate = settings.get('refresh', -1) self.db.upsert('drawers', values={'nextupdate': nextupdate}, params={'handle': handle}) # cache available and up to date else: (items, settings) = eval( self.db.select('drawers', values=['data'], params={'handle': handle})[0]['data'] ) # collect subdrawers in DB for caching if update is False and False: for item in items: if item['path'].startswith(self.addon['name']): settings['paths'] = settings.get('paths', []) + [item['path']] return items, settings def insert_paths(self, paths): for p in paths: handle = hash(p) data = self.db.select('drawers', values=['handle'], params={'handle': handle}) if data == []: self.db.upsert('drawers', values={'lastupdate': 0, 'timeout': 0, 'nextupdate': 1, 'path': p}, params={'handle': handle}) def _get_plugin_data(self, url, force_timeout): data, source_timeout, updated = self._cache_source(url, force_timeout) items = [] for f in data: info = dict((k, v) for (k, v) in f.iteritems() if k in video_info) if info.has_key('runtime') and not info.has_key('duration'): info['duration'] = info['runtime'] #info.pop('runtime') if info.has_key('duration') and info['duration'] < 3000: info['duration'] = info['duration'] * 60 if info.has_key('director') and isinstance(info['director'], list) and len(info['director']) > 0: info['director'] = info['director'][0] if info.has_key('studio') and isinstance(info['studio'], list): info['studio'] = '/'.join( info['studio'] ) if info.has_key('genre') and isinstance(info['genre'], list): info['genre'] = ' / '.join( info['genre'] ) info['isFolder'] = f.get('mimetype', '') == 'x-directory/normal' or f.get('filetype', '') == "directory" items.append( {'label': f.get('label', ''), 'path': f['file'], 'art': f.get('art', {}), 'info': info } ) return items, source_timeout, updated def _cache_source(self, source, force_timeout=-1): updated = False handle = _get_handle(source, URL_FILTERS) db_data = self.db.select('sources', values=['lastupdate', 'timeout', 'hash', 'hash_label'], params={'handle':handle}) if len(db_data) == 0: hash_old = '' timeout = 3 data_timeout = 0 hash_label = '' else: hash_old = db_data[0]['hash'] hash_label = db_data[0]['hash_label'] timeout = int(db_data[0]['timeout']) data_timeout = int( db_data[0]['lastupdate'] ) + SOURCE_TIMEOUTS[timeout] if data_timeout < time.time() or self.force_refresh: data = self._read_dir(source) raw_data = repr(data) hash_new = hash(raw_data) if hash_old != hash_new: updated = True # makes sure to only update timeout if actual ListElements are changed, # prevents updates due to changes in sessionids of path vars hash_label_new = hash(repr(sorted([d['label'] for d in data]))) if hash_label_new != hash_label: timeout = timeout - 1 else: timeout = timeout + (2 if not self.force_refresh else 1) timeout = min(max(0, timeout), len(SOURCE_TIMEOUTS)-1) self.db.upsert('sources', values={'source': source, 'lastupdate': time.time(), 'timeout': timeout, 'data': raw_data, 'hash': hash_new, 'hash_label': hash_label_new} , params={'handle': handle}) else: timeout = min(len(SOURCE_TIMEOUTS)-1, timeout + (2 if not self.force_refresh else 1) ) self.db.upsert('sources', values={'lastupdate': time.time(), 'timeout': timeout}, params={'handle': handle}) if force_timeout >= 0: data_timeout = time.time() + force_timeout else: data_timeout = time.time() + SOURCE_TIMEOUTS[timeout] else: raw_data = self.db.select('sources', values=['data'], params={'handle':handle}) data = eval( raw_data[0]['data'] ) return data, data_timeout, updated def _read_dir(self, dir): #xbmc.executebuiltin("Notification(Drawers, Reading: %s, 3000, C:/Users/Programming/AppData/Roaming/Kodi/addons/plugin.video.drawers/icon.png)" % dir ) viewmode = xbmcgui.Window(xbmcgui.getCurrentWindowId()).getFocusId() data = self._get_json().Files.GetDirectory(directory=dir, properties=file_info) if xbmcgui.Window(xbmcgui.getCurrentWindowId()).getFocusId() != viewmode: xbmc.executebuiltin('Container.setViewmode(%d)' % viewmode) if data is None or not data.has_key('files'): return [] return [clean(d) for d in data['files']] def _create_items(self, fids=[], sids=[], url='', depth=5, recur=False): items = [] menu = [('Refresh Drawer', 'RunPlugin("%s%s%s")' % (self._get_path(fids, sids, url), '&' if '?' in url else '?', 'drawer_action=force_refresh'))] timeout = time.time() + SOURCE_TIMEOUTS[-1] source_handles = [] updated = False # add folders if sids == []: for i, f in enumerate(self._xmlitems('folder', fids)): item = {'label': self._xmlitem('folder[%d]'% (i+1), fids).attrib['label'].encode('utf-8'), 'path': self._get_path(fids+[i+1]), 'info': { 'isPlayable': 'False', 'isFolder': True } } items.append( (item, fids+[i+1], []) ) # prepare to add items from sources if sids == []: sources = self._xmlitems('source', fids) else: source = self._xmlitem('source[%s]' % sids[0], fids) if url != '': source.attrib['url'] = url sources = [source] for s_i, s in enumerate(sources): if not s.attrib.has_key('url'): continue source_handles.append( _get_handle(s.attrib['url'], URL_FILTERS) ) if sids == []: sids_n = [str(s_i+1)] else: sids_n = sids force_timeout = calc_time( s.attrib['timeout'] ) if s.attrib.has_key('timeout') else -1 pitems, source_timeout, source_updated = self._get_plugin_data(s.attrib['url'], force_timeout) if timeout > source_timeout: timeout = source_timeout updated = updated or source_updated for p in pitems: p['url'] = s.attrib['url'] p['depth'] = depth if p['info'].get('isFolder', False): i, xml_subfolder = self._match_target(self._xmlitems('subfolder', fids, sids_n), \ p, attr='flatten', values=None) if xml_subfolder is not None: try: d = int(xml_subfolder.attrib['flatten']) except: d = 1 new_depth = min(depth, d) - 1 if new_depth >= 0: items_rec, source_handles_new = self._create_items(fids, sids_n, p['path'], new_depth, recur=True) items += items_rec source_handles += source_handles_new else: items.append( (p, fids, sids_n) ) else: items.append( (p, fids, sids_n) ) else: items.append( (p, fids, sids_n) ) if recur: return items, source_handles settings = self._mod_folder(fids, sids) items, settings = self._mod_items(items, settings, url) settings['sources'] = source_handles settings['timeout'] = timeout settings['updated'] = updated settings['menu'] = settings.get('menu', []) + menu return items, settings def _mod_folder(self, fids, sids): options = self._xmlitem('', fids, sids if len(sids) > 1 else []).attrib settings = { 'content': options.get('content', 'files'), 'refresh': -1 } if options.has_key('viewmode'): settings['viewmode'] = options['viewmode'] if options.has_key('timeout'): settings['max_timeout'] = options['timeout'] if options.has_key('update'): settings['refresh'] = calc_time( options['update'] ) return settings def _mod_item(self, item, fids, sids): items, s = self._mod_items([(item, fids, sids)], url='', add_folders=False) if len(items) == 0: return None return items[0] def _mod_items(self, items, settings={}, url='', add_folders=True): mod_items = [] added_items = [] for _item in items: item, fids, sids = _item item_mods = [] s_i = [] if len(sids) > 1: parent_folder = self._xmlitem('', fids, sids) if parent_folder is not None and parent_folder.attrib.get('type', '') == 'copy': item = self._mod_item(item, fids, sids[:-1]) if item is None: continue #=================================================================== # item matching #=================================================================== xml_subfolders = self._xmlitems('subfolder', fids, sids) xml_matches = self._match_targets( xml_subfolders, item, attr='type', values=['copy']) for i, xml_item in xml_matches: if url == '': url = self._xmlitem('', fids, [sids[0]]).attrib['url'] item_new = {'label': xml_item.get('label', 'Copyfolder'), 'path': self._get_path(fids, sids + [i], url), 'info': { 'isFolder': True } } if item_new['label'] not in [it.get('label','') for it in added_items if it is not None] \ and add_folders: item_new, s = self._mod_item_attrib(item_new, [xml_item.attrib]) if item_new is not None: item_new = self._mod_item(item_new, fids, sids) if item_new is not None: added_items.append( item_new ) if str(item['info']['isFolder']) == 'False': xml_items = self._xmlitems('item', fids, sids) xml_matches = self._match_targets( xml_items, item, attr='type', values=[None]) for i, xml_item in xml_matches: s_i.append( i ) item_mods.append( xml_item.attrib ) else: i, xml_subfolder = self._match_target( xml_subfolders, item, attr='type', values=['paging']) if xml_subfolder is not None: item['path'] = self._get_path(fids, sids, item['path']) item['type'] = 'paging' if xml_subfolder.attrib.has_key('precache'): settings['precache'] = settings.get('precache', []) + [item['path']] item_mods.append( xml_subfolder.attrib ) i, xml_subfolder = self._match_target( xml_subfolders, item, attr='type', values=[None]) if xml_subfolder is not None: item['path'] = self._get_path(fids, sids + [i], item['path']) item_mods.append( xml_subfolder.attrib ) elif sids == []: item_mods.append( self._xmlitem('', fids).attrib ) #=================================================================== # set item attributes #================================================================== item, settings_mod = self._mod_item_attrib(item, item_mods) print settings_mod if settings_mod.has_key('refresh'): print 'REFRESHING' if settings['refresh'] > 0: settings['refresh'] = min(settings['refresh'], settings_mod['refresh']) else: settings['refresh'] = settings_mod['refresh'] #=================================================================== # apply folder/source/subfolder mods #=================================================================== for i in s_i: item = self._mod_groups(item, fids, sids, i) if len(sids) == 1: item = self._mod_groups(item, fids) item = self._mod_groups(item, fids, sids[0]) if len(sids) > 1: item = self._mod_groups(item, fids, sids) if not item: continue if item.has_key('movie_stream'): settings['movie_stream'] = True if item.has_key('tvshow_stream'): settings['tvshow_stream'] = True # Propages show title to subfolders if (settings.get('content', '') == 'tvshows' or self.addon['options'].has_key('drawer_show')) and str(item['info']['isFolder']) == 'True': path = item.get('path', '') if path != '': if settings['content'] == 'tvshows': show = item['info'].get('titel', item.get('label', '')) else: show = self.addon['options']['drawer_show'] path += ('?' if not '?' in path else '&') + 'drawer_show=' + show item['path'] = path if settings.get('content', '') == 'episodes' and self.addon['options'].has_key('drawer_show'): item['info']['showtitle'] = urllib.unquote( self.addon['options']['drawer_show'] ) if item not in mod_items: mod_items.append(item) if add_folders: mod_items += added_items return mod_items, settings def _mod_item_attrib(self, item, item_mods): settings = {} for item_mod in item_mods: if item_mod.has_key('label'): item['label'] = item_mod['label'] item['art'] = item.get('art', {}) if item_mod.has_key('poster'): item['art']['poster'] = self._mod_url( item_mod['poster'] ) if item_mod.has_key('fanart'): item['art']['fanart'] = self._mod_url( item_mod['fanart'] ) if item_mod.has_key('banner'): item['art']['banner'] = self._mod_url( item_mod['banner'] ) if item_mod.has_key('thumb'): item['art']['thumb'] = self._mod_url( item_mod['thumb'] ) if item_mod.has_key('hide'): item = None if item_mod.has_key('force_refresh'): path = item.get('path', '') if path != '': path += ('?' if not '?' in path else '&') + 'drawer_options=force_refresh' item['path'] = path if item_mod.has_key('update'): settings['refresh'] = calc_time( item_mod['update'] ) return item, settings def _mod_groups(self, item, fids=[], sids=[], iid=None): if iid is not None: xml_items = self._xmlitems('item[%d]/mod' % iid, fids, sids) else: xml_items = self._xmlitems('mod', fids, sids) xml_items = xml_items for mod in xml_items: if item is None: return None options = mod.attrib type = options.get('type', '') if type == 'replace': value = self._get_prop(item, options.get('target', 'label')) if options.has_key('pattern'): value = re.sub(options['pattern'], options.get('replacement',''), value) for pattern in mod.findall('./pattern'): value = re.sub(pattern.text, pattern.attrib.get('replacement', ''), value) self._set_prop(item, options.get('target', 'label'), value) elif type == 'remove': if self._match_pattern(mod, item): item = None elif type == 'set_prop' and options.has_key('var'): if options.has_key('value'): self._set_prop(item, options['var'], options['value']) elif options.has_key('source'): value = self._get_prop(item, options['source']) if value is not None: self._set_prop(item, options['var'], value) elif type == 'get_movies': # try: from metahandler.metahandlers import MetaData metahandler = MetaData() #if metahandler._cache_lookup_by_name('movie', item['label']) is not None: label = self._get_prop(item, options.get('target', 'label')) data = metahandler.get_meta('movie', item['label'].encode('utf-8')) if data is None or not abs(int(data['year']) - int(item['info'].get('year', 0)) ) <= 1: data = None if not item['info'].has_key('year'): continue movies = metahandler.search_movies(item['label'].encode('utf-8')) if movies is None: continue movies = [m for m in movies if m.get('year', None) is not None] movies = sorted(movies, key=lambda m: abs(int(m.get('year', 0)) - int(item['info']['year'])) ) data = metahandler.get_meta('movie', item['label'].encode('utf-8'), tmdb_id=movies[0]['tmdb_id']) if data is None: continue data = clean(data) if data.get('cover_url','') != '': item['art']['poster'] = data['cover_url'] if data.get('backdrop_url','') != '': item['art']['fanart'] = data['backdrop_url'] data = dict( (k, v) for k, v in data.items() if k not in ['cover_url', 'backdrop_url', 'thumb', 'thumbnail', 'thumb_url'] ) item['info'].update(data) elif type == 'get_tvshows': try: from metahandler.metahandlers import MetaData metahandler = MetaData() except: pass data = metahandler.get_meta('tvshow', item['label'].encode('utf-8'), imdb_id=options.get('imdb_id', '')) if data is not None: data = clean(data) item['info'].update(data) if data.get('cover_url','') != '': item['art']['poster'] = data['cover_url'] if data.get('backdrop_url','') != '': item['art']['fanart'] = data['backdrop_url'] if data.get('banner_url','') != '': item['art']['banner'] = data['banner_url'] elif type == "create_movie_streams": item['movie_stream'] = options.get('folder', '') elif type == "create_tvshow_streams": item['tvshow_stream'] = options.get('folder', '') return item def create_movie_streams(self, items): for item in items: if not item.has_key('movie_stream'): continue folder = os.path.join(self.stream_folder, item['movie_stream']) if item['path'] != '' and self.addon['name'] not in item['path']: name = re.sub('(\:|\.|\,)', '', item['label']) name = name + (' (%s)' % item['info']['year'] if item['info'].get('year','') != '' else '') subfolder = self._make_dir(folder, name) self._create_file(subfolder, name + '.strm', str(item.get('path', '')) ) def create_tvshow_streams(self, items): for item in items: if not item['info'].get('isFolder', True) and item.has_key('tvshow_stream') and item['info'].has_key('showtitle'): name = item['info']['showtitle'] folder = self._make_dir(os.path.join(self.stream_folder, item['tvshow_stream']), name) if item['info'].has_key('season') and item['info'].has_key('episode') and item['info']['season'] != '-1': subfolder = self._make_dir(folder, 'Season %s' % item['info']['season']) filename = '%s.S%sE%s.strm' % (re.sub(' ', '.', name), item['info']['season'], item['info']['episode']) self._create_file(subfolder, filename, str(item.get('path', '')) ) elif item['info'].has_key('airdate'): from datetime import datetime d = datetime.strptime(item['info']['airdate'], '%d.%m.%Y') airdate = d.strftime('%m.%d.%Y') filename = '%s.%s.strm' % (re.sub(' ', '.', name), airdate) self._create_file(folder, filename, str(item.get('path', '')) ) def log(self, m): print '%s >>> %s' % (self.addon['name'], m) def _get_json(self): #======================================================================= # self.host_ip = self.addon['obj'].getSetting('host_ip') # self.host_port = self.addon['obj'].getSetting('host_port') # self.host_username = self.addon['obj'].getSetting('username') # self.host_password = self.addon['obj'].getSetting('host_ip') # # return JSONConnection(host=self.host_ip, port=self.host_port, username=self.host_username, password=self.host_password) #======================================================================= return JSONConnection() def _parse_path(self, path): url = '' path_split = path.split('_') fids = path_split[0].split('/')[3:-1] if len(path_split) > 1: sids = '_'.join(path_split[1:]).split('/') url = '/'.join( sids[sids.index('plugin:'):] ) sids = sids[1: sids.index('plugin:')] else: sids = [] return fids, sids, url def _get_path(self, fids=None, sids=None, url=None): if fids is None: return self.addon['path'] + dict2var(self.addon['vars']) path = '%s%s%s' % (self.addon['name'], '/'.join(str(f) for f in fids), '/' if fids != [] else '') if sids: path += '_/' + '/'.join(str(s) for s in sids) + ('/' if sids != [] else '') if url: path += url return path def _get_url(self, path): if 'plugin:' not in path[1:]: return path path_split = path.split('/') return '/'.join( path_split[path_split[1:].index('plugin:')+1:] ) def _make_dir(self, path, dir): subpath = os.path.join(path, dir) try: if not xbmcvfs.exists(subpath): xbmcvfs.mkdirs(subpath) except: if not os.path.exists(subpath): os.makedirs(subpath) return subpath def _create_file(self, path, filename, content): filepath = os.path.join(path, filename) if os.path.isfile(filepath): os.utime(filepath, None) else: f = xbmcvfs.File(filepath, 'w') f.write(content) f.close() def _set_prop(self, item, id, value): ids = id.split('/') for l in ids[:-1]: item = item[l] item[ids[-1]] = value def _get_prop(self, item, id): try: for l in id.split('/'): item = item[l] return item except: return None def _mod_url(self, url): for pattern, repl in self.url_replace: if re.match(pattern, url): url = repl + re.sub(pattern, '', url) return xbmc.translatePath(url) def _xmlitems(self, tag, fids=[], sids=[]): return self.root.findall(self._xpath(tag, fids, sids)) def _xmlitem(self, tag, fids=[], sids=[]): return self.root.find(self._xpath(tag, fids, sids)) def _xpath(self, tag, fids=[], sids=[]): xpath = '.' for fid in fids: xpath += '/folder[%s]' % str(fid) if sids: xpath += '/source[%s]' % str(sids[0]) for sid in sids[1:]: xpath += '/subfolder[%s]' % str(sid) if tag != '': xpath += '/' + tag return xpath def _match_target(self, matchers, target, attr=None, values=[]): res = self._match_targets(matchers, target, attr, values) if len(res) == 0: return None, None return res[0] def _match_targets(self, matchers, target, attr=None, values=[]): res = [] for i, matcher in enumerate(matchers): if self._match_pattern(matcher, target): if attr is None \ or (matcher.attrib.has_key(attr) and values is None)\ or (values is not None and matcher.attrib.get(attr, None) in values): res.append((i+1, matcher)) return res def _match_pattern(self, matcher, target): s = self._get_prop(target, matcher.get('target', 'label')).encode('utf-8', 'ignore') if not matcher.attrib.has_key('pattern') or s is None: return False return (re.search(matcher.attrib['pattern'], s) is None) == matcher.attrib.has_key('exclude') def _show_notification(self, header='Drawers', text='', time=5): text = uni(text) image = self.addon['obj'].getAddonInfo('icon') xbmc.executebuiltin('Notification(%s, %s, %d, "%s")' % (header, text, time, image))