Skip to content

Commit 9a748ea

Browse files
committed
- adding pyradio-client script
- pyradio-client will use requests instead of socket - adding pyradio-client -r command line parameter, removing -l - pyradio-client will send / if no command specified - Config Window: Do not open the Recoding Dir window, if a headless instance is recording
1 parent 9b19548 commit 9a748ea

15 files changed

+473
-42
lines changed

devel/build_install_pyradio

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ function uninstall_sudo(){
124124
sudo echo 'Uninstalling PyRadio'
125125
echo -n ' ** Removing executable ... '
126126
sudo rm -f `which pyradio 2>/dev/null` 2>/dev/null
127+
sudo rm -f `which pyradio-client 2>/dev/null` 2>/dev/null
127128
if [ -d /usr/share/doc/pyradio ]
128129
then
129130
sudo rm -rf /usr/share/doc/pyradio 2>/dev/null

docs/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,7 @@ <h2 id="command-line-options">Command line options <span style="padding-left: 10
12841284
[-scv PNG_FILE] [-srt] [-ach] [--headless IP_AND_PORT]
12851285
[--address] [-fd]
12861286

1287-
Curses based Internet radio player
1287+
Curses based Internet Radio Player
12881288

12891289
General options:
12901290
-h, --help Show this help message and exit

docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ Usage: pyradio [-h] [-c CONFIG_DIR] [-p [STATION_NUMBER]] [-u PLAYER] [-a]
169169
[-scv PNG_FILE] [-srt] [-ach] [--headless IP_AND_PORT]
170170
[--address] [-fd]
171171
172-
Curses based Internet radio player
172+
Curses based Internet Radio Player
173173
174174
General options:
175175
-h, --help Show this help message and exit

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ cli = [
2424

2525
[project.scripts]
2626
pyradio = "pyradio.main:shell"
27+
pyradio-client = "pyradio.main:run_client"
2728

2829
[project.urls]
2930
"Homepage" = "https://github.com/coderholic/pyradio"

pyradio/client.py

+327
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
# -*- coding: utf-8 -*-
2+
import argparse
3+
from argparse import ArgumentParser, SUPPRESS as SUPPRESS
4+
import requests
5+
from os import path, getenv
6+
import sys
7+
import re
8+
from sys import platform
9+
from rich import print
10+
11+
def format_list(a_string):
12+
print(a_string.replace(
13+
'[', '\[').replace(']', ']').replace(
14+
'/x,y', '/[green]x[/green],[blue]y[/blue]').replace(
15+
'/x', '/[green]x[/green]').replace(
16+
'[x]', '[[green]x[/green]]').replace(
17+
'x%', '[green]x[/green]%').replace(
18+
'id x', 'id [green]x[/green]').replace(
19+
'item x', 'item [green]x[/green]').replace(
20+
'if x', 'if [green]x[/green]').replace(
21+
'id y', 'id [blue]y[/blue]').replace(
22+
'(x', '([green]x[/green]').replace(
23+
' (text only)', '').replace(
24+
'RadioBrowser', '[medium_purple]RadioBrowser[/medium_purple]').replace(
25+
'(headless)', '([blue]headless[/blue])'
26+
)
27+
)
28+
29+
class PyRadioClient(object):
30+
31+
def __init__(
32+
self,
33+
host=None,
34+
port=None,
35+
server_file=None,
36+
alternative_server_file=None,
37+
timeout=1.0,
38+
reverse_detection=False
39+
):
40+
self._host = None
41+
self._port = None
42+
self._file = None
43+
self._files = None
44+
self._last_command = None
45+
self._last_reply = None
46+
self._timeout = timeout
47+
self._type = -1
48+
49+
if host and port:
50+
self._host = host
51+
self._port = port
52+
''' set self._file so that
53+
server_found()
54+
is_recording()
55+
are happy '''
56+
self._file = host
57+
elif server_file:
58+
if path.exists(server_file):
59+
self._file = server_file
60+
elif alternative_server_file is not None:
61+
if path.exists(alternative_server_file):
62+
self._file = alternative_server_file
63+
64+
if self._file:
65+
self._get_host_and_port_from_file()
66+
else:
67+
self._get_files()
68+
# search for files
69+
chk = (1, 0) if reverse_detection else (0, 1)
70+
for n in chk:
71+
if path.exists(self._files[n]):
72+
self._file = self._files[n]
73+
self._type = n
74+
break
75+
if self._file:
76+
self._get_host_and_port_from_file()
77+
78+
@property
79+
def server_ip(self):
80+
return self._host
81+
82+
@property
83+
def server_port(self):
84+
return self._port
85+
86+
@property
87+
def server_found(self):
88+
return False if self._file is None else True
89+
90+
@property
91+
def last_command(self):
92+
return self._last_command
93+
94+
@property
95+
def last_reply(self):
96+
server_id = ''
97+
if self._type >= 0:
98+
if self._last_command:
99+
server_id = 'Server found: [green]{}[/green]:[green]{}[/green] {}\n'.format(
100+
self._host, self._port,
101+
'([blue]headless[/blue])' if self._type == 0 else ''
102+
)
103+
else:
104+
server_id = 'Server found: {}:{} {}\n'.format(
105+
self._host, self._port,
106+
'(headless)' if self._type == 0 else ''
107+
)
108+
if self._last_reply:
109+
# out = None
110+
# ss = self._last_reply.splitlines()
111+
# for n in range(0, len(ss)):
112+
# if ss[n] == '':
113+
# out = ss[n+1:]
114+
# break
115+
# if out:
116+
# if self._type == -1:
117+
# return '\n'.join(out)
118+
# else:
119+
# return server_id + '\n' + '\n'.join(out)
120+
return server_id + '\n' + self._last_reply if server_id else self._last_reply
121+
122+
# empty reply
123+
if self._type == -1:
124+
return 'Command executed'
125+
else:
126+
return server_id + '\nCommand Executed'
127+
128+
def _get_files(self):
129+
if self._files is None:
130+
if platform.lower().startswith('win'):
131+
appdata = path.join(
132+
getenv('APPDATA'),
133+
'pyradio', 'data')
134+
self._files = (
135+
path.join(appdata, 'server-headless.txt'),
136+
path.join(appdata, 'server.txt')
137+
)
138+
139+
else:
140+
''' linux et al '''
141+
# get XDG dirs
142+
data_dir = getenv(
143+
'XDG_DATA_HOME',
144+
path.join(path.expanduser('~'), '.local', 'share', 'pyradio')
145+
)
146+
state_dir = getenv(
147+
'XDG_STATE_HOME',
148+
path.join(path.expanduser('~'), '.local', 'state', 'pyradio')
149+
)
150+
if not path.exists(data_dir) or not path.exists(state_dir):
151+
state_dir = getenv(
152+
'XDG_CONFIG_HOME',
153+
path.join(path.expanduser('~'), '.config')
154+
)
155+
state_dir = path.join(state_dir, 'pyradio', 'data')
156+
self._files = (
157+
path.join(state_dir, 'server-headless.txt'),
158+
path.join(state_dir, 'server.txt')
159+
)
160+
print(f'{self._files}')
161+
162+
def print_addresses(self):
163+
self._get_files()
164+
disp = []
165+
tok = ('Headless server', 'Server')
166+
out = ' {}: {}'
167+
for n in 0, 1:
168+
if path.exists(self._files[n]):
169+
try:
170+
with open(self._files[n], 'r') as f:
171+
addr = f.read()
172+
disp.append(out.format(tok[n], addr))
173+
except:
174+
pass
175+
if disp:
176+
print('[magenta]PyRadio Remote Control Server[/magenta]\n' + '\n'.join(disp))
177+
else:
178+
print('No [magenta]PyRadio[/magenta] Remote Control Servers running\n')
179+
180+
def is_recording(self):
181+
''' Return recording to file status
182+
Return value:
183+
-2 : Error
184+
-1 : request timeout
185+
0 : not recording
186+
1 : recording a file
187+
2 : No files found
188+
'''
189+
if self._file:
190+
ret, self._last_reply = self.send_command('srec')
191+
if ret == 0:
192+
if 'currently' in self._last_reply:
193+
''' recording to file '''
194+
return 1
195+
''' not recording to file '''
196+
return 0
197+
elif ret == 2:
198+
''' request timeout '''
199+
return -1
200+
''' error '''
201+
return -2
202+
''' no server files found '''
203+
return 2
204+
205+
def send_command(self, command):
206+
'''
207+
0 : all ok
208+
1 : error
209+
'''
210+
if command is None:
211+
command = ''
212+
try:
213+
response = requests.get(
214+
'http://' + self._host + ':' + self._port + '/' + command,
215+
timeout=self._timeout)
216+
response.raise_for_status() # Raise an exception for HTTP errors
217+
self._last_reply = response.text
218+
return 0, self._last_reply
219+
except requests.exceptions.RequestException as e:
220+
self._last_reply = f'{str(e)}'.split(':')[-1].strip(
221+
).replace('"', '').replace("'", '').replace(')', '')
222+
return 1, self._last_reply
223+
224+
def _get_host_and_port_from_file(self):
225+
try:
226+
with open(self._file, 'r') as f:
227+
line = f.read()
228+
except:
229+
pass
230+
sp = line.split(':')
231+
try:
232+
self._host = sp[0]
233+
self._port = sp[1]
234+
except IndexError:
235+
pass
236+
237+
238+
239+
class MyArgParser(ArgumentParser):
240+
241+
def __init(self):
242+
super(MyArgParser, self).__init__(
243+
description = description
244+
)
245+
246+
def print_usage(self, file=None):
247+
if file is None:
248+
file = sys.stdout
249+
usage = self.format_usage()
250+
print(self._add_colors(self.format_usage()))
251+
252+
def print_help(self, file=None):
253+
if file is None:
254+
file = sys.stdout
255+
print(self._add_colors(self.format_help()))
256+
257+
def _add_colors(self, txt):
258+
t = txt.replace('show this help', 'Show this help').replace('usage:', '• Usage:').replace('options:', '• General options:').replace('[', '|').replace(']', '||')
259+
x = re.sub(r'([^a-zZ-Z0-9])(--*[^ ,\t|]*)', r'\1[red]\2[/red]', t)
260+
t = re.sub(r'([A-Z_][A-Z_]+)', r'[green]\1[/green]', x)
261+
x = re.sub('([^"]pyradio)', r'[magenta]\1[/magenta]', t, flags=re.I)
262+
t = re.sub(r'(player_name:[a-z:_]+)', r'[plum2]\1[/plum2]', x)
263+
x = re.sub(r'(•.*:)', r'[orange_red1]\1[/orange_red1]', t)
264+
t = x.replace('mpv', '[green]mpv[/green]').replace(
265+
'mplayer', '[green]mplayer[/green]').replace(
266+
'vlc', '[green]vlc[/green]').replace(
267+
'command', '[green]command[/green]').replace(
268+
'[green]command[/green]s', 'commands').replace(
269+
'[magenta] pyradio[/magenta]-client',
270+
' [magenta]pyradio-client[/magenta]').replace(
271+
'PyRadio[/magenta] Remote Control Client',
272+
'PyRadio Remote Control Client[/magenta]')
273+
# with open('/home/spiros/pyradio-client.txt', 'w') as f:
274+
# f.write(t + '\n')
275+
return '[bold]' + t.replace('||', r']').replace('|', r'\[').replace('• ', '') + '[/bold]'
276+
277+
278+
def client():
279+
280+
parser = MyArgParser(
281+
description='PyRadio Remote Control Client'
282+
)
283+
284+
parser.add_argument('--address', action='store_true',
285+
help='List available servers')
286+
287+
server_opts = parser.add_argument_group('• Server Parameters')
288+
server_opts.add_argument('-s', '--server_and_port', default='',
289+
help="Set the servers's IP and PORT (format: IP:PORT)")
290+
server_opts.add_argument('-r', '--reverse-detection', action='store_true', default=False,
291+
help='Reverse server detection (when no server IP and PORT specified);'
292+
' detect headless server last, instead of headless server first')
293+
server_opts.add_argument('-t', '--timeout', default='1.0',
294+
help='Set the timeout (default = 1.0)')
295+
server_opts.add_argument('command', nargs='?', type=str, default=None,
296+
help='The command to send to the server')
297+
args = parser.parse_args()
298+
# sys.stdout.flush()
299+
300+
timeout = float(args.timeout)
301+
302+
if args.address:
303+
x = PyRadioClient()
304+
x.print_addresses()
305+
sys.exit()
306+
307+
host = None
308+
port = None
309+
if args.server_and_port:
310+
try:
311+
host, port = args.server_and_port.split(':')
312+
except ValueError:
313+
print('[red]Error[/red]: Invalid server IP and PORT specified\n')
314+
sys.exit()
315+
x = PyRadioClient(host=host, port=port, reverse_detection=args.reverse_detection)
316+
317+
if x.server_ip is None or x.server_port is None:
318+
print('No [magenta]PyRadio[/magenta] Remote Control Servers running\n')
319+
else:
320+
x.send_command(args.command)
321+
if x.last_command:
322+
print(x.last_reply)
323+
else:
324+
format_list(x.last_reply)
325+
326+
if __name__ == '__main__':
327+
client()

pyradio/config.py

+2
Original file line numberDiff line numberDiff line change
@@ -2360,6 +2360,8 @@ def _read_config(self, distro_config=False):
23602360
if not platform.startswith('win') and \
23612361
not self.xdg_compliant and \
23622362
distro_config:
2363+
# d_dir = path.join(XdgDirs.get_xdg_dir('XDG_DATA_HOME'), 'pyradio')
2364+
# s_dir = path.join(XdgDirs.get_xdg_dir('XDG_STATE_HOME'), 'pyradio')
23632365
d_dir = XdgDirs.get_xdg_dir('XDG_DATA_HOME')
23642366
s_dir = XdgDirs.get_xdg_dir('XDG_STATE_HOME')
23652367
if path.exists(d_dir) and path.exists(s_dir):

0 commit comments

Comments
 (0)