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

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


Check-in [9d6c4e81f8]

Overview
Comment:Catch HTTP errors for reload_categories(). Provide a descriptive .placeholder[] and .empty_stub[] stream list for channels reloading.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 9d6c4e81f8588882fb6a102cfef88c618a6333cc
User & Date: mario on 2015-04-04 01:48:31
Other Links: manifest | tags
Context
2015-04-04
01:50
Use plain module_list() for config_dialog.add_plugins() instead of traversing main.channels and main.features separately. Uses module= lookup with hardwired "channels." and ".py" retrieval. check-in: 9ed03bc901 user: mario tags: trunk
01:48
Catch HTTP errors for reload_categories(). Provide a descriptive .placeholder[] and .empty_stub[] stream list for channels reloading. check-in: 9d6c4e81f8 user: mario tags: trunk
01:46
Moved back to old `mostPolular` method. check-in: 8996e21a01 user: mario tags: trunk
Changes

Modified ahttp.py from [eff6de2cac] to [46b2bffbda].

61
62
63
64
65
66
67




68
69
70
71
72
73
74
    
    # combine headers
    headers = {}
    if ajax:
        headers["X-Requested-With"] = "XMLHttpRequest"
    if referer:
        headers["Referer"] = (referer if referer else url)




    
    # read
    if post:
        r = session.post(url, params=params, headers=headers, timeout=7.5)
    else:    
        r = session.get(url, params=params, headers=headers, timeout=9.75)








>
>
>
>







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    
    # combine headers
    headers = {}
    if ajax:
        headers["X-Requested-With"] = "XMLHttpRequest"
    if referer:
        headers["Referer"] = (referer if referer else url)

#ifdef BLD_DEBUG
#srcout    raise Exception("Simulated HTTP error")
#endif
    
    # read
    if post:
        r = session.post(url, params=params, headers=headers, timeout=7.5)
    else:    
        r = session.get(url, params=params, headers=headers, timeout=9.75)

Modified channels/__init__.py from [98bda28d4c] to [f281706365].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# encoding: UTF-8
# api: streamtuner2
# type: base
# category: ui
# title: Channel plugins
# description: Base implementation for channels and feature plugins
# version: 1.1
# license: public domain
# author: mario
# url: http://fossil.include-once.org/streamtuner2/
# pack:
#    bookmarks.py  configwin.py  global_key.py  history.py
#    icast.py  internet_radio.py  itunes.py  jamendo.py links.py  live365.py
#    modarchive.py  myoggradio.py  punkcast.py  radiotray.py  search.py
#    shoutcast.py  streamedit.py  surfmusik.py  timer.py  tunein.py  xiph.py
#    youtube.py  *.png
# config: -
# priority: core
#
#
# Just exports GenericChannel and ChannelPlugin.
# GenericChannel implements the basic GUI functions and defines
# the default channel data structure. It implements base and











|
|
|
|
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

16
17
18
19
20
21
22
# encoding: UTF-8
# api: streamtuner2
# type: base
# category: ui
# title: Channel plugins
# description: Base implementation for channels and feature plugins
# version: 1.1
# license: public domain
# author: mario
# url: http://fossil.include-once.org/streamtuner2/
# pack:
#    bookmarks.py configwin.py streamedit.py history.py search.py links.py 
#    icast.py internet_radio.py itunes.py jamendo.py live365.py global_key.py
#    modarchive.py myoggradio.py punkcast.py radiobrowser.py radiotray.py
#    shoutcast.py surfmusik.py timer.py tunein.py xiph.py youtube.py

# config: -
# priority: core
#
#
# Just exports GenericChannel and ChannelPlugin.
# GenericChannel implements the basic GUI functions and defines
# the default channel data structure. It implements base and
95
96
97
98
99
100
101




