Skip to content

Commit 3cc81a3

Browse files
committed
- Clicking a station is now threaded
- Starting Radio Browser search window
1 parent 86a509a commit 3cc81a3

File tree

5 files changed

+456
-16
lines changed

5 files changed

+456
-16
lines changed

pyradio/browser.py

Lines changed: 209 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# -*- coding: utf-8 -*-
2-
# http://www.radio-browser.info/webservice#Advanced_station_search
32
import curses
43
try:
54
from dns import resolver
@@ -19,6 +18,7 @@
1918
from .player import info_dict_to_list
2019
from .cjkwrap import cjklen, PY3
2120
from .countries import countries
21+
from .simple_curses_widgets import SimpleCursesLineEdit, SimpleCursesHorizontalPushButtons
2222

2323
import locale
2424
locale.setlocale(locale.LC_ALL, '') # set your locale
@@ -50,14 +50,14 @@ class PyRadioStationsBrowser(object):
5050

5151
BASE_URL = ''
5252
TITLE = ''
53-
_parent = None
53+
_parent = _outer_parent = None
5454
_raw_stations = []
5555
_last_search = None
5656
_internal_header_height = 0
5757
_url_timeout = 3
5858
_search_timeout = 3
5959
_vote_callback = None
60-
_sort = None
60+
_sort = _sort_win = None
6161

6262
# Normally outer boddy (holding box, header, internal header) is
6363
# 2 chars wider that the internal body (holding the stations)
@@ -99,6 +99,16 @@ def parent(self, val):
9999
if self._sort:
100100
self._sort._parent = val
101101

102+
@property
103+
def outer_parent(self):
104+
return self._outer_parent
105+
106+
@outer_parent.setter
107+
def outer_parent(self, val):
108+
self._outer_parent = val
109+
if self._sort_win:
110+
self._sort_win._parent = val
111+
102112
@property
103113
def outer_internal_body_half_diff(self):
104114
return self._outer_internal_body_half_diff
@@ -322,14 +332,16 @@ def url(self, id_in_list):
322332
return ''
323333

324334
def click(self, a_station):
325-
url = 'http://' + self._server + '/json/url/' + self._raw_stations[a_station]['stationuuid']
326-
try:
327-
r = self._session.get(url=url, headers=self._headers, timeout=(self._search_timeout, 2 * self._search_timeout))
328-
if logger.isEnabledFor(logging.DEBUG):
329-
logger.debug('Station click result: "{}"'.format(r.text))
330-
except:
331-
if logger.isEnabledFor(logging.DEBUG):
332-
logger.debug('Station click failed...')
335+
def do_click(a_station_uuid):
336+
url = 'http://' + self._server + '/json/url/' + a_station_uuid
337+
try:
338+
r = self._session.get(url=url, headers=self._headers, timeout=(self._search_timeout, 2 * self._search_timeout))
339+
if logger.isEnabledFor(logging.DEBUG):
340+
logger.debug('Station click result: "{}"'.format(r.text))
341+
except:
342+
if logger.isEnabledFor(logging.DEBUG):
343+
logger.debug('Station click failed...')
344+
threading.Thread(target=do_click, args=(self._raw_stations[a_station]['stationuuid'], )).start()
333345

334346
def vote(self, a_station):
335347
url = 'http://' + self._server + '/json/vote/' + self._raw_stations[a_station]['stationuuid']
@@ -1082,6 +1094,192 @@ def keypress(self, char):
10821094

10831095
return ret
10841096

