Skip to content

Commit fc4beaf

Browse files
committed
- finilizing update notification
- starting session locking
1 parent 4f87509 commit fc4beaf

File tree

4 files changed

+262
-29
lines changed

4 files changed

+262
-29
lines changed

Changelog

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
2019-06-21
2+
* mpv now uses a dedicated socket file. This way multiple instances of
3+
PyRadio can be executed.
4+
* Introducing session locking.
5+
* Added the "--unlock" command line parameter, to force sessions' unlock.
6+
* PyRadio can load external theme files.
7+
* Three more themes added. These are system themes (actual files).
8+
* Theme selection window reworked - themes are separated by location,
9+
theme selection is remembered when resizing, and loading default or
10+
saved theme (in config window).
11+
* PyRadio will check and report when a new release is available.
12+
* Added good bye message.
13+
* Theme editor implementation started (disabled for this release).
14+
* Minor other fixes.
15+
116
2019-06-06
217
* Verion 0.7.6.2
318
This is a BUG FIX release, fixing config status (indicating whether

pyradio/config.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class PyRadioStations(object):
4545

4646
playlist_recovery_result = 0
4747

48+
locked = False
49+
4850
def __init__(self, stationFile=''):
4951

5052
if sys.platform.startswith('win'):
@@ -60,15 +62,25 @@ def __init__(self, stationFile=''):
6062
sys.exit(1)
6163
self.root_path = path.join(path.dirname(__file__), 'stations.csv')
6264

63-
""" If a station.csv file exitst, which is wrong,
64-
we rename it to stations.csv """
65-
if path.exists(path.join(self.stations_dir, 'station.csv')):
66-
copyfile(path.join(self.stations_dir, 'station.csv'),
67-
path.join(self.stations_dir, 'stations.csv'))
68-
remove(path.join(self.stations_dir, 'station.csv'))
65+
if path.exists(path.join(self.stations_dir, '.lock')):
66+
self.locked = True
67+
else:
68+
try:
69+
with open(path.join(self.stations_dir, '.lock'), 'w') as f:
70+
pass
71+
except:
72+
pass
73+
74+
if not self.locked:
75+
""" If a station.csv file exitst, which is wrong,
76+
we rename it to stations.csv """
77+
if path.exists(path.join(self.stations_dir, 'station.csv')):
78+
copyfile(path.join(self.stations_dir, 'station.csv'),
79+
path.join(self.stations_dir, 'stations.csv'))
80+
remove(path.join(self.stations_dir, 'station.csv'))
6981

70-
self._move_old_csv(self.stations_dir)
71-
self._check_stations_csv(self.stations_dir, self.root_path)
82+
self._move_old_csv(self.stations_dir)
83+
self._check_stations_csv(self.stations_dir, self.root_path)
7284

7385
def _move_old_csv(self, usr):
7486
""" if a ~/.pyradio files exists, relocate it in user
@@ -696,7 +708,12 @@ def save_config(self):
696708
Returns:
697709
-1: Error saving config
698710
0: Config saved successfully
699-
1: Config not saved (not modified"""
711+
1: Config not saved (not modified)
712+
TODO: 2: Config not saved (session locked) """
713+
if self.locked:
714+
if logger.isEnabledFor(logging.INFO):
715+
logger.info('Config not saved (session locked)')
716+
return 1
700717
if not self.opts['dirty_config'][1]:
701718
if logger.isEnabledFor(logging.INFO):
702719
logger.info('Config not saved (not modified)')

pyradio/main.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ def shell():
4040
print('Pyradio requires python 2.7 or 3.5+...')
4141
sys.exit(1)
4242

43-
# set window title
44-
try:
45-
sys.stdout.write("\x1b]2;PyRadio: The Internet Radio player\x07")
46-
except:
47-
pass
4843
requested_player = ''
4944
parser = ArgumentParser(description="Curses based Internet radio player")
5045
parser.add_argument("-s", "--stations", default='',
@@ -67,13 +62,24 @@ def shell():
6762
help="Print config directory location and exit.")
6863
parser.add_argument("-ocd", "--open-config-dir", action='store_true',
6964
help="Open config directory with default file manager.")
65+
parser.add_argument('--unlock', action='store_true',
66+
help="Remove sessions' lock file.")
7067
parser.add_argument("-d", "--debug", action='store_true',
7168
help="Start pyradio in debug mode.")
7269
args = parser.parse_args()
7370

7471
sys.stdout.flush()
7572
pyradio_config = PyRadioConfig()
7673

74+
# set window title
75+
try:
76+
if pyradio_config.locked:
77+
sys.stdout.write("\x1b]2;PyRadio: The Internet Radio player (session locked)\x07")
78+
else:
79+
sys.stdout.write("\x1b]2;PyRadio: The Internet Radio player\x07")
80+
except:
81+
pass
82+
7783
if args.show_config_dir:
7884
print('PyRadio config dir: "{}"'.format(pyradio_config.stations_dir))
7985
sys.exit()
@@ -86,6 +92,19 @@ def shell():
8692
pyradio_config.list_playlists()
8793
sys.exit()
8894

95+
if args.unlock:
96+
lock_file = path.join(pyradio_config.stations_dir, '.lock')
97+
if path.exists(lock_file):
98+
from os import remove
99+
try:
100+
remove(lock_file)
101+
print('Lock file removed...')
102+
except:
103+
print('Failed to remove Lock file...')
104+
else:
105+
print('Lock file not found...')
106+
sys.exit()
107+
89108
if args.list is False and args.add is False:
90109
print('Reading config...')
91110
ret = pyradio_config.read_config()

pyradio/radio.py

Lines changed: 197 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
from os.path import join, basename, getmtime, getsize
1818
from platform import system
1919
from time import ctime, sleep
20-
#from copy import deepcopy
21-
#from textwrap import wrap
20+
from datetime import datetime
2221

2322
from .common import *
2423
from .config_window import *
@@ -98,10 +97,14 @@ class PyRadio(object):
9897

9998
_old_config_encoding = ''
10099

101-
102-
_update_version = '0.8'
103-
_update_notify_lock = threading.Lock()
100+
# update notification
101+
_update_version = ''
104102
_update_version_do_display = ''
103+
_update_notification_thread = None
104+
stop_update_notification_thread = False
105+
_update_notify_lock = threading.Lock()
106+
107+
_locked = False
105108

106109
def __init__(self, pyradio_config, play=False, req_player='', theme=''):
107110
self._cnf = pyradio_config
@@ -413,7 +416,14 @@ def run(self):
413416
else:
414417
# start update thread
415418
if CAN_CHECK_FOR_UPDATES:
416-
pass
419+
if self._cnf.locked:
420+
if logger.isEnabledFor(logging.INFO):
421+
logger.info('(detectUpdateThread): session locked. Not starting!!!')
422+
else:
423+
self._update_notification_thread = threading.Thread(target=self.detectUpdateThread,
424+
args=(self._cnf.stations_dir, self._update_notify_lock,
425+
lambda: self.stop_update_notification_thread))
426+
self._update_notification_thread.start()
417427

418428
#signal.signal(signal.SIGINT, self.ctrl_c_handler)
419429
self.log.write('Selected player: {}'.format(self._format_player_string()), help_msg=True)
@@ -458,6 +468,24 @@ def ctrl_c_handler(self, signum, frame):
458468
""" Try to auto save config on exit
459469
Do not check result!!! """
460470
self._cnf.save_config()
471+
if not self._cnf.locked:
472+
lock_file = path.join(self._cnf.stations_dir, '.lock')
473+
if path.exists(lock_file):
474+
try:
475+
os.remove(lock_file)
476+
if logger.isEnabledFor(logging.INFO):
477+
logger.info('Lock file removed...')
478+
except:
479+
if logger.isEnabledFor(logging.INFO):
480+
logger.info('Failed to remove Lock file...')
481+
else:
482+
if logger.isEnabledFor(logging.INFO):
483+
logger.info('Lock file not found...')
484+
485+
if self._update_notification_thread:
486+
if self._update_notification_thread.is_alive():
487+
self.stop_update_notification_thread = True
488+
self._update_notification_thread.join()
461489

462490
def _goto_playing_station(self, changing_playlist=False):
463491
""" make sure playing station is visible """
@@ -1510,6 +1538,169 @@ def _show_config_window(self):
15101538
self._config_win.parent = self.bodyWin
15111539
self._config_win.refresh_config_win()
15121540

1541+
1542+
def detectUpdateThread(self, a_path, a_lock, stop):
1543+
""" a thread to check if an update is available """
1544+
1545+
def delay(secs, stop):
1546+
for i in range(0, 2 * secs):
1547+
sleep(.5)
1548+
if stop():
1549+
return
1550+
1551+
def to_ver(this_version):
1552+
a_v = this_version.replace('-r', '.')
1553+
a_l = a_v.split('.')
1554+
a_n_l = []
1555+
for n in a_l:
1556+
a_n_l.append(int(n))
1557+
return a_n_l
1558+
1559+
def clean_date_files(files, start=0):
1560+
files_to_delete = files[start+1:]
1561+
for a_file in files_to_delete:
1562+
try:
1563+
remove(a_file)
1564+
except:
1565+
pass
1566+
1567+
def create_tadays_date_file(a_path):
1568+
d1 = datetime.now()
1569+
now_str = d1.strftime('%Y-%m-%d')
1570+
try:
1571+
with open(path.join(a_path, '.' + now_str + '.date'), 'w') as f:
1572+
pass
1573+
except:
1574+
pass
1575+
1576+
if logger.isEnabledFor(logging.DEBUG):
1577+
logger.debug('detectUpdateThread: starting...')
1578+
##################
1579+
#delay(5, stop)
1580+
from pyradio import version as this_version
1581+
connection_fail_count = 0
1582+
delay(random.randint(25, 45), stop)
1583+
if stop():
1584+
if logger.isEnabledFor(logging.DEBUG):
1585+
logger.debug('detectUpdateThread: Asked to stop. Stoping...')
1586+
return
1587+
files = glob.glob(path.join(a_path, '.*.date'))
1588+
if files:
1589+
files.sort(reverse=True)
1590+
if len(files) > 1:
1591+
clean_date_files(files)
1592+
a_date = path.split(path.splitext(files[0])[0])[1][1:]
1593+
1594+
1595+
d1 = datetime.now()
1596+
d2 = datetime.strptime(a_date, '%Y-%m-%d')
1597+
delta = (d1 - d2).days
1598+
1599+
if delta > 3:
1600+
if logger.isEnabledFor(logging.DEBUG):
1601+
logger.debug('detectUpdateThread: checking for updates')
1602+
else:
1603+
if logger.isEnabledFor(logging.DEBUG):
1604+
if 3 - delta == 1:
1605+
logger.debug('detectUpdateThread: Will check tomorrow...')
1606+
else:
1607+
logger.debug('detectUpdateThread: Will check in {} days...'.format(3 - delta))
1608+
return
1609+
1610+
url = 'https://api.github.com/repos/coderholic/pyradio/tags'
1611+
while True:
1612+
if stop():
1613+
if logger.isEnabledFor(logging.DEBUG):
1614+
logger.debug('detectUpdateThread: Asked to stop. Stoping...')
1615+
break
1616+
if version_info < (3, 0):
1617+
try:
1618+
last_tag = urlopen(url).read(300)
1619+
except:
1620+
last_tag = None
1621+
else:
1622+
try:
1623+
with urlopen(url) as https_response:
1624+
last_tag = https_response.read(300)
1625+
except:
1626+
last_tag = None
1627+
1628+
if stop():
1629+
if logger.isEnabledFor(logging.DEBUG):
1630+
logger.debug('detectUpdateThread: Asked to stop. Stoping...')
1631+
break
1632+
if last_tag:
1633+
connection_fail_count = 0
1634+
x = str(last_tag).split('"name":"')
1635+
last_tag = x[1].split('"')[0]
1636+
#last_tag = '0.9.9'
1637+
if logger.isEnabledFor(logging.DEBUG):
1638+
logger.debug('detectUpdateThread: Upstream version found: {}'.format(last_tag))
1639+
if this_version == last_tag:
1640+
if logger.isEnabledFor(logging.DEBUG):
1641+
logger.debug('detectUpdateThread: No update found. Exiting...')
1642+
break
1643+
else:
1644+
existing_version = to_ver(this_version)
1645+
new_version = to_ver(last_tag)
1646+
if existing_version < new_version:
1647+
if stop():
1648+
if logger.isEnabledFor(logging.DEBUG):
1649+
logger.debug('detectUpdateThread: Asked to stop. Stoping...')
1650+
break
1651+
# remove all existing date files
1652+
clean_date_files(files, -1)
1653+
############
1654+
#delay(5 , stop)
1655+
delay(random.randint(120, 300), stop)
1656+
if stop():
1657+
if logger.isEnabledFor(logging.DEBUG):
1658+
logger.debug('detectUpdateThread: Asked to stop. Stoping...')
1659+
break
1660+
# set new verion
1661+
if logger.isEnabledFor(logging.INFO):
1662+
logger.info('detectUpdateThread: Update available: {}'.format(last_tag))
1663+
a_lock.acquire()
1664+
self._update_version = last_tag
1665+
a_lock.release()
1666+
while True:
1667+
""" Wait until self._update_version becomes ''
1668+
which means that notification window has been
1669+
displayed. Then create date file and exit.
1670+
If asked to terminate, do not write date file """
1671+
########################33
1672+
#delay(5, stop)
1673+
delay(60, stop)
1674+
if stop():
1675+
if logger.isEnabledFor(logging.DEBUG):
1676+
logger.debug('detectUpdateThread: Asked to stop. Stoping but not writing date file...')
1677+
return
1678+
a_lock.acquire()
1679+
if self._update_version == '':
1680+
a_lock.release()
1681+
# create today's date file
1682+
create_tadays_date_file(a_path)
1683+
if logger.isEnabledFor(logging.INFO):
1684+
logger.info('detectUpdateThread: Terminating after notification... I will check again in 3 days')
1685+
return
1686+
a_lock.release()
1687+
else:
1688+
if logger.isEnabledFor(logging.ERROR):
1689+
logger.error('detectUpdateThread: Ahead of upstream? (current version: {0}, upstream version: {1})'.format(this_version, last_tag))
1690+
break
1691+
1692+
else:
1693+
if logger.isEnabledFor(logging.DEBUG):
1694+
logger.debug('detectUpdateThread: Error: Cannot get upstream version!!!')
1695+
connection_fail_count += 1
1696+
if connection_fail_count > 4:
1697+
if logger.isEnabledFor(logging.DEBUG):
1698+
logger.debug('detectUpdateThread: Error: Too many connection failures. Exiting')
1699+
break
1700+
delay(60, stop)
1701+
1702+
1703+
15131704
def keypress(self, char):
15141705

15151706
if self.operation_mode == CONFIG_SAVE_ERROR_MODE:
@@ -2595,13 +2786,4 @@ def keypress(self, char):
25952786
self.selections[self.operation_mode] = (self.selection, self.startPos, self.playing, self._cnf.playlists)
25962787
self.refreshBody()
25972788

2598-
def detectUpdateThread(self, *args):
2599-
""" a thread to check if an update is available
2600-
2601-
arg[0]: config dir
2602-
arg[1]: lock
2603-
"""
2604-
while True:
2605-
2606-
pass
26072789
# pymode:lint_ignore=W901

0 commit comments

Comments
 (0)