102
103
104
105
106
107
108
       [False,	0,	["favourite",	bool,	None,	{}],	],
       [False,	0,	["deleted",	bool,	None,	{}],	],
       [False,	0,	["search_col",	str,	None,	{}],	],
       [False,	0,	["search_set",	bool,	None,	{}],	],
    ]
    rowmap = []   # [state,genre,title,...] field enumeration still needed separately
    titles = {}   # for easier adapting of column titles in datamap




    
    # regex            
    rx_www_url = re.compile("""(www(\.\w+[\w-]+){2,}|(\w+[\w-]+[ ]?\.)+(com|FM|net|org|de|PL|fr|uk))""", re.I)


    # constructor
    def __init__(self, parent=None):







>
>
>
>







94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
       [False,	0,	["favourite",	bool,	None,	{}],	],
       [False,	0,	["deleted",	bool,	None,	{}],	],
       [False,	0,	["search_col",	str,	None,	{}],	],
       [False,	0,	["search_set",	bool,	None,	{}],	],
    ]
    rowmap = []   # [state,genre,title,...] field enumeration still needed separately
    titles = {}   # for easier adapting of column titles in datamap

    # for empty grouping / categories
    placeholder = [dict(genre="./.", title="Subcategory placeholder", playing="./.", url="none:", listeners=0, bitrate=0, homepage="", state="gtk-folder")]
    empty_stub = [dict(genre="./.", title="No categories found (HTTP error)", playing="Try Channel→Reload Categories later..", url="none:", listeners=0, bitrate=0, homepage="", state="gtk-stop")]
    
    # regex            
    rx_www_url = re.compile("""(www(\.\w+[\w-]+){2,}|(\w+[\w-]+[ ]?\.)+(com|FM|net|org|de|PL|fr|uk))""", re.I)


    # constructor
    def __init__(self, parent=None):
193
194
195
196
197
198
199
200
201
202
203
204
205



206
207
208
209
210
211
212
213
                row[0] = title


    # switch stream category,
    # load data,
    # update treeview content
    def load(self, category, force=False):
    
        # get data from cache or download
        if (force or not category in self.streams):
            __print__(dbg.PROC, "load", "update_streams")
            self.parent.status("Updating streams...")
            self.parent.status(-0.1)



            new_streams = self.update_streams(category)
  
            if new_streams:

                # check and modify entry;
                # assert that title and url are present
                modified = []
                for row in new_streams:







|





>
>
>
|







196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
                row[0] = title


    # switch stream category,
    # load data,
    # update treeview content
    def load(self, category, force=False):

        # get data from cache or download
        if (force or not category in self.streams):
            __print__(dbg.PROC, "load", "update_streams")
            self.parent.status("Updating streams...")
            self.parent.status(-0.1)
            if category == "empty":
                new_streams = self.empty_stub
            else:
                new_streams = self.update_streams(category)
  
            if new_streams:

                # check and modify entry;
                # assert that title and url are present
                modified = []
                for row in new_streams:
352
353
354
355
356
357
358
359
360
361

362
363
364
365
366

367



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400

401
402
403
404
405
406
407
    def reload_if_current(self, category):
        if self.current == category:
            self.reload()
    
        
    # display .current category, once notebook/channel tab is first opened
    def first_show(self):
        __print__(dbg.PROC, "first_show ", self.module, self.shown)

        if (self.shown != 55555):

        
            # if category tree is empty, initialize it
            if not self.categories:
                __print__(dbg.PROC, "first_show: reload_categories");
                #self.parent.thread(self.reload_categories)

                self.reload_categories()



                self.display_categories()
                self.current = self.categories.keys()[0]
                __print__(dbg.STAT, self.current)
                self.load(self.current)
        
            # load current category
            else:
                __print__(dbg.STAT, "first_show: load current category");
                self.load(self.current)
            
            # put selection/cursor on last position
            try:
                __print__(dbg.STAT, "first_show: select last known category treelist position")
                self.gtk_list.get_selection().select_path(self.shown)
            except:
                pass
                
            # this method will only be invoked once
            self.shown = 55555


    # update categories, save, and display                
    def reload_categories(self):
    
        # get data and save
        self.update_categories()
        if self.categories:
            conf.save("cache/categories_"+self.module, self.categories)
        if self.catmap:
            conf.save("cache/catmap_" + self.module, self.catmap);

        # display outside of this non-main thread            
        uikit.do(self.display_categories)


    # insert content into gtk category list
    def display_categories(self):
    
        # remove any existing columns
        if self.gtk_cat:
            [self.gtk_cat.remove_column(c) for c in self.gtk_cat.get_columns()]







