Index: channels/configwin.py ================================================================== --- channels/configwin.py +++ channels/configwin.py @@ -87,24 +87,27 @@ if row[0] and row[1]: config[key][row[0]] = row[1] __print__(dbg.CONF, "config save", prefix+key, val) - # Generic Gtk callback to update ListStore when entries get edited + # Generic Gtk callback to update ListStore when entries get edited. + # (The main signal_connect() dict prepares individual lambda funcs + # for each ListStore column id.) def list_edit(self, liststore, path, column, new_text): liststore[path][column] = new_text liststore[path][3] = self.app_bin_check(new_text) - # The signal_connect() dict actually prepares individual lambda functions - # to bind the correct ListStore and column id. # return OK or CANCEL depending on availability of app def app_bin_check(self, v): m = re.search("(?![$(`])\S+", v) - if m and m.group(0) and find_executable(m.group(0)): - return gtk.STOCK_MEDIA_PLAY + if m and m.group(0): + if find_executable(m.group(0)): + return gtk.STOCK_MEDIA_PLAY + else: + return gtk.STOCK_CANCEL else: - return gtk.STOCK_CANCEL + return gtk.STOCK_NEW # iterate over channel and feature plugins def add_plugins(self): @@ -115,10 +118,12 @@ # elif name in self.features: # ls[name] = self.features[name].meta # else: # ls[name] = plugin_meta(module=name) for name,meta in sorted(ls.items(), key=lambda e: e[1]["type"]+e[1]["title"].lower(), reverse=False): + if not name in conf.plugins: + conf.plugins[name] = False self.add_plg(name, meta) # add configuration setting definitions from plugins plugin_text = "%s (%s/%s) %s\n%s" Index: config.py ================================================================== --- config.py +++ config.py @@ -99,41 +99,17 @@ } self.record = { "audio/*": self.find_player(typ="xterm") + " -e streamripper %srv", # -d /home/***USERNAME***/Musik "video/youtube": self.find_player(typ="xterm") + " -e \"youtube-dl %srv\"", } - # these presets are a temporary workaround, until `priority:` is checked before module loading + # Presets are redundant now. On first startup the `priority:` field of each plugin is checked. self.plugins = { # core plugins, cannot be disabled anyway "bookmarks": 1, "search": 1, "streamedit": 1, "configwin": 1, - # standard channels - "shoutcast": 1, - "xiph": 1, - "myoggradio": 1, - "internet_radio": 1, - "surfmusik": 1, - "jamendo": 1, - "icast": 1, - "itunes": 1, - # disabled per default - "radiobrowser": 0, - "youtube": 0, - "modarchive": 0, - "live365": 0, - "radiotray": 0, - "timer": 0, - "history": 0, - "global_key": 0, - "useragentswitcher": 0, - # obsolete / removed - "file": 0, - "punkcast": 0, - "basicch": 0, - "tv": 0, } self.tmp = os.environ.get("TEMP", "/tmp") self.max_streams = "500" self.show_bookmarks = 1 self.show_favicons = 1 @@ -157,11 +133,12 @@ if ("name" in opt) and ("value" in opt) and (opt["name"] not in vars(self)): self.__dict__[opt["name"]] = opt["value"] # plugin state if module and module not in conf.plugins: - conf.plugins[module] = meta.get("priority") in ("core", "builtin", "default", "standard") + conf.plugins[module] = meta.get("priority") in ("core", "builtin", "always", "default", "standard") + # look at system binaries for standard audio players def find_player(self, typ="audio", default="xdg-open"): players = { "audio": ["audacious %g", "audacious2", "exaile %p", "xmms2", "banshee", "amarok %g", "clementine", "qmmp", "quodlibet", "aqualung", "mp3blaster %g", "vlc --one-instance %srv", "totem"], @@ -171,10 +148,11 @@ } for bin in players[typ]: if find_executable(bin.split()[0]): return bin return default + # http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html def xdg(self, path="/streamtuner2"): home = os.environ.get("HOME", self.tmp) config = os.environ.get("XDG_CONFIG_HOME", os.environ.get("APPDATA", home+"/.config")) @@ -244,10 +222,11 @@ if type(value) == dict: self[key].update(value) else: self[key] = value # descends into sub-dicts instead of wiping them with subkeys + # update old setting names def migrate(self): # 2.1.1 if "audio/mp3" in self.play: @@ -258,10 +237,11 @@ # check for existing filename in directory list def find_in_dirs(self, dirs, file): for d in dirs: if os.path.exists(d+"/"+file): return d+"/"+file + # standard user account storage in ~/.netrc or less standard but contemporarily in ~/.config/netrc def netrc(self, varhosts=("shoutcast.com")): global netrc if not netrc: @@ -280,13 +260,13 @@ # Retrieve content from install path or pyzip archive (alias for pkgutil.get_data) # -def get_data(fn, decode=False, z=False): +def get_data(fn, decode=False, z=False, file_base="config"): try: - bin = pkgutil.get_data("config", fn) + bin = pkgutil.get_data(file_base, fn) if z: bin = zlib.decompress(bin) if decode: return bin.decode("utf-8") else: @@ -296,14 +276,14 @@ # Search through ./channels/ and get module basenames. # (Reordering channel tabs is now done by uikit.apply_state.) # -def module_list(): +def module_list(plugin_base="channels"): # Should list plugins within zips as well as local paths - ls = pkgutil.iter_modules([conf.share+"/channels", "channels"]) + ls = pkgutil.iter_modules([plugin_base, conf.share+"/"+plugin_base, conf.dir+"/plugins"]) return [name for loader,name,ispkg in ls] # Plugin meta data extraction @@ -313,16 +293,16 @@ # · fn= to read from literal files, out of a .pyzip package # · src= to extract from pre-read script code # · module= utilizes pkgutil to read # · frame= automatically extract comment header from caller # -def plugin_meta(fn=None, src=None, module=None, frame=1): +def plugin_meta(fn=None, src=None, module=None, frame=1, plugin_base="channels"): # try via pkgutil first if module: fn = module - src = pkgutil.get_data("channels", fn+".py") + src = pkgutil.get_data(plugin_base, fn+".py") # get source directly from caller elif not src and not fn: module = inspect.getmodule(sys._getframe(frame)) fn = inspect.getsourcefile(module) Index: st2.py ================================================================== --- st2.py +++ st2.py @@ -15,13 +15,12 @@ # { type: env, name: XDG_CONFIG_HOME, description: relocates user .config subdirectory } # category: sound # depends: pygtk | gi, threading, requests, pyquery, lxml # id: streamtuner2 # pack: *.py, gtk3.xml.zlib, bin, channels/__init__.py, bundle/*.py, CREDITS, help/index.page, -# streamtuner2.desktop=/usr/share/applications/, README=/usr/share/doc/streamtuner2/, -# help/streamtuner2.1=/usr/share/man/man1/, NEWS.gz=/usr/share/doc/streamtuner2/changelog.gz, -# logo.png=/usr/share/pixmaps/streamtuner2.png +# streamtuner2.desktop, README, help/streamtuner2.1=/usr/share/man/man1/, +# NEWS.gz=/usr/share/doc/streamtuner2/changelog.gz, logo.png=/usr/share/pixmaps/streamtuner2.png # architecture: all # # Streamtuner2 is a GUI for browsing internet radio directories, music # collections, and video services - grouped by genres or categories. # It runs your preferred audio player, and streamripper for recording. @@ -403,37 +402,41 @@ # load plugins from /usr/share/streamtuner2/channels/ def load_plugin_channels(self): # initialize plugin modules (pre-ordered) ls = module_list() - for module in ls: - gui_startup(4/20.0 + 13.5/20.0 * float(ls.index(module))/len(ls), "loading module "+module) + for name in ls: + gui_startup(4/20.0 + 13.5/20.0 * float(ls.index(name))/len(ls), "loading module "+name) + # load defaults on first startup + if not name in conf.plugins: + conf.add_plugin_defaults(plugin_meta(module=name), name) + # skip module if disabled - if conf.plugins.get(module, 1) == False: - __print__(dbg.STAT, "disabled plugin:", module) + if conf.plugins.get(name, 1) == False: + __print__(dbg.STAT, "disabled plugin:", name) continue # or if it's a built-in (already imported) - elif module in self.features or module in self.channels: + elif name in self.features or name in self.channels: continue # load plugin try: - plugin = __import__("channels."+module, globals(), None, [""]) + plugin = __import__("channels."+name, globals(), None, [""]) #print [name for name,c in inspect.getmembers(plugin) if inspect.isclass(c)] - plugin_class = plugin.__dict__[module] + plugin_class = plugin.__dict__[name] plugin_obj = plugin_class(parent=self) # add to .channels{} if issubclass(plugin_class, channels.GenericChannel): - self.channels[module] = plugin_obj + self.channels[name] = plugin_obj # or .features{} for other plugin types else: - self.features[module] = plugin_obj + self.features[name] = plugin_obj except Exception as e: - __print__(dbg.INIT, "load_plugin_channels: error initializing:", module, ", exception:") + __print__(dbg.INIT, "load_plugin_channels: error initializing:", name, ", exception:") traceback.print_exc() # store window/widget states (sizes, selections, etc.) def app_state(self, widget):