diff --git a/plugins/favorites/manifest.json b/plugins/favorites/manifest.json index 98fbb31..b6f006b 100644 --- a/plugins/favorites/manifest.json +++ b/plugins/favorites/manifest.json @@ -1,11 +1,11 @@ { "manifest": { - "name": "Favorites Plugin", + "name": "Favorites", "author": "ITDominator", "version": "0.0.1", "support": "", "requests": { - "ui_target": "plugin_control_list", + "ui_target": "main_menu_bttn_box_bar", "pass_fm_events": "true", "pass_ui_objects": ["path_entry"], "bind_keys": [] diff --git a/plugins/favorites/plugin.py b/plugins/favorites/plugin.py index c2d8f18..ac061ca 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -26,7 +26,7 @@ def daemon_threaded(fn): class Plugin: def __init__(self): - self.name = "Favorites Plugin" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus + self.name = "Favorites" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus # where self.name should not be needed for message comms self.path = os.path.dirname(os.path.realpath(__file__)) self._GLADE_FILE = f"{self.path}/favorites.glade" diff --git a/plugins/movie_tv_info/__init__.py b/plugins/movie_tv_info/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/movie_tv_info/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/movie_tv_info/__main__.py b/plugins/movie_tv_info/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/movie_tv_info/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/movie_tv_info/manifest.json b/plugins/movie_tv_info/manifest.json new file mode 100644 index 0000000..9290b70 --- /dev/null +++ b/plugins/movie_tv_info/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest": { + "name": "Movie/TV Info", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": { + "ui_target": "context_menu", + "pass_fm_events": "true" + } + } +} diff --git a/plugins/movie_tv_info/movie_tv_info.glade b/plugins/movie_tv_info/movie_tv_info.glade new file mode 100644 index 0000000..d32ab8c --- /dev/null +++ b/plugins/movie_tv_info/movie_tv_info.glade @@ -0,0 +1,201 @@ + + + + + + 1 + 100 + 65 + 1 + 10 + + + + False + 6 + Movie / TV Info + True + center-on-parent + 420 + True + dialog + True + True + center + + + True + False + 12 + + + True + False + end + + + gtk-close + True + True + True + False + True + + + True + True + 0 + + + + + False + False + end + 0 + + + + + True + False + vertical + + + 320 + True + False + 5 + 5 + 5 + gtk-missing-image + 6 + + + True + True + 0 + + + + + True + False + vertical + + + 120 + True + True + 5 + 5 + 10 + 10 + word + False + textbuffer + True + True + + + True + True + 0 + + + + + False + True + 1 + + + + + True + False + 4 + 2 + 2 + 12 + 6 + + + True + False + <b>File _Name:</b> + True + True + file_name + 0 + + + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + + + + + + True + False + <b>_Location:</b> + True + True + file_location + 0 + + + 1 + 2 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + False + True + 2 + + + + + True + True + 1 + + + + + + cancel_button + + + diff --git a/plugins/movie_tv_info/plugin.py b/plugins/movie_tv_info/plugin.py new file mode 100644 index 0000000..e9d1114 --- /dev/null +++ b/plugins/movie_tv_info/plugin.py @@ -0,0 +1,192 @@ +# Python imports +import os, threading, subprocess, time, inspect, requests, shutil + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('GdkPixbuf', '2.0') +from gi.repository import Gtk, GLib, GdkPixbuf + +# Application imports +from tmdbscraper import scraper + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class Plugin: + def __init__(self): + self.path = os.path.dirname(os.path.realpath(__file__)) + self.name = "Movie/TV Info" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus + # where self.name should not be needed for message comms + self._GLADE_FILE = f"{self.path}/movie_tv_info.glade" + + self._builder = None + self._dialog = None + self._thumbnail_preview_img = None + self._tmdb = scraper.get_tmdb_scraper() + self._overview = None + self._file_name = None + self._file_location = None + self._state = None + + self._event_system = None + self._event_sleep_time = .5 + self._event_message = None + + + def get_ui_element(self): + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) + + classes = [self] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + print(repr(e)) + + self._builder.connect_signals(handlers) + + self._thumbnailer_dialog = self._builder.get_object("info_dialog") + self._overview = self._builder.get_object("textbuffer") + self._file_name = self._builder.get_object("file_name") + self._file_location = self._builder.get_object("file_location") + self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img") + self._file_hash = self._builder.get_object("file_hash") + + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_info_page) + return button + + def set_fm_event_system(self, fm_event_system): + self._event_system = fm_event_system + + def run(self): + self._module_event_observer() + + + @threaded + def _show_info_page(self, widget=None, eve=None): + self._event_system.push_gui_event([self.name, "get_current_state", ()]) + self.wait_for_fm_message() + + state = self._event_message + self._event_message = None + + GLib.idle_add(self._process_changes, (state)) + + def _process_changes(self, state): + self._state = None + + if len(state.selected_files) == 1: + self._state = state + self._set_ui_data() + response = self._thumbnailer_dialog.run() + if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: + self._thumbnailer_dialog.hide() + + def _set_ui_data(self): + title, path, video_data = self.get_video_data() + keys = video_data.keys() if video_data else None + + overview_text = video_data["overview"] if video_data else f"...NO {self.name.upper()} DATA..." + + self.set_text_data(title, path, overview_text) + self.set_thumbnail(video_data) if video_data else ... + + print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ... + + def get_video_data(self): + uri = self._state.selected_files[0] + path = self._state.tab.get_current_directory() + parts = uri.split("/") + _title = parts[ len(parts) - 1 ] + + try: + title = _title.split("(")[0].strip() + startIndex = _title.index('(') + 1 + endIndex = _title.index(')') + date = title[startIndex:endIndex] + except Exception as e: + title = _title + date = None + + try: + video_data = self._tmdb.search(title, date)[0] + except Exception as e: + video_data = None + + return title, path, video_data + + + def set_text_data(self, title, path, overview_text): + self._file_name.set_text(title) + self._file_location.set_text(path) + self._overview.set_text(overview_text) + + @threaded + def set_thumbnail(self, video_data): + background_url = video_data["backdrop_path"] + # background_url = video_data["poster_path"] + background_pth = "/tmp/sfm_mvtv_info.jpg" + + try: + os.remove(background_pth) + except Exception as e: + ... + + r = requests.get(background_url, stream = True) + + if r.status_code == 200: + r.raw.decode_content = True + with open(background_pth,'wb') as f: + shutil.copyfileobj(r.raw, f) + + print('Cover Background Image sucessfully retreived...') + preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(background_pth) + self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) + else: + print('Cover Background Image Couldn\'t be retreived...') + + + + def wait_for_fm_message(self): + while not self._event_message: + pass + + @daemon_threaded + def _module_event_observer(self): + while True: + time.sleep(self._event_sleep_time) + event = self._event_system.read_module_event() + if event: + try: + if event[0] == self.name: + target_id, method_target, data = self._event_system.consume_module_event() + + if not method_target: + self._event_message = data + else: + method = getattr(self.__class__, f"{method_target}") + if data: + data = method(*(self, *data)) + else: + method(*(self,)) + except Exception as e: + print(repr(e)) diff --git a/plugins/movie_tv_info/tmdbscraper/scraper.py b/plugins/movie_tv_info/tmdbscraper/scraper.py new file mode 100644 index 0000000..db25e2b --- /dev/null +++ b/plugins/movie_tv_info/tmdbscraper/scraper.py @@ -0,0 +1,22 @@ +import json +import sys + +from .lib.tmdbscraper.tmdb import TMDBMovieScraper +from .lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork +from .lib.tmdbscraper.imdbratings import get_details as get_imdb_details +from .lib.tmdbscraper.traktratings import get_trakt_ratinginfo +from .scraper_datahelper import combine_scraped_details_info_and_ratings, \ + combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params +from .scraper_config import configure_scraped_details, PathSpecificSettings, \ + configure_tmdb_artwork, is_fanarttv_configured + + + + + + +def get_tmdb_scraper(): + language = 'en-US' + certcountry = 'us' + ADDON_SETTINGS = None + return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry) diff --git a/plugins/movie_tv_info/tmdbscraper/scraper_config.py b/plugins/movie_tv_info/tmdbscraper/scraper_config.py new file mode 100644 index 0000000..76a042b --- /dev/null +++ b/plugins/movie_tv_info/tmdbscraper/scraper_config.py @@ -0,0 +1,111 @@ +def configure_scraped_details(details, settings): + details = _configure_rating_prefix(details, settings) + details = _configure_keeporiginaltitle(details, settings) + details = _configure_trailer(details, settings) + details = _configure_multiple_studios(details, settings) + details = _configure_default_rating(details, settings) + details = _configure_tags(details, settings) + return details + +def configure_tmdb_artwork(details, settings): + if 'available_art' not in details: + return details + + art = details['available_art'] + fanart_enabled = settings.getSettingBool('fanart') + if not fanart_enabled: + if 'fanart' in art: + del art['fanart'] + if 'set.fanart' in art: + del art['set.fanart'] + if not settings.getSettingBool('landscape'): + if 'landscape' in art: + if fanart_enabled: + art['fanart'] = art.get('fanart', []) + art['landscape'] + del art['landscape'] + if 'set.landscape' in art: + if fanart_enabled: + art['set.fanart'] = art.get('set.fanart', []) + art['set.landscape'] + del art['set.landscape'] + + return details + +def is_fanarttv_configured(settings): + return settings.getSettingBool('enable_fanarttv_artwork') + +def _configure_rating_prefix(details, settings): + if details['info'].get('mpaa'): + details['info']['mpaa'] = settings.getSettingString('certprefix') + details['info']['mpaa'] + return details + +def _configure_keeporiginaltitle(details, settings): + if settings.getSettingBool('keeporiginaltitle'): + details['info']['title'] = details['info']['originaltitle'] + return details + +def _configure_trailer(details, settings): + if details['info'].get('trailer') and not settings.getSettingBool('trailer'): + del details['info']['trailer'] + return details + +def _configure_multiple_studios(details, settings): + if not settings.getSettingBool('multiple_studios'): + details['info']['studio'] = details['info']['studio'][:1] + return details + +def _configure_default_rating(details, settings): + imdb_default = bool(details['ratings'].get('imdb')) and settings.getSettingString('RatingS') == 'IMDb' + trakt_default = bool(details['ratings'].get('trakt')) and settings.getSettingString('RatingS') == 'Trakt' + default_rating = 'themoviedb' + if imdb_default: + default_rating = 'imdb' + elif trakt_default: + default_rating = 'trakt' + if default_rating not in details['ratings']: + default_rating = list(details['ratings'].keys())[0] if details['ratings'] else None + for rating_type in details['ratings'].keys(): + details['ratings'][rating_type]['default'] = rating_type == default_rating + return details + +def _configure_tags(details, settings): + if not settings.getSettingBool('add_tags'): + del details['info']['tag'] + return details + +# pylint: disable=invalid-name +try: + basestring +except NameError: # py2 / py3 + basestring = str + +#pylint: disable=redefined-builtin +class PathSpecificSettings(object): + # read-only shim for typed `xbmcaddon.Addon().getSetting*` methods + def __init__(self, settings_dict, log_fn): + self.data = settings_dict + self.log = log_fn + + def getSettingBool(self, id): + return self._inner_get_setting(id, bool, False) + + def getSettingInt(self, id): + return self._inner_get_setting(id, int, 0) + + def getSettingNumber(self, id): + return self._inner_get_setting(id, float, 0.0) + + def getSettingString(self, id): + return self._inner_get_setting(id, basestring, '') + + def _inner_get_setting(self, setting_id, setting_type, default): + value = self.data.get(setting_id) + if isinstance(value, setting_type): + return value + self._log_bad_value(value, setting_id) + return default + + def _log_bad_value(self, value, setting_id): + if value is None: + self.log("requested setting ({0}) was not found.".format(setting_id)) + else: + self.log('failed to load value "{0}" for setting {1}'.format(value, setting_id)) diff --git a/plugins/movie_tv_info/tmdbscraper/scraper_datahelper.py b/plugins/movie_tv_info/tmdbscraper/scraper_datahelper.py new file mode 100644 index 0000000..23504e0 --- /dev/null +++ b/plugins/movie_tv_info/tmdbscraper/scraper_datahelper.py @@ -0,0 +1,54 @@ +import re +try: + from urlparse import parse_qsl +except ImportError: # py2 / py3 + from urllib.parse import parse_qsl + +# get addon params from the plugin path querystring +def get_params(argv): + result = {'handle': int(argv[0])} + if len(argv) < 2 or not argv[1]: + return result + + result.update(parse_qsl(argv[1].lstrip('?'))) + return result + +def combine_scraped_details_info_and_ratings(original_details, additional_details): + def update_or_set(details, key, value): + if key in details: + details[key].update(value) + else: + details[key] = value + + if additional_details: + if additional_details.get('info'): + update_or_set(original_details, 'info', additional_details['info']) + if additional_details.get('ratings'): + update_or_set(original_details, 'ratings', additional_details['ratings']) + return original_details + +def combine_scraped_details_available_artwork(original_details, additional_details): + if additional_details and additional_details.get('available_art'): + available_art = additional_details['available_art'] + if not original_details.get('available_art'): + original_details['available_art'] = available_art + else: + for arttype, artlist in available_art.items(): + original_details['available_art'][arttype] = \ + artlist + original_details['available_art'].get(arttype, []) + + return original_details + +def find_uniqueids_in_text(input_text): + result = {} + res = re.search(r'(themoviedb.org/movie/)([0-9]+)', input_text) + if (res): + result['tmdb'] = res.group(2) + res = re.search(r'imdb....?/title/tt([0-9]+)', input_text) + if (res): + result['imdb'] = 'tt' + res.group(1) + else: + res = re.search(r'imdb....?/Title\?t{0,2}([0-9]+)', input_text) + if (res): + result['imdb'] = 'tt' + res.group(1) + return result diff --git a/plugins/movie_tv_info/tmdbscraper/scraper_xbmc.py b/plugins/movie_tv_info/tmdbscraper/scraper_xbmc.py new file mode 100644 index 0000000..c976407 --- /dev/null +++ b/plugins/movie_tv_info/tmdbscraper/scraper_xbmc.py @@ -0,0 +1,175 @@ +import json +import sys +import xbmc +import xbmcaddon +import xbmcgui +import xbmcplugin + +from lib.tmdbscraper.tmdb import TMDBMovieScraper +from lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork +from lib.tmdbscraper.imdbratings import get_details as get_imdb_details +from lib.tmdbscraper.traktratings import get_trakt_ratinginfo +from scraper_datahelper import combine_scraped_details_info_and_ratings, \ + combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params +from scraper_config import configure_scraped_details, PathSpecificSettings, \ + configure_tmdb_artwork, is_fanarttv_configured + +ADDON_SETTINGS = xbmcaddon.Addon() +ID = ADDON_SETTINGS.getAddonInfo('id') + +def log(msg, level=xbmc.LOGDEBUG): + xbmc.log(msg='[{addon}]: {msg}'.format(addon=ID, msg=msg), level=level) + +def get_tmdb_scraper(settings): + language = settings.getSettingString('language') + certcountry = settings.getSettingString('tmdbcertcountry') + return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry) + +def search_for_movie(title, year, handle, settings): + log("Find movie with title '{title}' from year '{year}'".format(title=title, year=year), xbmc.LOGINFO) + title = _strip_trailing_article(title) + search_results = get_tmdb_scraper(settings).search(title, year) + if not search_results: + return + if 'error' in search_results: + header = "The Movie Database Python error searching with web service TMDB" + xbmcgui.Dialog().notification(header, search_results['error'], xbmcgui.NOTIFICATION_WARNING) + log(header + ': ' + search_results['error'], xbmc.LOGWARNING) + return + + for movie in search_results: + listitem = _searchresult_to_listitem(movie) + uniqueids = {'tmdb': str(movie['id'])} + xbmcplugin.addDirectoryItem(handle=handle, url=build_lookup_string(uniqueids), + listitem=listitem, isFolder=True) + +_articles = [prefix + article for prefix in (', ', ' ') for article in ("the", "a", "an")] +def _strip_trailing_article(title): + title = title.lower() + for article in _articles: + if title.endswith(article): + return title[:-len(article)] + return title + +def _searchresult_to_listitem(movie): + movie_info = {'title': movie['title']} + movie_label = movie['title'] + + movie_year = movie['release_date'].split('-')[0] if movie.get('release_date') else None + if movie_year: + movie_label += ' ({})'.format(movie_year) + movie_info['year'] = movie_year + + listitem = xbmcgui.ListItem(movie_label, offscreen=True) + + listitem.setInfo('video', movie_info) + if movie['poster_path']: + listitem.setArt({'thumb': movie['poster_path']}) + + return listitem + +# Low limit because a big list of artwork can cause trouble in some cases +# (a column can be too large for the MySQL integration), +# and how useful is a big list anyway? Not exactly rhetorical, this is an experiment. +IMAGE_LIMIT = 10 + +def add_artworks(listitem, artworks): + for arttype, artlist in artworks.items(): + if arttype == 'fanart': + continue + for image in artlist[:IMAGE_LIMIT]: + listitem.addAvailableArtwork(image['url'], arttype) + + fanart_to_set = [{'image': image['url'], 'preview': image['preview']} + for image in artworks['fanart'][:IMAGE_LIMIT]] + listitem.setAvailableFanart(fanart_to_set) + +def get_details(input_uniqueids, handle, settings): + if not input_uniqueids: + return False + details = get_tmdb_scraper(settings).get_details(input_uniqueids) + if not details: + return False + if 'error' in details: + header = "The Movie Database Python error with web service TMDB" + xbmcgui.Dialog().notification(header, details['error'], xbmcgui.NOTIFICATION_WARNING) + log(header + ': ' + details['error'], xbmc.LOGWARNING) + return False + + details = configure_tmdb_artwork(details, settings) + + if settings.getSettingString('RatingS') == 'IMDb' or settings.getSettingBool('imdbanyway'): + imdbinfo = get_imdb_details(details['uniqueids']) + if 'error' in imdbinfo: + header = "The Movie Database Python error with website IMDB" + log(header + ': ' + imdbinfo['error'], xbmc.LOGWARNING) + else: + details = combine_scraped_details_info_and_ratings(details, imdbinfo) + + if settings.getSettingString('RatingS') == 'Trakt' or settings.getSettingBool('traktanyway'): + traktinfo = get_trakt_ratinginfo(details['uniqueids']) + details = combine_scraped_details_info_and_ratings(details, traktinfo) + + if is_fanarttv_configured(settings): + fanarttv_info = get_fanarttv_artwork(details['uniqueids'], + settings.getSettingString('fanarttv_clientkey'), + settings.getSettingString('fanarttv_language'), + details['_info']['set_tmdbid']) + details = combine_scraped_details_available_artwork(details, fanarttv_info) + + details = configure_scraped_details(details, settings) + + listitem = xbmcgui.ListItem(details['info']['title'], offscreen=True) + listitem.setInfo('video', details['info']) + listitem.setCast(details['cast']) + listitem.setUniqueIDs(details['uniqueids'], 'tmdb') + add_artworks(listitem, details['available_art']) + + for rating_type, value in details['ratings'].items(): + if 'votes' in value: + listitem.setRating(rating_type, value['rating'], value['votes'], value['default']) + else: + listitem.setRating(rating_type, value['rating'], defaultt=value['default']) + + xbmcplugin.setResolvedUrl(handle=handle, succeeded=True, listitem=listitem) + return True + +def find_uniqueids_in_nfo(nfo, handle): + uniqueids = find_uniqueids_in_text(nfo) + if uniqueids: + listitem = xbmcgui.ListItem(offscreen=True) + xbmcplugin.addDirectoryItem( + handle=handle, url=build_lookup_string(uniqueids), listitem=listitem, isFolder=True) + +def build_lookup_string(uniqueids): + return json.dumps(uniqueids) + +def parse_lookup_string(uniqueids): + try: + return json.loads(uniqueids) + except ValueError: + log("Can't parse this lookup string, is it from another add-on?\n" + uniqueids, xbmc.LOGWARNING) + return None + +def run(): + params = get_params(sys.argv[1:]) + enddir = True + if 'action' in params: + settings = ADDON_SETTINGS if not params.get('pathSettings') else \ + PathSpecificSettings(json.loads(params['pathSettings']), lambda msg: log(msg, xbmc.LOGWARNING)) + action = params["action"] + if action == 'find' and 'title' in params: + search_for_movie(params["title"], params.get("year"), params['handle'], settings) + elif action == 'getdetails' and 'url' in params: + enddir = not get_details(parse_lookup_string(params["url"]), params['handle'], settings) + elif action == 'NfoUrl' and 'nfo' in params: + find_uniqueids_in_nfo(params["nfo"], params['handle']) + else: + log("unhandled action: " + action, xbmc.LOGWARNING) + else: + log("No action in 'params' to act on", xbmc.LOGWARNING) + if enddir: + xbmcplugin.endOfDirectory(params['handle']) + +if __name__ == '__main__': + run() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py index 4f96b2d..0995a8f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py @@ -50,7 +50,8 @@ class ManifestProcessor: if "ui_target" in keys: if requests["ui_target"] in [ - "none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list", + "none", "other", "main_Window", "main_menu_bar", + "main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list", "context_menu", "window_1", "window_2", "window_3", "window_4" ]: if requests["ui_target"] == "other": diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index d793f72..d9fd59e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -65,7 +65,9 @@ class Plugins: def load_plugin_module(self, path, folder, target): os.chdir(path) - sys.path.insert(0, path) + sys.path.insert(0, path) # NOTE: I think I'm not using this correctly... + # The folder and target aren't working to create parent package references, so using as stopgap. + # The above is probably polutling import logic and will cause unforseen import issues. spec = importlib.util.spec_from_file_location(folder, target) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index c7d8965..2bf3bd8 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1008,7 +1008,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - + True False 5 @@ -2364,283 +2364,362 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - + True False vertical - + True - False - vertical + True + 5 + 5 + True + True - - gtk-open - open + True - True - True - Open... - True - + False + vertical + + + gtk-open + open + True + True + True + Open... + True + + + + False + True + 0 + + + + + Open With + open_with + True + True + True + open_with_img + True + + + + False + True + 1 + + + + + gtk-execute + execute + True + True + True + True + + + + False + True + 2 + + + + + Execute in Terminal + execute_in_terminal + True + True + True + exec_in_term_img + + + + False + True + 3 + + - - False - True - 0 - - - - Open With - open_with + + True - True - True - open_with_img - True - + False + False + Open + center - - False - True - 1 - - - - - gtk-execute - execute - True - True - True - True - - - - False - True - 2 - - - - - Execute in Terminal - execute_in_terminal - True - True - True - exec_in_term_img - - - - False - True - 3 - - - - - gtk-new - create - True - True - True - New File/Folder... - 20 - True - - - - False - True - 4 - - - - - Rename - rename - True - True - True - Rename... - rename_img2 - True - - - - False - True - 5 - - - - - gtk-cut - cut - True - True - True - Cut... - True - True - - - - False - True - 6 - - - - - gtk-copy - copy - True - True - True - Copy... - True - True - - - - False - True - 7 - - - - - gtk-paste - paste - True - True - True - Paste... - True - True - - - - False - True - 8 - - - - - Archive - archive - True - True - True - Archive... - archive_img - True - - - - False - True - 9 - - - - - Restore From Trash - restore_from_trash - True - True - True - Restore From Trash... - 20 - - - - False - True - 10 - - - - - Empty Trash - empty_trash - True - True - True - Empty Trash... - - - - False - True - 11 - - - - - Trash - trash - True - True - True - Move to Trash... - 20 - trash_img - True - - - - False - True - 12 - - - - - Go To Trash - go_to_trash - True - True - True - Go To Trash... - trash_img2 - True - - - - False - True - 13 - - - - - gtk-delete - delete - True - True - True - Delete... - 20 - True - True - - - - False - True - 14 - False True - 2 + 4 + + + + + True + True + 5 + 5 + True + True + + + True + False + vertical + + + gtk-new + create + True + True + True + New File/Folder... + True + + + + False + True + 0 + + + + + Rename + rename + True + True + True + Rename... + rename_img2 + True + + + + False + True + 1 + + + + + gtk-cut + cut + True + True + True + Cut... + True + True + + + + False + True + 2 + + + + + gtk-copy + copy + True + True + True + Copy... + True + True + + + + False + True + 3 + + + + + gtk-paste + paste + True + True + True + Paste... + True + True + + + + False + True + 4 + + + + + Archive + archive + True + True + True + Archive... + archive_img + True + + + + False + True + 5 + + + + + + + True + False + False + File Actions + center + end + + + + + False + True + 5 + + + + + True + True + 5 + 10 + True + + + True + False + vertical + + + Restore From Trash + restore_from_trash + True + True + True + Restore From Trash... + + + + False + True + 0 + + + + + Empty Trash + empty_trash + True + True + True + Empty Trash... + + + + False + True + 1 + + + + + Trash + trash + True + True + True + Move to Trash... + 20 + trash_img + True + + + + False + True + 2 + + + + + Go To Trash + go_to_trash + True + True + True + Go To Trash... + trash_img2 + True + + + + False + True + 3 + + + + + gtk-delete + delete + True + True + True + Delete... + 20 + True + True + + + + False + True + 4 + + + + + + + True + False + False + Trash + center + + + + + False + True + 6