<


>



|

>
|
>
>
>


|




|




|




















>







358
359
360
361
362
363
364

365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
    def reload_if_current(self, category):
        if self.current == category:
            self.reload()
    
        
    # display .current category, once notebook/channel tab is first opened
    def first_show(self):


        if (self.shown != 55555):
            __print__(dbg.PROC, self.module+".first_show()")
        
            # if category tree is empty, initialize it
            if not self.categories:
                __print__(dbg.PROC, self.module+"first_show: reload_categories");
                #self.parent.thread(self.reload_categories)
                try:
                    self.reload_categories()
                except:
                    __print__(dbg.ERR, "HTTP Error or something")
                    self.categories = ["empty"]
                self.display_categories()
                self.current = self.categories.keys()[0]
                __print__(dbg.STAT, "Use first category as current =", self.current)
                self.load(self.current)
        
            # load current category
            else:
                __print__(dbg.STAT, self.module+".first_show(): load current category =", self.current);
                self.load(self.current)
            
            # put selection/cursor on last position
            try:
                __print__(dbg.STAT, self.module+".first_show()", "select last known category treelist position =", self.shown)
                self.gtk_list.get_selection().select_path(self.shown)
            except:
                pass
                
            # this method will only be invoked once
            self.shown = 55555


    # update categories, save, and display                
    def reload_categories(self):
    
        # get data and save
        self.update_categories()
        if self.categories:
            conf.save("cache/categories_"+self.module, self.categories)
        if self.catmap:
            conf.save("cache/catmap_" + self.module, self.catmap);

        # display outside of this non-main thread            
        uikit.do(self.display_categories)


    # insert content into gtk category list
    def display_categories(self):
    
        # remove any existing columns
        if self.gtk_cat:
            [self.gtk_cat.remove_column(c) for c in self.gtk_cat.get_columns()]

Modified channels/punkcast.py from [b9d872c733] to [b3964ae39a].

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#   ytANoskYgMb5W64b51jEuJ6Lq3Rmy2tEUcz1WR1pGGiyiIhMDOlQGHZRQRAQRR4CqJ2eUNvbJcAiEYz45NMtKrdvkc8a1G5M81e1RuiGjDpt1CTASKeQytDxowmmZdLpdDl8+QJLBqTxKSYkc0afbOyz
#   OD/F/fIynhA0wwChazhIpOuNYCJI6hpxPOHiqk1K8+ldthDumFhXnLUHjEZj9DggKyWfbWywPpXHVqBEOKayXubDyl1qx0f0eh1E6GLaRX7aO8NLJjG9PvNLt0kpuJWeMHPTxnan6V81UWmpYacTVN57
#   H9uewu01+OPJDle9AY//O+Dj++9yr3wHUnl8PyCTyzL2PE5aPUgYaFlNPDKigGe7VW6U1iDwcXuXzBbz+K5PThMEvQ6xjJjEAi2Z4Wn1mP03FyR1DaU0jUbnGj81IZXLkckoSpUV7JTB0HF4urvH3IMN
#   jG6XbGGWemeAkc+hNEE0DtCSmngUCkFSixn321iWyVzGImEIVssllAh5e9kkCDV2T05pD4dMTeWpv6ohwghp6opCOOHh1kfcXZzm8S/bPNs/ZOC4mELj4eYGWiT54Z896qMJ9vws4XBE3lRIBeqmDmk/
#   Qmnw+YNNlsur/PrbDqv5NFm7SHMwpNbuMggClmYL5JWkqytErKGPPVQmilhLSpbtBE1nxMFhjedHrymkNUrv3KHTuqLvjWl7MQevL+gNXaqNLlGzSyUvUVuEzMzkWVhZYqd6xPc//o6MBfWrAZcdBxnH
#   zOcy/F1rsf3yFFmtEcWwOZ2h1R8hP5jATGmRQE+wXz0gjiMWihnO2z1qjS6ZQoF76yuU7DSxFFhJC10phmHIhVT8D1yefHn5PzXrAAAAAElFTkSuQmCC
# priority: obsolete
# config: { name: punkcast_img, type: boolean, value: 0, desciption: Load banners. (Channel > Update favicons) }
#
# Punkcast is no longer updated. This plugin is kept for
# historic reasons. It was one of the default streamtuner1
# channels.


