malojaserver 3.2.2__py3-none-any.whl → 3.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. maloja/__main__.py +1 -94
  2. maloja/__pkginfo__.py +1 -1
  3. maloja/apis/_base.py +26 -19
  4. maloja/apis/_exceptions.py +1 -1
  5. maloja/apis/audioscrobbler.py +35 -7
  6. maloja/apis/audioscrobbler_legacy.py +5 -5
  7. maloja/apis/listenbrainz.py +8 -5
  8. maloja/apis/native_v1.py +43 -26
  9. maloja/cleanup.py +9 -7
  10. maloja/data_files/config/rules/predefined/krateng_kpopgirlgroups.tsv +2 -2
  11. maloja/database/__init__.py +68 -23
  12. maloja/database/associated.py +10 -6
  13. maloja/database/exceptions.py +28 -3
  14. maloja/database/sqldb.py +216 -168
  15. maloja/dev/profiler.py +3 -4
  16. maloja/images.py +6 -0
  17. maloja/malojauri.py +2 -0
  18. maloja/pkg_global/conf.py +30 -28
  19. maloja/proccontrol/tasks/export.py +2 -1
  20. maloja/proccontrol/tasks/import_scrobbles.py +110 -47
  21. maloja/server.py +11 -10
  22. maloja/setup.py +29 -17
  23. maloja/web/jinja/abstracts/base.jinja +1 -1
  24. maloja/web/jinja/admin_albumless.jinja +2 -0
  25. maloja/web/jinja/admin_issues.jinja +2 -2
  26. maloja/web/jinja/admin_overview.jinja +3 -3
  27. maloja/web/jinja/admin_setup.jinja +3 -3
  28. maloja/web/jinja/partials/album_showcase.jinja +1 -1
  29. maloja/web/jinja/snippets/entityrow.jinja +2 -2
  30. maloja/web/jinja/snippets/links.jinja +3 -1
  31. maloja/web/static/css/maloja.css +8 -2
  32. maloja/web/static/css/startpage.css +2 -2
  33. maloja/web/static/js/manualscrobble.js +2 -2
  34. maloja/web/static/js/notifications.js +16 -8
  35. maloja/web/static/js/search.js +18 -12
  36. maloja/web/static/js/upload.js +1 -1
  37. {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/METADATA +24 -72
  38. {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/RECORD +42 -42
  39. {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/WHEEL +1 -1
  40. /maloja/data_files/state/{scrobbles → import}/dummy +0 -0
  41. {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/LICENSE +0 -0
  42. {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,8 @@
1
1
  # server
2
2
  from bottle import request, response, FormsDict
3
3
 
4
+ from ..pkg_global import conf
5
+
4
6
 
5
7
  # decorator that makes sure this function is only run in normal operation,
6
8
  # not when we run a task that needs to access the database
@@ -27,7 +29,6 @@ from . import exceptions
27
29
 
28
30
  # doreah toolkit
29
31
  from doreah.logging import log
30
- from doreah.auth import authenticated_api, authenticated_api_with_alternate
31
32
  import doreah
32
33
 
33
34
 
@@ -42,6 +43,7 @@ from collections import namedtuple
42
43
  from threading import Lock
43
44
  import yaml, json
44
45
  import math
46
+ from itertools import takewhile
45
47
 
46
48
  # url handling
47
49
  import urllib
@@ -318,7 +320,7 @@ def associate_tracks_to_album(target_id,source_ids):
318
320
  if target_id:
319
321
  target = sqldb.get_album(target_id)
320
322
  log(f"Adding {sources} into {target}")
321
- sqldb.add_tracks_to_albums({src:target_id for src in source_ids})
323
+ sqldb.add_tracks_to_albums({src:target_id for src in source_ids},replace=True)
322
324
  else:
323
325
  sqldb.remove_album(source_ids)
324
326
  result = {'sources':sources,'target':target}
@@ -444,10 +446,11 @@ def get_charts_albums(dbconn=None,resolve_ids=True,only_own_albums=False,**keys)
444
446
  (since,to) = keys.get('timerange').timestamps()
445
447
 
446
448
  if 'artist' in keys:
447
- result = sqldb.count_scrobbles_by_album_combined(since=since,to=to,artist=keys['artist'],associated=keys.get('associated',False),resolve_ids=resolve_ids,dbconn=dbconn)
449
+ artist = sqldb.get_artist(sqldb.get_artist_id(keys['artist']))
450
+ result = sqldb.count_scrobbles_by_album_combined(since=since,to=to,artist=artist,associated=keys.get('associated',False),resolve_ids=resolve_ids,dbconn=dbconn)
448
451
  if only_own_albums:
449
452
  # TODO: this doesnt take associated into account and doesnt change ranks
450
- result = [e for e in result if keys['artist'] in (e['album']['artists'] or [])]
453
+ result = [e for e in result if artist in (e['album']['artists'] or [])]
451
454
  else:
452
455
  result = sqldb.count_scrobbles_by_album(since=since,to=to,resolve_ids=resolve_ids,dbconn=dbconn)
453
456
  return result
@@ -570,7 +573,7 @@ def get_performance(dbconn=None,**keys):
570
573
  return results
571
574
 
572
575
  @waitfordb
573
- def get_top_artists(dbconn=None,**keys):
576
+ def get_top_artists(dbconn=None,compatibility=True,**keys):
574
577
 
575
578
  separate = keys.get('separate')
576
579
 
@@ -578,42 +581,73 @@ def get_top_artists(dbconn=None,**keys):
578
581
  results = []
579
582
 
580
583
  for rng in rngs:
581
- try:
582
- res = get_charts_artists(timerange=rng,separate=separate,dbconn=dbconn)[0]
583
- results.append({"range":rng,"artist":res["artist"],"scrobbles":res["scrobbles"],"real_scrobbles":res["real_scrobbles"],"associated_artists":sqldb.get_associated_artists(res["artist"])})
584
- except Exception:
585
- results.append({"range":rng,"artist":None,"scrobbles":0,"real_scrobbles":0})
584
+ result = {'range':rng}
585
+ res = get_charts_artists(timerange=rng,separate=separate,dbconn=dbconn)
586
+
587
+ result['top'] = [
588
+ {'artist': r['artist'], 'scrobbles': r['scrobbles'], 'real_scrobbles':r['real_scrobbles'], 'associated_artists': sqldb.get_associated_artists(r['artist'])}
589
+ for r in takewhile(lambda x:x['rank']==1,res)
590
+ ]
591
+ # for third party applications
592
+ if compatibility:
593
+ if result['top']:
594
+ result.update(result['top'][0])
595
+ else:
596
+ result.update({'artist':None,'scrobbles':0,'real_scrobbles':0})
597
+
598
+ results.append(result)
586
599
 
587
600
  return results
588
601
 
589
602
 
590
603
  @waitfordb
591
- def get_top_tracks(dbconn=None,**keys):
604
+ def get_top_tracks(dbconn=None,compatibility=True,**keys):
592
605
 
593
606
  rngs = ranges(**{k:keys[k] for k in keys if k in ["since","to","within","timerange","step","stepn","trail"]})
594
607
  results = []
595
608
 
596
609
  for rng in rngs:
597
- try:
598
- res = get_charts_tracks(timerange=rng,dbconn=dbconn)[0]
599
- results.append({"range":rng,"track":res["track"],"scrobbles":res["scrobbles"]})
600
- except Exception:
601
- results.append({"range":rng,"track":None,"scrobbles":0})
610
+ result = {'range':rng}
611
+ res = get_charts_tracks(timerange=rng,dbconn=dbconn)
612
+
613
+ result['top'] = [
614
+ {'track': r['track'], 'scrobbles': r['scrobbles']}
615
+ for r in takewhile(lambda x:x['rank']==1,res)
616
+ ]
617
+ # for third party applications
618
+ if compatibility:
619
+ if result['top']:
620
+ result.update(result['top'][0])
621
+ else:
622
+ result.update({'track':None,'scrobbles':0})
623
+
624
+ results.append(result)
602
625
 
603
626
  return results
604
627
 
605
628
  @waitfordb
606
- def get_top_albums(dbconn=None,**keys):
629
+ def get_top_albums(dbconn=None,compatibility=True,**keys):
607
630
 
608
631
  rngs = ranges(**{k:keys[k] for k in keys if k in ["since","to","within","timerange","step","stepn","trail"]})
609
632
  results = []
610
633
 
611
634
  for rng in rngs:
612
- try:
613
- res = get_charts_albums(timerange=rng,dbconn=dbconn)[0]
614
- results.append({"range":rng,"album":res["album"],"scrobbles":res["scrobbles"]})
615
- except Exception:
616
- results.append({"range":rng,"album":None,"scrobbles":0})
635
+
636
+ result = {'range':rng}
637
+ res = get_charts_albums(timerange=rng,dbconn=dbconn)
638
+
639
+ result['top'] = [
640
+ {'album': r['album'], 'scrobbles': r['scrobbles']}
641
+ for r in takewhile(lambda x:x['rank']==1,res)
642
+ ]
643
+ # for third party applications
644
+ if compatibility:
645
+ if result['top']:
646
+ result.update(result['top'][0])
647
+ else:
648
+ result.update({'album':None,'scrobbles':0})
649
+
650
+ results.append(result)
617
651
 
618
652
  return results
619
653
 
@@ -900,6 +934,9 @@ def get_predefined_rulesets(dbconn=None):
900
934
 
901
935
 
902
936
  def start_db():
937
+
938
+ conf.AUX_MODE = True # that is, without a doubt, the worst python code you have ever seen
939
+
903
940
  # Upgrade database
904
941
  from .. import upgrade
905
942
  upgrade.upgrade_db(sqldb.add_scrobbles)
@@ -909,11 +946,19 @@ def start_db():
909
946
  from . import associated
910
947
  associated.load_associated_rules()
911
948
 
949
+ # import scrobbles
950
+ from ..proccontrol.tasks.import_scrobbles import import_scrobbles #lmao this codebase is so fucked
951
+ for f in os.listdir(data_dir['import']()):
952
+ if f != 'dummy':
953
+ import_scrobbles(data_dir['import'](f))
954
+
912
955
  dbstatus['healthy'] = True
913
956
 
957
+ conf.AUX_MODE = False # but you have seen it
958
+
914
959
  # inform time module about begin of scrobbling
915
960
  try:
916
- firstscrobble = sqldb.get_scrobbles()[0]
961
+ firstscrobble = sqldb.get_scrobbles(limit=1)[0]
917
962
  register_scrobbletime(firstscrobble['time'])
918
963
  except IndexError:
919
964
  register_scrobbletime(int(datetime.datetime.now().timestamp()))
@@ -19,12 +19,16 @@ def load_associated_rules():
19
19
 
20
20
  # load from file
21
21
  rawrules = []
22
- for f in os.listdir(data_dir["rules"]()):
23
- if f.split('.')[-1].lower() != 'tsv': continue
24
- filepath = data_dir["rules"](f)
25
- with open(filepath,'r') as filed:
26
- reader = csv.reader(filed,delimiter="\t")
27
- rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
22
+ try:
23
+ for f in os.listdir(data_dir["rules"]()):
24
+ if f.split('.')[-1].lower() != 'tsv': continue
25
+ filepath = data_dir["rules"](f)
26
+ with open(filepath,'r') as filed:
27
+ reader = csv.reader(filed,delimiter="\t")
28
+ rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
29
+ except FileNotFoundError:
30
+ return
31
+
28
32
  rules = [{'source_artist':r[1],'target_artist':r[2]} for r in rawrules if r[0]=="countas"]
29
33
 
30
34
  #for rule in rules:
@@ -1,37 +1,57 @@
1
1
  from bottle import HTTPError
2
2
 
3
+
3
4
  class EntityExists(Exception):
4
- def __init__(self,entitydict):
5
+ def __init__(self, entitydict):
5
6
  self.entitydict = entitydict
6
7
 
7
8
 
8
9
  class TrackExists(EntityExists):
9
10
  pass
10
11
 
12
+
11
13
  class ArtistExists(EntityExists):
12
14
  pass
13
15
 
16
+
14
17
  class AlbumExists(EntityExists):
15
18
  pass
16
19
 
20
+
21
+ # if the scrobbles dont match
22
+ class DuplicateTimestamp(Exception):
23
+ def __init__(self, existing_scrobble, rejected_scrobble):
24
+ self.existing_scrobble = existing_scrobble
25
+ self.rejected_scrobble = rejected_scrobble
26
+
27
+
28
+ # if it's the same scrobble
29
+ class DuplicateScrobble(Exception):
30
+ def __init__(self, scrobble):
31
+ self.scrobble = scrobble
32
+
33
+
17
34
  class DatabaseNotBuilt(HTTPError):
18
35
  def __init__(self):
19
36
  super().__init__(
20
37
  status=503,
21
38
  body="The Maloja Database is being upgraded to support new Maloja features. This could take a while.",
22
- headers={"Retry-After":120}
39
+ headers={"Retry-After": 120}
23
40
  )
24
41
 
25
42
 
26
43
  class MissingScrobbleParameters(Exception):
27
- def __init__(self,params=[]):
44
+ def __init__(self, params=[]):
28
45
  self.params = params
29
46
 
47
+
30
48
  class MissingEntityParameter(Exception):
31
49
  pass
32
50
 
51
+
33
52
  class EntityDoesNotExist(HTTPError):
34
53
  entitytype = 'Entity'
54
+
35
55
  def __init__(self,entitydict):
36
56
  self.entitydict = entitydict
37
57
  super().__init__(
@@ -39,9 +59,14 @@ class EntityDoesNotExist(HTTPError):
39
59
  body=f"The {self.entitytype} '{self.entitydict}' does not exist in the database."
40
60
  )
41
61
 
62
+
42
63
  class ArtistDoesNotExist(EntityDoesNotExist):
43
64
  entitytype = 'Artist'
65
+
66
+
44
67
  class AlbumDoesNotExist(EntityDoesNotExist):
45
68
  entitytype = 'Album'
69
+
70
+
46
71
  class TrackDoesNotExist(EntityDoesNotExist):
47
72
  entitytype = 'Track'