Skip to content

Commit ce26b1e

Browse files
committed
pdbprovider: improve error handling for partially broken dbs
1 parent 29a89df commit ce26b1e

4 files changed

Lines changed: 65 additions & 60 deletions

File tree

prodj/data/dataprovider.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@
66
from .datastore import DataStore
77
from .dbclient import DBClient
88
from .pdbprovider import PDBProvider
9-
10-
class TemporaryQueryError(Exception):
11-
pass
12-
13-
class FatalQueryError(Exception):
14-
pass
9+
from .exceptions import TemporaryQueryError, FatalQueryError
1510

1611
class DataProvider(Thread):
1712
def __init__(self, prodj):

prodj/data/dbclient.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from prodj.network import packets
77
from prodj.data import dataprovider
8+
from prodj.data.exceptions import FatalQueryError, TemporaryQueryError
89
from prodj.pdblib.usbanlz import AnlzTag
910

1011
metadata_type = {
@@ -249,7 +250,7 @@ def receive_dbmessage(self, sock):
249250
except (StreamError, RangeError, TypeError) as e:
250251
logging.debug("Received %d bytes but parsing failed, trying to receive more", len(data))
251252
parse_errors += 1
252-
raise dataprovider.TemporaryQueryError("Failed to receive dbmessage after {} tries and {} timeouts".format(parse_errors, receive_timeouts))
253+
raise TemporaryQueryError("Failed to receive dbmessage after {} tries and {} timeouts".format(parse_errors, receive_timeouts))
253254

254255
def query_list(self, player_number, slot, sort_mode, id_list, request_type):
255256
sock = self.getSocket(player_number)
@@ -340,7 +341,7 @@ def query_list(self, player_number, slot, sort_mode, id_list, request_type):
340341
else:
341342
break
342343
if parse_errors >= self.parse_error_count or receive_timeouts >= self.receive_timeout_count:
343-
raise dataprovider.FatalQueryError("Failed to receive {} render reply after {} timeouts, {} parse errors".format(request_type, receive_timeouts, parse_errors))
344+
raise FatalQueryError("Failed to receive {} render reply after {} timeouts, {} parse errors".format(request_type, receive_timeouts, parse_errors))
344345

345346
# basically, parse_metadata returns a single dict whereas parse_list returns a list of dicts
346347
if request_type in ["metadata_request", "mount_info_request", "track_info_request"]:
@@ -395,7 +396,7 @@ def get_server_port(self, player_number):
395396
if player_number not in self.remote_ports:
396397
client = self.prodj.cl.getClient(player_number)
397398
if client is None:
398-
raise dataprovider.TemporaryQueryError("failed to get remote port, player {} unknown".format(player_number))
399+
raise TemporaryQueryError("failed to get remote port, player {} unknown".format(player_number))
399400
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
400401
sock.connect((client.ip_addr, packets.DBServerQueryPort))
401402
sock.send(packets.DBServerQuery.build({}))
@@ -425,7 +426,7 @@ def send_setup_packet(self, sock, player_number):
425426
sock.send(packets.DBMessage.build(query))
426427
data = sockrcv(sock, 48)
427428
if len(data) == 0:
428-
raise dataprovider.TemporaryQueryError("Failed to connect to player {}".format(player_number))
429+
raise TemporaryQueryError("Failed to connect to player {}".format(player_number))
429430
reply = packets.DBMessage.parse(data)
430431
logging.info("connected to player {}".format(reply["args"][1]["value"]))
431432

@@ -479,19 +480,19 @@ def socksnd(self, sock, data):
479480
except BrokenPipeError as e:
480481
player_number = next((n for n, d in self.socks.items() if d[0] == sock), None)
481482
if player_number is None:
482-
raise dataprovider.FatalQueryError("socksnd failed with unknown sock")
483+
raise FatalQueryError("socksnd failed with unknown sock")
483484
else:
484485
self.closeSocket(player_number)
485-
raise dataprovider.TemporaryQueryError("Connection to player {} lost".format(player_number))
486+
raise TemporaryQueryError("Connection to player {} lost".format(player_number))
486487

487488
def ensure_request_possible(self, request, player_number):
488489
client = self.prodj.cl.getClient(player_number)
489490
if client is None:
490-
raise dataprovider.TemporaryQueryError("player {} not found in clientlist".format(player_number))
491+
raise TemporaryQueryError("player {} not found in clientlist".format(player_number))
491492
critical_requests = ["metadata_request", "artwork_request", "preview_waveform_request", "beatgrid_request", "waveform_request"]
492493
critical_play_states = ["no_track", "loading_track", "cannot_play_track", "emergency"]
493494
if request in critical_requests and client.play_state in critical_play_states:
494-
raise dataprovider.TemporaryQueryError("DataProvider: delaying %s request due to play state: %s".format(request, client.play_state))
495+
raise TemporaryQueryError("DataProvider: delaying %s request due to play state: %s".format(request, client.play_state))
495496

496497
def handle_request(self, request, params):
497498
self.ensure_request_possible(request, params[0])
@@ -544,10 +545,10 @@ def handle_request(self, request, params):
544545
try: # pre-parse beatgrid data (like metadata) for easier access
545546
return packets.Beatgrid.parse(reply)["beats"]
546547
except (RangeError, FieldError) as e:
547-
raise dataprovider.FatalQueryError("failed to parse beatgrid data: {}".format(e))
548+
raise FatalQueryError("failed to parse beatgrid data: {}".format(e))
548549
elif request == "mount_info":
549550
return self.query_list(*params[:2], None, [params[2]], "mount_info_request")
550551
elif request == "track_info":
551552
return self.query_list(*params[:2], None, [params[2]], "track_info_request")
552553
else:
553-
raise dataprovider.FatalQueryError("invalid request type {}".format(request))
554+
raise FatalQueryError("invalid request type {}".format(request))

prodj/data/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class TemporaryQueryError(Exception):
2+
pass
3+
4+
class FatalQueryError(Exception):
5+
pass

prodj/data/pdbprovider.py

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import logging
22
import os
33

4-
from . import dataprovider
5-
from .datastore import DataStore
4+
from prodj.data.exceptions import FatalQueryError
5+
from prodj.data.datastore import DataStore
66
from prodj.pdblib.pdbdatabase import PDBDatabase
77
from prodj.pdblib.usbanlzdatabase import UsbAnlzDatabase
88
from prodj.network.rpcreceiver import ReceiveTimeout
@@ -16,6 +16,15 @@ def __init__(self, reason):
1616
def __str__(self):
1717
return self.reason
1818

19+
def wrap_get_name_from_db(call, id):
20+
if id == 0:
21+
return ""
22+
try:
23+
return call(id).name
24+
except KeyError as e:
25+
logging.warning(f'Broken database: {e}')
26+
return "?"
27+
1928
class PDBProvider:
2029
def __init__(self, prodj):
2130
self.prodj = prodj
@@ -39,7 +48,7 @@ def delete_pdb(self, filename):
3948
def download_pdb(self, player_number, slot):
4049
player = self.prodj.cl.getClient(player_number)
4150
if player is None:
42-
raise dataprovider.FatalQueryError("player {} not found in clientlist".format(player_number))
51+
raise FatalQueryError("player {} not found in clientlist".format(player_number))
4352
filename = "databases/player-{}-{}.pdb".format(player_number, slot)
4453
self.delete_pdb(filename)
4554
try:
@@ -49,7 +58,7 @@ def download_pdb(self, player_number, slot):
4958
logging.debug("default pdb path not found on player %d, trying MacOS path", player_number)
5059
self.prodj.nfs.enqueue_download(player.ip_addr, slot, "/.PIONEER/rekordbox/export.pdb", filename, sync=True)
5160
except (RuntimeError, ReceiveTimeout) as e:
52-
raise dataprovider.FatalQueryError("database download from player {} failed: {}".format(player_number, e))
61+
raise FatalQueryError("database download from player {} failed: {}".format(player_number, e))
5362
return filename
5463

5564
def download_and_parse_pdb(self, player_number, slot):
@@ -58,27 +67,27 @@ def download_and_parse_pdb(self, player_number, slot):
5867
try:
5968
db.load_file(filename)
6069
except RuntimeError as e:
61-
raise dataprovider.FatalQueryError("PDBProvider: failed to parse \"{}\": {}".format(filename, e))
70+
raise FatalQueryError("PDBProvider: failed to parse \"{}\": {}".format(filename, e))
6271
return db
6372

6473
def get_db(self, player_number, slot):
6574
if (player_number, slot) not in self.dbs:
6675
try:
6776
db = self.download_and_parse_pdb(player_number, slot)
68-
except dataprovider.FatalQueryError as e:
77+
except FatalQueryError as e:
6978
db = InvalidPDBDatabase(str(e))
7079
finally:
7180
self.dbs[player_number, slot] = db
7281
else:
7382
db = self.dbs[player_number, slot]
7483
if isinstance(db, InvalidPDBDatabase):
75-
raise dataprovider.FatalQueryError(f'PDB database not available: {db}')
84+
raise FatalQueryError(f'PDB database not available: {db}')
7685
return db
7786

7887
def download_and_parse_usbanlz(self, player_number, slot, anlz_path):
7988
player = self.prodj.cl.getClient(player_number)
8089
if player is None:
81-
raise dataprovider.FatalQueryError("player {} not found in clientlist".format(player_number))
90+
raise FatalQueryError("player {} not found in clientlist".format(player_number))
8291
dat = self.prodj.nfs.enqueue_buffer_download(player.ip_addr, slot, anlz_path)
8392
ext = self.prodj.nfs.enqueue_buffer_download(player.ip_addr, slot, anlz_path.replace("DAT", "EXT"))
8493
db = UsbAnlzDatabase()
@@ -99,16 +108,15 @@ def get_anlz(self, player_number, slot, track_id):
99108
def get_metadata(self, player_number, slot, track_id):
100109
db = self.get_db(player_number, slot)
101110
track = db.get_track(track_id)
102-
artist = db.get_artist(track.artist_id).name if track.artist_id > 0 else ""
103-
album = db.get_album(track.album_id).name if track.album_id > 0 else ""
104-
key = db.get_key(track.key_id).name if track.key_id > 0 else ""
105-
genre = db.get_genre(track.genre_id).name if track.genre_id > 0 else ""
106-
color_name = colors[track.color_id] if track.color_id > 0 else ""
107-
if track.color_id > 0:
108-
color = db.get_color(track.color_id)
109-
color_text = color.name
110-
else:
111-
color_text = ""
111+
artist = wrap_get_name_from_db(db.get_artist, track.artist_id)
112+
album = wrap_get_name_from_db(db.get_album, track.album_id)
113+
key = wrap_get_name_from_db(db.get_key, track.key_id)
114+
genre = wrap_get_name_from_db(db.get_genre, track.genre_id)
115+
color_text = wrap_get_name_from_db(db.get_color, track.color_id)
116+
117+
color_name = ""
118+
if track.color_id in range(1, len(colors)):
119+
color_name = colors[track.color_id]
112120

113121
metadata = {
114122
"track_id": track.id,
@@ -135,7 +143,7 @@ def get_metadata(self, player_number, slot, track_id):
135143
def get_artwork(self, player_number, slot, artwork_id):
136144
player = self.prodj.cl.getClient(player_number)
137145
if player is None:
138-
raise dataprovider.FatalQueryError("player {} not found in clientlist".format(player_number))
146+
raise FatalQueryError("player {} not found in clientlist".format(player_number))
139147
db = self.get_db(player_number, slot)
140148
try:
141149
artwork = db.get_artwork(artwork_id)
@@ -222,30 +230,26 @@ def convert_and_sort_track_list(self, db, track_list, sort_mode):
222230
else:
223231
col2_name = sort_mode
224232
for track in track_list:
225-
try:
226-
if col2_name in ["title", "artist"]:
227-
col2_item = db.get_artist(track.artist_id).name if track.artist_id > 0 else ""
228-
elif col2_name == "album":
229-
col2_item = db.get_album(track.album_id).name if track.album_id > 0 else ""
230-
elif col2_name == "genre":
231-
col2_item = db.get_genre(track.genre_id).name if track.genre_id > 0 else ""
232-
elif col2_name == "label":
233-
col2_item = db.get_label(track.label_id).name if track.label_id > 0 else ""
234-
elif col2_name == "original_artist":
235-
col2_item = db.get_artist(track.original_artist_id).name if track.original_artist_id > 0 else ""
236-
elif col2_name == "remixer":
237-
col2_item = db.get_artist(track.remixer_id).name if track.remixer_id > 0 else ""
238-
elif col2_name == "key":
239-
col2_item = db.get_key(track.key_id).name if track.key_id > 0 else ""
240-
elif col2_name == "bpm":
241-
col2_item = track.bpm_100/100
242-
elif col2_name in ["rating", "comment", "duration", "bitrate", "play_count"]: # 1:1 mappings
243-
col2_item = track[col2_name]
244-
else:
245-
raise dataprovider.FatalQueryError("unknown sort mode {}".format(sort_mode))
246-
except KeyError as e:
247-
logging.warning(f'Broken database: {e}')
248-
col2_item = "?"
233+
if col2_name in ["title", "artist"]:
234+
col2_item = wrap_get_name_from_db(db.get_artist, track.artist_id)
235+
elif col2_name == "album":
236+
col2_item = wrap_get_name_from_db(db.get_album, track.album_id)
237+
elif col2_name == "genre":
238+
col2_item = wrap_get_name_from_db(db.get_genre, track.genre_id)
239+
elif col2_name == "label":
240+
col2_item = wrap_get_name_from_db(db.get_label, track.label_id)
241+
elif col2_name == "original_artist":
242+
col2_item = wrap_get_name_from_db(db.get_artist, track.original_artist_id)
243+
elif col2_name == "remixer":
244+
col2_item = wrap_get_name_from_db(db.get_artist, track.remixer_id)
245+
elif col2_name == "key":
246+
col2_item = wrap_get_name_from_db(db.get_key, track.key_id)
247+
elif col2_name == "bpm":
248+
col2_item = track.bpm_100/100
249+
elif col2_name in ["rating", "comment", "duration", "bitrate", "play_count"]: # 1:1 mappings
250+
col2_item = track[col2_name]
251+
else:
252+
raise FatalQueryError("unknown sort mode {}".format(sort_mode))
249253
converted += [{
250254
"title": track.title,
251255
col2_name: col2_item,
@@ -399,4 +403,4 @@ def handle_request(self, request, params):
399403
elif request == "mount_info":
400404
return self.get_mount_info(*params)
401405
else:
402-
raise dataprovider.FatalQueryError("invalid request type {}".format(request))
406+
raise FatalQueryError("invalid request type {}".format(request))

0 commit comments

Comments
 (0)