import re







|







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#   ytANoskYgMb5W64b51jEuJ6Lq3Rmy2tEUcz1WR1pGGiyiIhMDOlQGHZRQRAQRR4CqJ2eUNvbJcAiEYz45NMtKrdvkc8a1G5M81e1RuiGjDpt1CTASKeQytDxowmmZdLpdDl8+QJLBqTxKSYkc0afbOyz
#   OD/F/fIynhA0wwChazhIpOuNYCJI6hpxPOHiqk1K8+ldthDumFhXnLUHjEZj9DggKyWfbWywPpXHVqBEOKayXubDyl1qx0f0eh1E6GLaRX7aO8NLJjG9PvNLt0kpuJWeMHPTxnan6V81UWmpYacTVN57
#   H9uewu01+OPJDle9AY//O+Dj++9yr3wHUnl8PyCTyzL2PE5aPUgYaFlNPDKigGe7VW6U1iDwcXuXzBbz+K5PThMEvQ6xjJjEAi2Z4Wn1mP03FyR1DaU0jUbnGj81IZXLkckoSpUV7JTB0HF4urvH3IMN
#   jG6XbGGWemeAkc+hNEE0DtCSmngUCkFSixn321iWyVzGImEIVssllAh5e9kkCDV2T05pD4dMTeWpv6ohwghp6opCOOHh1kfcXZzm8S/bPNs/ZOC4mELj4eYGWiT54Z896qMJ9vws4XBE3lRIBeqmDmk/
#   Qmnw+YNNlsur/PrbDqv5NFm7SHMwpNbuMggClmYL5JWkqytErKGPPVQmilhLSpbtBE1nxMFhjedHrymkNUrv3KHTuqLvjWl7MQevL+gNXaqNLlGzSyUvUVuEzMzkWVhZYqd6xPc//o6MBfWrAZcdBxnH
#   zOcy/F1rsf3yFFmtEcWwOZ2h1R8hP5jATGmRQE+wXz0gjiMWihnO2z1qjS6ZQoF76yuU7DSxFFhJC10phmHIhVT8D1yefHn5PzXrAAAAAElFTkSuQmCC
# priority: obsolete
# config: { name: punkcast_img, type: boolean, value: 0, description: Load banners. (Channel - Update favicons) }
#
# Punkcast is no longer updated. This plugin is kept for
# historic reasons. It was one of the default streamtuner1
# channels.


import re

Modified channels/surfmusik.py from [08bed30ea2] to [9e4623f402].

110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
        entries = []
        i = 0
        max = int(conf.max_streams)
        is_tv = 0
        
        # placeholder category
        if cat in ["Genres"]:
            path = None
        # separate
        elif cat in ["Poli", "Flug"]:
            path = ""
        # tv
        elif cat in ["SurfTV", "MusikTV", "NewsTV"]:
            path = ""
            is_tv = 1







|







110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
        entries = []
        i = 0
        max = int(conf.max_streams)
        is_tv = 0
        
        # placeholder category
        if cat in ["Genres"]:
            return self.placeholder
        # separate
        elif cat in ["Poli", "Flug"]:
            path = ""
        # tv
        elif cat in ["SurfTV", "MusikTV", "NewsTV"]:
            path = ""
            is_tv = 1