diff --git a/nowplaying/db.py b/nowplaying/db.py index 123ecf20..151dbb38 100755 --- a/nowplaying/db.py +++ b/nowplaying/db.py @@ -10,6 +10,8 @@ import time import traceback +import aiosqlite + from watchdog.observers import Observer # pylint: disable=import-error from watchdog.events import PatternMatchingEventHandler # pylint: disable=import-error @@ -143,7 +145,7 @@ def watcher(self): ''' get access to a watch on the database file ''' return DBWatcher(self.databasefile) - def write_to_metadb(self, metadata=None): + async def write_to_metadb(self, metadata=None): ''' update metadb ''' def filterkeys(mydict): @@ -161,7 +163,8 @@ def filterkeys(mydict): if not self.databasefile.exists(): self.setupsql() - with sqlite3.connect(self.databasefile, timeout=10) as connection: + async with aiosqlite.connect(self.databasefile, + timeout=10) as connection: # do not want to modify the original dictionary # otherwise Bad Things(tm) will happen mdcopy = copy.deepcopy(metadata) @@ -170,7 +173,7 @@ def filterkeys(mydict): # toss any keys we do not care about mdcopy = filterkeys(mdcopy) - cursor = connection.cursor() + cursor = await connection.cursor() logging.debug('Adding record with %s/%s', mdcopy['artist'], mdcopy['title']) @@ -190,7 +193,8 @@ def filterkeys(mydict): sql += '?,' * (len(mdcopy.keys()) - 1) + '?)' datatuple = tuple(list(mdcopy.values())) - cursor.execute(sql, datatuple) + await cursor.execute(sql, datatuple) + await connection.commit() def make_previoustracklist(self): ''' create a reversed list of the tracks played ''' @@ -220,28 +224,38 @@ def make_previoustracklist(self): return previouslist - def read_last_meta(self): - ''' update metadb ''' + async def make_previoustracklist_async(self): + ''' create a reversed list of the tracks played ''' if not self.databasefile.exists(): logging.error('MetadataDB does not exist yet?') return None - with sqlite3.connect(self.databasefile, timeout=10) as connection: + async with aiosqlite.connect(self.databasefile, + timeout=10) as connection: connection.row_factory = sqlite3.Row - cursor = connection.cursor() + cursor = await connection.cursor() try: - cursor.execute( - '''SELECT * FROM currentmeta ORDER BY id DESC LIMIT 1''') + await cursor.execute( + '''SELECT artist, title FROM currentmeta ORDER BY id DESC''' + ) except sqlite3.OperationalError: - for line in traceback.format_exc().splitlines(): - logging.error(line) return None - row = cursor.fetchone() - if not row: - return None + records = await cursor.fetchall() + previouslist = [] + if records: + previouslist.extend({ + 'artist': row['artist'], + 'title': row['title'] + } for row in records) + + return previouslist + + @staticmethod + def _postprocess_read_last_meta(row): + ''' common post-process of read_last_meta ''' metadata = {data: row[data] for data in METADATALIST} for key in METADATABLOBLIST: metadata[key] = row[key] @@ -254,6 +268,59 @@ def read_last_meta(self): metadata[key] = metadata[key].split(SPLITSTR) metadata['dbid'] = row['id'] + return metadata + + async def read_last_meta_async(self): + ''' update metadb ''' + + if not self.databasefile.exists(): + logging.error('MetadataDB does not exist yet?') + return None + + async with aiosqlite.connect(self.databasefile, + timeout=10) as connection: + connection.row_factory = sqlite3.Row + cursor = await connection.cursor() + try: + await cursor.execute( + '''SELECT * FROM currentmeta ORDER BY id DESC LIMIT 1''') + except sqlite3.OperationalError: + for line in traceback.format_exc().splitlines(): + logging.error(line) + return None + + row = await cursor.fetchone() + await cursor.close() + if not row: + return None + + metadata = self._postprocess_read_last_meta(row) + metadata['previoustrack'] = await self.make_previoustracklist_async() + return metadata + + def read_last_meta(self): + ''' update metadb ''' + + if not self.databasefile.exists(): + logging.error('MetadataDB does not exist yet?') + return None + + with sqlite3.connect(self.databasefile, timeout=10) as connection: + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + try: + cursor.execute( + '''SELECT * FROM currentmeta ORDER BY id DESC LIMIT 1''') + except sqlite3.OperationalError: + for line in traceback.format_exc().splitlines(): + logging.error(line) + return None + + row = cursor.fetchone() + if not row: + return None + + metadata = self._postprocess_read_last_meta(row) metadata['previoustrack'] = self.make_previoustracklist() return metadata diff --git a/nowplaying/processes/discordbot.py b/nowplaying/processes/discordbot.py index 4817ee9c..b8ccc366 100644 --- a/nowplaying/processes/discordbot.py +++ b/nowplaying/processes/discordbot.py @@ -157,7 +157,7 @@ async def start(self): if not template: continue - metadata = metadb.read_last_meta() + metadata = await metadb.read_last_meta_async() if not metadata: continue diff --git a/nowplaying/processes/trackpoll.py b/nowplaying/processes/trackpoll.py index 97d9083b..29a9a300 100755 --- a/nowplaying/processes/trackpoll.py +++ b/nowplaying/processes/trackpoll.py @@ -355,7 +355,7 @@ async def gettrack(self): # pylint: disable=too-many-branches, if not self.testmode: metadb = nowplaying.db.MetadataDB() - metadb.write_to_metadb(metadata=self.currentmeta) + await metadb.write_to_metadb(metadata=self.currentmeta) self._write_to_text() def _artfallbacks(self): diff --git a/nowplaying/processes/webserver.py b/nowplaying/processes/webserver.py index 297a3cbe..2c0e686c 100755 --- a/nowplaying/processes/webserver.py +++ b/nowplaying/processes/webserver.py @@ -183,7 +183,7 @@ async def _htm_handler(request, template, metadata=None): # pylint: disable=unu htmloutput = INDEXREFRESH try: if not metadata: - metadata = request.app['metadb'].read_last_meta() + metadata = await request.app['metadb'].read_last_meta_async() if not metadata: metadata = nowplaying.hostmeta.gethostmeta() metadata['httpport'] = request.app['config'].cparser.value( @@ -202,7 +202,7 @@ async def _metacheck_htm_handler(self, request, template): # pylint: disable=un source = os.path.basename(template) htmloutput = "" request.app['config'].get() - metadata = request.app['metadb'].read_last_meta() + metadata = await request.app['metadb'].read_last_meta_async() lastid = await self.getlastid(request, source) once = request.app['config'].cparser.value('weboutput/once', type=bool) #once = False @@ -252,7 +252,7 @@ async def getlastid(request, source): @staticmethod async def indextxt_handler(request): ''' handle static index.txt ''' - metadata = request.app['metadb'].read_last_meta() + metadata = await request.app['metadb'].read_last_meta_async() txtoutput = "" if metadata: request.app['config'].get() @@ -279,7 +279,7 @@ async def _image_handler(imgtype, request): # this makes the client code significantly easier image = nowplaying.utils.TRANSPARENT_PNG_BIN try: - metadata = request.app['metadb'].read_last_meta() + metadata = await request.app['metadb'].read_last_meta_async() if metadata and metadata.get(imgtype): image = metadata[imgtype] except: #pylint: disable=bare-except @@ -306,7 +306,7 @@ async def artistthumb_handler(self, request): async def api_v1_last_handler(self, request): ''' v1/last just returns the metadata''' data = {} - if metadata := request.app['metadb'].read_last_meta(): + if metadata := await request.app['metadb'].read_last_meta_async(): try: del metadata['dbid'] data = self._base64ifier(metadata) @@ -364,7 +364,7 @@ async def websocket_artistfanart_streamer(self, request): try: while not self.stopevent.is_set( ) and not endloop and not websocket.closed: - metadata = request.app['metadb'].read_last_meta() + metadata = await request.app['metadb'].read_last_meta_async() if not metadata or not metadata.get('artist'): await asyncio.sleep(5) continue @@ -408,7 +408,7 @@ async def websocket_artistfanart_streamer(self, request): async def websocket_lastjson_handler(self, request, websocket): ''' handle singular websocket request ''' - metadata = request.app['metadb'].read_last_meta() + metadata = await request.app['metadb'].read_last_meta_async() del metadata['dbid'] if not websocket.closed: await websocket.send_json(self._base64ifier(metadata)) @@ -421,7 +421,7 @@ async def _wss_do_update(self, websocket, database): while not metadata and not websocket.closed: if self.stopevent.is_set(): return time.time() - metadata = database.read_last_meta() + metadata = await database.read_last_meta_async() await asyncio.sleep(1) del metadata['dbid'] if not websocket.closed: diff --git a/nowplaying/trackrequests.py b/nowplaying/trackrequests.py index 56222207..08b41075 100644 --- a/nowplaying/trackrequests.py +++ b/nowplaying/trackrequests.py @@ -659,7 +659,7 @@ async def twofer_request(self, setting, user, user_input): ''' twofer request ''' metadb = nowplaying.db.MetadataDB() - metadata = metadb.read_last_meta() + metadata = await metadb.read_last_meta_async() if not metadata: logging.debug('Twofer: No currently playing track? skipping') return {} diff --git a/nowplaying/twitch/chat.py b/nowplaying/twitch/chat.py index 8bb209dc..5a6760fc 100644 --- a/nowplaying/twitch/chat.py +++ b/nowplaying/twitch/chat.py @@ -388,7 +388,7 @@ async def _async_announce_track(self): anntemplate) return - metadata = self.metadb.read_last_meta() + metadata = await self.metadb.read_last_meta_async() if not metadata: logging.debug('No metadata to announce') @@ -425,7 +425,7 @@ async def _post_template(self, msg=None, template=None, moremetadata=None): #py ''' take a template, fill it in, and post it ''' if not template: return - metadata = self.metadb.read_last_meta() + metadata = await self.metadb.read_last_meta_async() if not metadata: metadata = {} if 'coverimageraw' in metadata: diff --git a/tests/test_db.py b/tests/test_db.py index 6dafdffe..854f9886 100755 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -41,21 +41,38 @@ def results(expected, metadata): assert metadata == {} -def test_empty_db(getmetadb): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_empty_db(getmetadb): # pylint: disable=redefined-outer-name ''' test writing false data ''' metadb = getmetadb - metadb.write_to_metadb(metadata=None) + await metadb.write_to_metadb(metadata=None) readdata = metadb.read_last_meta() assert readdata is None metadata = {'filename': 'tests/audio/15_Ghosts_II_64kb_orig.mp3'} - metadb.write_to_metadb(metadata=metadata) + await metadb.write_to_metadb(metadata=metadata) readdata = metadb.read_last_meta() assert readdata is None -def test_data_db1(getmetadb): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_empty_db_async(getmetadb): # pylint: disable=redefined-outer-name + ''' test writing false data ''' + metadb = getmetadb + await metadb.write_to_metadb(metadata=None) + readdata = await metadb.read_last_meta_async() + + assert readdata is None + + metadata = {'filename': 'tests/audio/15_Ghosts_II_64kb_orig.mp3'} + await metadb.write_to_metadb(metadata=metadata) + readdata = await metadb.read_last_meta_async() + assert readdata is None + + +@pytest.mark.asyncio +async def test_data_db1(getmetadb): # pylint: disable=redefined-outer-name ''' simple data test ''' metadb = getmetadb @@ -105,14 +122,15 @@ def test_data_db1(getmetadb): # pylint: disable=redefined-outer-name 'track_total': None, } - metadb.write_to_metadb(metadata=expected) + await metadb.write_to_metadb(metadata=expected) readdata = metadb.read_last_meta() expected['dbid'] = 1 results(expected, readdata) -def test_data_db2(getmetadb): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_data_db2(getmetadb): # pylint: disable=redefined-outer-name ''' more complex data test ''' metadb = getmetadb @@ -133,7 +151,7 @@ def test_data_db2(getmetadb): # pylint: disable=redefined-outer-name 'title': 'Lakini\'s Juice', } - metadb.write_to_metadb(metadata=expected) + await metadb.write_to_metadb(metadata=expected) readdata = metadb.read_last_meta() expected = { @@ -190,7 +208,8 @@ def test_data_db2(getmetadb): # pylint: disable=redefined-outer-name results(expected, readdata) -def test_data_dbid(getmetadb): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_data_dbid(getmetadb): # pylint: disable=redefined-outer-name ''' make sure dbid increments ''' metadb = getmetadb @@ -199,7 +218,7 @@ def test_data_dbid(getmetadb): # pylint: disable=redefined-outer-name 'title': '15 Ghosts II', } - metadb.write_to_metadb(metadata=expected) + await metadb.write_to_metadb(metadata=expected) readdata = metadb.read_last_meta() expected = { @@ -207,18 +226,19 @@ def test_data_dbid(getmetadb): # pylint: disable=redefined-outer-name 'title': 'Great Title Here', } - metadb.write_to_metadb(metadata=expected) + await metadb.write_to_metadb(metadata=expected) readdata = metadb.read_last_meta() assert readdata['dbid'] == 2 -def test_data_previoustrack(getmetadb): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_data_previoustrack(getmetadb): # pylint: disable=redefined-outer-name ''' test the previoustrack functionality ''' metadb = getmetadb for counter in range(4): - metadb.write_to_metadb(metadata={ + await metadb.write_to_metadb(metadata={ 'artist': f'a{counter}', 'title': f't{counter}' }) @@ -240,7 +260,8 @@ def test_empty_setlist(bootstrap): nowplaying.db.create_setlist(config) -def test_simple_setlist(config_and_getmetadb): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_simple_setlist(config_and_getmetadb): # pylint: disable=redefined-outer-name ''' test a single entry db ''' config, metadb = config_and_getmetadb config.cparser.setValue('setlist/enabled', True) @@ -250,11 +271,12 @@ def test_simple_setlist(config_and_getmetadb): # pylint: disable=redefined-oute 'title': 'Great Title Here', } - metadb.write_to_metadb(metadata=expected) + await metadb.write_to_metadb(metadata=expected) nowplaying.db.create_setlist(config, databasefile=metadb.databasefile) -def test_missingartist_setlist(config_and_getmetadb): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_missingartist_setlist(config_and_getmetadb): # pylint: disable=redefined-outer-name ''' test a single entry db ''' config, metadb = config_and_getmetadb config.cparser.setValue('setlist/enabled', True) @@ -264,5 +286,5 @@ def test_missingartist_setlist(config_and_getmetadb): # pylint: disable=redefin 'title': 'Great Title Here', } - metadb.write_to_metadb(metadata=expected) + await metadb.write_to_metadb(metadata=expected) nowplaying.db.create_setlist(config, databasefile=metadb.databasefile) diff --git a/tests/test_trackrequests.py b/tests/test_trackrequests.py index b4fde8b9..c8a58c48 100644 --- a/tests/test_trackrequests.py +++ b/tests/test_trackrequests.py @@ -4,7 +4,6 @@ import asyncio import logging import pathlib -import threading import pytest # pylint: disable=import-error import pytest_asyncio # pylint: disable=import-error @@ -16,7 +15,7 @@ @pytest_asyncio.fixture async def trackrequestbootstrap(bootstrap, getroot): # pylint: disable=redefined-outer-name ''' bootstrap a configuration ''' - stopevent = threading.Event() + stopevent = asyncio.Event() config = bootstrap config.cparser.setValue('settings/input', 'jsonreader') playlistpath = pathlib.Path(getroot).joinpath('tests', 'playlists', 'json', @@ -311,7 +310,7 @@ async def test_trackrequest_getrequest_title(trackrequestbootstrap): # pylint: @pytest.mark.asyncio async def test_twofer(bootstrap, getroot): # pylint: disable=redefined-outer-name ''' test twofers ''' - stopevent = threading.Event() + stopevent = asyncio.Event() config = bootstrap config.cparser.setValue('settings/input', 'json') playlistpath = pathlib.Path(getroot).joinpath('tests', 'playlists', 'json', @@ -336,7 +335,7 @@ async def test_twofer(bootstrap, getroot): # pylint: disable=redefined-outer-na assert not data testdata = {'artist': 'myartist', 'title': 'mytitle1'} - metadb.write_to_metadb(testdata) + await metadb.write_to_metadb(testdata) data = await trackrequest.twofer_request({ 'displayname': 'test', diff --git a/tests/test_webserver.py b/tests/test_webserver.py index 33c843f4..507706a9 100644 --- a/tests/test_webserver.py +++ b/tests/test_webserver.py @@ -48,7 +48,8 @@ def test_startstopwebserver(getwebserver): # pylint: disable=redefined-outer-na time.sleep(5) -def test_webserver_htmtest(getwebserver): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_webserver_htmtest(getwebserver): # pylint: disable=redefined-outer-name ''' start webserver, read existing data, add new data, then read that ''' config, metadb = getwebserver config.cparser.setValue( @@ -67,7 +68,7 @@ def test_webserver_htmtest(getwebserver): # pylint: disable=redefined-outer-nam # handle first write - metadb.write_to_metadb(metadata={ + await metadb.write_to_metadb(metadata={ 'title': 'testhtmtitle', 'artist': 'testhtmartist' }) @@ -95,7 +96,7 @@ def test_webserver_htmtest(getwebserver): # pylint: disable=redefined-outer-nam # handle second write - metadb.write_to_metadb(metadata={ + await metadb.write_to_metadb(metadata={ 'artist': 'artisthtm2', 'title': 'titlehtm2', }) @@ -105,7 +106,8 @@ def test_webserver_htmtest(getwebserver): # pylint: disable=redefined-outer-nam assert req.text == ' artisthtm2 - titlehtm2' -def test_webserver_txttest(getwebserver): # pylint: disable=redefined-outer-name +@pytest.mark.asyncio +async def test_webserver_txttest(getwebserver): # pylint: disable=redefined-outer-name ''' start webserver, read existing data, add new data, then read that ''' config, metadb = getwebserver config.cparser.setValue('weboutput/httpenabled', 'true') @@ -123,7 +125,7 @@ def test_webserver_txttest(getwebserver): # pylint: disable=redefined-outer-nam req = requests.get('http://localhost:8899/index.txt', timeout=5) assert req.status_code == 200 - assert req.text == '' + assert req.text == '' # sourcery skip: simplify-empty-collection-comparison # should return empty req = requests.get('http://localhost:8899/v1/last', timeout=5) @@ -131,7 +133,7 @@ def test_webserver_txttest(getwebserver): # pylint: disable=redefined-outer-nam assert req.json() == {} # handle first write - metadb.write_to_metadb(metadata={ + await metadb.write_to_metadb(metadata={ 'title': 'testtxttitle', 'artist': 'testtxtartist' }) @@ -163,7 +165,7 @@ def test_webserver_txttest(getwebserver): # pylint: disable=redefined-outer-nam # handle second write - metadb.write_to_metadb(metadata={ + await metadb.write_to_metadb(metadata={ 'artist': 'artisttxt2', 'title': 'titletxt2', })