Internet radio browser GUI for music/video streams from various directory services.

โŒˆโŒ‹ โŽ‡ branch:  streamtuner2


Check-in [24a5fe69a1]

Overview
Comment:Add workaround for ArgumentParser, which tries to map config: descriptors onto AP.add_argument(*yikes) params.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 24a5fe69a17765e7fd94431b90884c19b643fbbd
User & Date: mario on 2015-04-06 18:55:12
Original Comment: Add workaround for ArgumentParser, which tries to map config: descriptors onto AP.add_argument() params. (yikes)
Other Links: manifest | tags
Context
2015-04-07
05:51
Move argv initialization to conf.apply_args(). Document config: format for argparse conversion. Enable file=sys.stderr for __print__/debug messages. check-in: 1eea3140f8 user: mario tags: trunk
2015-04-06
18:55
Add workaround for ArgumentParser, which tries to map config: descriptors onto AP.add_argument(*yikes) params. check-in: 24a5fe69a1 user: mario tags: trunk
18:53
Add UserAgentSwitcher plugin. (Just for experimenting really, not required.) check-in: e8c162f72a user: mario tags: trunk
Changes

Modified Makefile from [0d018ef979] to [d3f05bad3b].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Requires 
# ยท http://fossil.include-once.org/versionnum/
# ยท http://fossil.include-once.org/xpm/

SHELL   := /bin/bash #(for brace expansion)
NAME    := streamtuner2
VERSION := $(shell version get:plugin st2.py || echo 2.1dev)
DEST    := /usr/share/streamtuner2
INST    := install -m 644
PACK    := xpm
DEPS    := -n $(NAME) -d python-pyquery -d python-gtk2 -d python-requests -d python-keybinder
OPTS    := -s src -u man,fixperms -f --prefix=$(DEST) --deb-compression xz --rpm-compression xz --exe-autoextract
.PHONY:  bin
all:  gtk3 #(most used)
pack: all ver docs xpm src
gtk3: gtk3.xml.gz
zip:  pyz











|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Requires 
# ยท http://fossil.include-once.org/versionnum/
# ยท http://fossil.include-once.org/xpm/

SHELL   := /bin/bash #(for brace expansion)
NAME    := streamtuner2
VERSION := $(shell version get:plugin st2.py || echo 2.1dev)
DEST    := /usr/share/streamtuner2
INST    := install -m 644
PACK    := xpm
DEPS    := -n $(NAME) -d python -d python-pyquery -d python-gtk2 -d python-requests -d python-keybinder
OPTS    := -s src -u man,fixperms -f --prefix=$(DEST) --deb-compression xz --rpm-compression xz --exe-autoextract
.PHONY:  bin
all:  gtk3 #(most used)
pack: all ver docs xpm src
gtk3: gtk3.xml.gz
zip:  pyz

Modified cli.py from [b00bb5a675] to [436f001346].

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

56