1097+
def do_search(self, parent=None, init=False):
1098+
if init:
1099+
self._sort_win = RadioBrowserInfoSearchWindow(
1100+
parent=parent,
1101+
init=init
1102+
)
1103+
self.keyboard_handler = self._sort_win
1104+
self._sort_win.show()
1105+
1106+
class RadioBrowserInfoSearchWindow(object):
1107+
1108+
search_by_items = (
1109+
'Name',
1110+
'Tag',
1111+
'Country',
1112+
'State',
1113+
'Codec',
1114+
'Language',
1115+
)
1116+
1117+
sort_by_items = (
1118+
'None',
1119+
'Random',
1120+
'Name',
1121+
'Tag',
1122+
'Country',
1123+
'State',
1124+
'Language',
1125+
'Votes',
1126+
'Clicks',
1127+
'Bitrate',
1128+
'Codec',
1129+
)
1130+
1131+
def __init__(self,
1132+
parent,
1133+
init=False
1134+
):
1135+
self._parent = parent
1136+
self._init = init
1137+
self._too_small = False
1138+
self._focus = 0
1139+
self._win = None
1140+
self.maxY = self.maxX = 0
1141+
self.TITLE = ' Radio Browser Search '
1142+
1143+
self._widgets = [ None, None, None, None, None ]
1144+
1145+
@property
1146+
def focus(self):
1147+
return self._focus
1148+
1149+
@focus.setter
1150+
def focus(self, val):
1151+
if val in range(0, len(self._widgets)):
1152+
self._focus = val
1153+
else:
1154+
if val < 0:
1155+
self._focus = len(self._widgets) - 1
1156+
else:
1157+
self._focus = 0
1158+
self.show()
1159+
1160+
def show(self):
1161+
pY, pX = self._parent.getmaxyx()
1162+
logger.error('DE pY = {}, pX = {}'.format(pY, pX))
1163+
self.Y, self.X = self._parent.getbegyx()
1164+
1165+
if self.maxY != pY or self.maxX != pX:
1166+
logger.error('DE --== SEARCH ==--')
1167+
pY, pX = self._parent.getmaxyx()
1168+
logger.error('DE pY = {}, pX = {}'.format(pY, pX))
1169+
self.maxY = pY
1170+
self.maxX = pX
1171+
self._win = self._parent
1172+
# self._win = curses.newwin(
1173+
# self.maxY, self.maxX,
1174+
# Y, X
1175+
# )
1176+
self._win.bkgdset(' ', curses.color_pair(5))
1177+
# self._win.erase()
1178+
self._win.box()
1179+
self._win.addstr(0, int((self.maxX - len(self.TITLE)) / 2),
1180+
self.TITLE,
1181+
curses.color_pair(4))
1182+
self._win.refresh()
1183+
self._erase_win(self.maxY, self.maxX, self.Y, self.X)
1184+
1185+
''' start displaying things '''
1186+
self._win.addstr(1, 2, 'Search for', curses.color_pair(5))
1187+
self._win.addstr(4, 2, 'Search by', curses.color_pair(5))
1188+
1189+
for i, n in enumerate(self._widgets):
1190+
if n is None:
1191+
if i == 0:
1192+
self._widgets[0] = SimpleCursesLineEdit(
1193+
parent=self._win,
1194+
width=-2,
1195+
begin_y=3,
1196+
begin_x=2,
1197+
boxed=False,
1198+
has_history=False,
1199+
caption='',
1200+
box_color=curses.color_pair(9),
1201+
caption_color=curses.color_pair(4),
1202+
edit_color=curses.color_pair(9),
1203+
cursor_color=curses.color_pair(8),
1204+
unfocused_color=curses.color_pair(5),
1205+
string_changed_handler='')
1206+
self._widgets[0].bracket = False
1207+
self._line_editor = self._widgets[0]
1208+
elif i == 1:
1209+
self._widgets[i] = None
1210+
elif i == 2:
1211+
self._widgets[i] = None
1212+
elif i == 3:
1213+
self._widgets[i] = None
1214+
elif i == 4:
1215+
self._widgets[i] = None
1216+
''' add horizontal push buttons '''
1217+
self._h_buttons = SimpleCursesHorizontalPushButtons(
1218+
Y=6, captions=('OK', 'Cancel'),
1219+
color_focused=curses.color_pair(9),
1220+
color=curses.color_pair(4),
1221+
bracket_color=curses.color_pair(5),
1222+
parent=self._win)
1223+
#self._h_buttons.calculate_buttons_position()
1224+
self._widgets[4], self._widgets[5] = self._h_buttons.buttons
1225+
self._widgets[4]._focused = self._widgets[5].focused = False
1226+
1227+
self._win.refresh()
1228+
self._update_focus()
1229+
if not self._too_small:
1230+
self._line_editor.show(self._win, opening=False)
1231+
self._h_buttons.calculate_buttons_position()
1232+
self._widgets[1].show()
1233+
self._widgets[2].show()
1234+
# self._refresh()
1235+
1236+
def _update_focus(self):
1237+
# use _focused here to avoid triggering
1238+
# widgets' refresh
1239+
for i, x in enumerate(self._widgets):
1240+
if x:
1241+
if self._focus == i:
1242+
x._focused = True
1243+
else:
1244+
x._focused = False
1245+
1246+
def _erase_win(self, pY, pX, Y, X):
1247+
empty_win = curses.newwin(
1248+
pY - 2, pX - 2,
1249+
Y + 1, X + 1
1250+
)
1251+
empty_win.bkgdset(' ', curses.color_pair(5))
1252+
empty_win.erase()
1253+
empty_win.refresh()
1254+
1255+
def keypress(self, char):
1256+
''' RadioBrowserInfoSearchWindow keypress
1257+
1258+
Returns
1259+
-------
1260+
-1 - Cancel
1261+
0 - do search
1262+
1 - Continue
1263+
2 - Display help
1264+
'''
1265+
if self._too_small:
1266+
return 1
1267+
1268+
if char == ord('?'):
1269+
return 2
1270+
1271+
if char in (
1272+
curses.KEY_EXIT, ord('q'), 27,
1273+
ord('h'), curses.KEY_LEFT
1274+
):
1275+
return -1
1276+
1277+
elif char in (
1278+
ord('l'), ord(' '), ord('\n'), ord('\r'),
1279+
curses.KEY_RIGHT, curses.KEY_ENTER
1280+
):
1281+
return 0
1282+
10851283
class RadioBrowserInfoData(object):
10861284
''' Read search parameters for radio.browser.info service
10871285

pyradio/player.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ class Player(object):
213213

214214
all_config_files = {}
215215

216+
''' the function to call when a station starts to play
217+
(playback detected) to add a click to its click counter
218+
'''
219+
click_station_function = None
220+
216221
def __init__(self,
217222
config,
218223
outputStream,
@@ -518,6 +523,10 @@ def _stop_delay_thread(self):
518523
pass
519524
self.delay_thread = None
520525

526+
''' click station (if applicable) '''
527+
if self.click_station_function and self.isPlaying():
528+
self.click_station_function()
529+
521530
def _is_in_playback_token(self, a_string):
522531
for a_token in self._playback_token_tuple:
523532
if a_token in a_string:

0 commit comments

Comments
 (0)