57
58
59
60
61
62
63
64
65
66
    # channel plugins
    channel_modules = ["shoutcast", "xiph", "internet_radio", "jamendo", "myoggradio", "live365"]
    current_channel = "cli"
    plugins = {} # only populated sparsely by .stream()
    
    
    # start
    def __init__(self):

        # fake init    
        action.action.main = empty_parent()
        action.action.main.current_channel = self.current_channel

        # check if enough arguments, else  help
        if len(sys.argv)<3:
            a = self.help
        # first cmdline arg == action
        else:
            command = sys.argv[1]

            a = self.__getattribute__(command)




        # run
        result = a(*sys.argv[2:])
        if result:
            self.json(result)
        
        
    # show help
    def help(self, *args): 
        print("""







|






|



|
>
|
>
>
>


|







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    # channel plugins
    channel_modules = ["shoutcast", "xiph", "internet_radio", "jamendo", "myoggradio", "live365"]
    current_channel = "cli"
    plugins = {} # only populated sparsely by .stream()
    
    
    # start
    def __init__(self, actions):

        # fake init    
        action.action.main = empty_parent()
        action.action.main.current_channel = self.current_channel

        # check if enough arguments, else  help
        if not actions:
            a = self.help
        # first cmdline arg == action
        else:
            command = actions[0]
            if command in self.__dict__:
                cmd = self.__getattribute__(command)
            else:
                print "No such command:", command
                return

        # run
        result = cmd(*actions[1:])
        if result:
            self.json(result)
        
        
    # show help
    def help(self, *args): 
        print("""

Modified config.py from [2154593dfb] to [af23ce772a].

1
2
3
4
5
6
7





8
9
10
11
12
13
14
#
# encoding: UTF-8
# api: streamtuner2
# type: class
# title: global config object
# description: reads ~/.config/streamtuner/*.json files
# config: {type:var, name:z, description:v}





#
# In the main application or module files which need access
# to a global conf.* object, just import this module as follows:
#
#   from config import *
#
# Here conf is already an instantiation of the underlying






|
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#
# encoding: UTF-8
# api: streamtuner2
# type: class
# title: global config object
# description: reads ~/.config/streamtuner/*.json files
# config:
#    { arg: -d,     type: str,      name: plugin[],  description: omit plugin from initialization  }
#    { arg: --gtk3, type: boolean,  name: gtk3,      description: use gtk3 interface }
#    { arg: -D,     type: boolean,  name: debug,     description: enable debug messages on console }
#    { arg: action, type: str*,     name: action[],  description: commandline actions }
#    { arg: -x,     type: boolean,  name: exit,      description: terminate right away }
#
# In the main application or module files which need access
# to a global conf.* object, just import this module as follows:
#
#   from config import *
#
# Here conf is already an instantiation of the underlying
27
28
29
30
31
32
33

34
35
36
37
38
39
40
import platform
import re
from compat2and3 import gzip_decode, find_executable
import zlib
import zipfile
import inspect
import pkgutil


# export symbols
__all__ = ["conf", "__print__", "dbg", "plugin_meta", "module_list", "get_data", "find_executable"]


#-- create a stub instance of config object
conf = object()







>







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import platform
import re
from compat2and3 import gzip_decode, find_executable
import zlib
import zipfile
import inspect
import pkgutil
import argparse

# export symbols
__all__ = ["conf", "__print__", "dbg", "plugin_meta", "module_list", "get_data", "find_executable"]


#-- create a stub instance of config object
conf = object()
48
49
50
51
52
53
54

55
56
57
58
59
60
61
# Global configuration store
#
# Autointializes itself on startup, makes conf.vars available.
# Also provides .load() and .save() for JSON data/cache files.
#
class ConfigDict(dict):



    # start
    def __init__(self):
    
        # object==dict means conf.var is conf["var"]
        self.__dict__ = self  # let's pray this won't leak memory due to recursion issues








>







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# Global configuration store
#
# Autointializes itself on startup, makes conf.vars available.
# Also provides .load() and .save() for JSON data/cache files.
#
class ConfigDict(dict):

    args = {}

    # start
    def __init__(self):
    
        # object==dict means conf.var is conf["var"]
        self.__dict__ = self  # let's pray this won't leak memory due to recursion issues

73
74
75
76
77
78
79



80
81
82
83
84
85
86
                del last["share"]
            self.update(last)
            self.migrate()
        # store defaults in file
        else:
            self.save("settings")
            self.firstrun = 1





    # some defaults
    def defaults(self):
        self.play = {
           "audio/mpeg": self.find_player(),
           "audio/ogg": self.find_player(),







>
>
>







80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
                del last["share"]
            self.update(last)
            self.migrate()
        # store defaults in file
        else:
            self.save("settings")
            self.firstrun = 1
        
        # add argv
        self.args = self.init_args(argparse.ArgumentParser())


    # some defaults
    def defaults(self):
        self.play = {
           "audio/mpeg": self.find_player(),
           "audio/ogg": self.find_player(),
245
246
247
248
249
250
251
252







































253
254
255
256
257
258
259
                except:
                     netrc = parser(self.xdg() + "/netrc").hosts
            except:
                pass
        for server in varhosts:
            if server in netrc:
                return netrc[server]
        









































# Retrieve content from install path or pyzip archive (alias for pkgutil.get_data)
#
def get_data(fn, decode=False, gz=False, file_base="config"):
    try:
        bin = pkgutil.get_data(file_base, fn)







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
                except:
                     netrc = parser(self.xdg() + "/netrc").hosts
            except:
                pass
        for server in varhosts:
            if server in netrc:
                return netrc[server]

    # Use config: definitions for argv extraction
    def init_args(self, ap):
        for opt in plugin_meta(frame=0).get("config"):
            kwargs = self.argparse_map(opt)
            #print kwargs
            if kwargs:
                args = kwargs["args"]
                del kwargs["args"]
                ap.add_argument(*args, **kwargs)
        return ap.parse_args()

    # Transform config: description into quirky ArgumentParser list
    def argparse_map(self, opt):
        if not ("arg" in opt and opt["name"] and opt["type"]):
            return {}

        # Extract --flag names
        args = opt["arg"].split() + re.findall("-+\w+", opt["name"])

        # Prepare mapping options
        typing = re.findall("bool|str|\[\]|store|append|const", opt["type"])
        naming = re.findall("\[\]", opt["name"])
        name   = re.findall("(?<!-)\\b\\w+", opt["name"])
        nargs  = re.findall("\\b\d+\\b|\?|\*", opt["type"]) or [None]

        # Populate partially - ArgumentParser is highly fragile with combinations of named params
        kwargs = {
            "args": args,
            "dest": name[0] if not name[0] in args else None,
            "action": "append" if "[]" in (naming+typing) else ("store_true" if "bool" in typing else "store"),
            "nargs": nargs[0],
            "default": opt["value"],
           # "type":  int if "int" in typing else bool if "bool" in typing else str,
            "choices": opt["select"].split("|") if "select" in opt else None,
           # "required": "required" in opt,
            "help": opt["description"] or "",
        }
        return dict((k,w) for k,w in kwargs.items() if w is not None)



# Retrieve content from install path or pyzip archive (alias for pkgutil.get_data)
#
def get_data(fn, decode=False, gz=False, file_base="config"):
    try:
        bin = pkgutil.get_data(file_base, fn)

Modified st2.py from [a62db5a28b] to [2915a6cb3f].

487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531




# startup procedure
def main():

    # process a few command line flags (needs a rewrite)
    argv = sys.argv[1:]
    while len(argv) and argv[0] in ("-d", "-D"):
        opt = argv.pop(0)
        if opt == "-D":
            conf.debug = 1
        elif opt == "-d":
            conf.plugins[argv.pop(0)] = 0
            sys.argv.pop(1)
        sys.argv.pop(1)

    # graphical
    if len(sys.argv) < 2 or "--gtk3" in sys.argv:

        # prepare for threading in Gtk+ callbacks
        gobject.threads_init()

        # prepare main window
        main = StreamTunerTwo()

        # first invocation
        if (conf.get("firstrun")):
            main.configwin.open(None)
            del conf.firstrun

        # run
        gtk.main()
        __print__(dbg.PROC, r" gtk_main_quit ")
        
    # invoke command-line interface
    else:
        import cli
        cli.StreamTunerCLI()

# run
if __name__ == "__main__":
    main()








|
|
|
|
|
|
<
<
|
|


|



















|





487
488
489
490
491
492
493
494
495
496
497
498
499


500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529




# startup procedure
def main():

    # process a few command line flags
    if conf.args.plugin:
        for p_id in conf.args.plugin:
            conf.plugins[p_id] = 0
    if conf.args.debug
        conf.debug = conf.args.debug


    if conf.args.exit:
        return

    # graphical
    if not len(conf.args.action[0]):

        # prepare for threading in Gtk+ callbacks
        gobject.threads_init()

        # prepare main window
        main = StreamTunerTwo()

        # first invocation
        if (conf.get("firstrun")):
            main.configwin.open(None)
            del conf.firstrun

        # run
        gtk.main()
        __print__(dbg.PROC, r" gtk_main_quit ")
        
    # invoke command-line interface
    else:
        import cli
        cli.StreamTunerCLI(conf.args.action[0])

# run
if __name__ == "__main__":
    main()

Modified uikit.py from [c920bbe1ed] to [594d66e076].

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import inspect
from compat2and3 import unicode, xrange, PY3, gzip_decode


# gtk version (2=gtk2, 3=gtk3, 7=tk;)
ver = 2
# if running on Python3 or with commandline flag
if PY3 or "--gtk3" in sys.argv:
    ver = 3
# load gtk modules
if ver==3:
    from gi import pygtkcompat as pygtk
    pygtk.enable() 
    pygtk.enable_gtk(version='3.0')
    from gi.repository import Gtk as gtk







|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import inspect
from compat2and3 import unicode, xrange, PY3, gzip_decode


# gtk version (2=gtk2, 3=gtk3, 7=tk;)
ver = 2
# if running on Python3 or with commandline flag
if PY3 or conf.args.gtk3:
    ver = 3
# load gtk modules
if ver==3:
    from gi import pygtkcompat as pygtk
    pygtk.enable() 
    pygtk.enable_gtk(version='3.0')
    from gi.repository import Gtk as gtk