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
maloja/__main__.py CHANGED
@@ -26,77 +26,6 @@ def print_header_info():
26
26
  #print("#####")
27
27
  print()
28
28
 
29
-
30
-
31
- def get_instance():
32
- try:
33
- return int(subprocess.check_output(["pgrep","-f","maloja$"]))
34
- except Exception:
35
- return None
36
-
37
- def get_instance_supervisor():
38
- try:
39
- return int(subprocess.check_output(["pgrep","-f","maloja_supervisor"]))
40
- except Exception:
41
- return None
42
-
43
- def restart():
44
- if stop():
45
- start()
46
- else:
47
- print(col["red"]("Could not stop Maloja!"))
48
-
49
- def start():
50
- if get_instance_supervisor() is not None:
51
- print("Maloja is already running.")
52
- else:
53
- print_header_info()
54
- setup()
55
- try:
56
- #p = subprocess.Popen(["python3","-m","maloja.server"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
57
- sp = subprocess.Popen(["python3","-m","maloja","supervisor"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
58
- print(col["green"]("Maloja started!"))
59
-
60
- port = conf.malojaconfig["PORT"]
61
-
62
- print("Visit your server address (Port " + str(port) + ") to see your web interface. Visit /admin_setup to get started.")
63
- print("If you're installing this on your local machine, these links should get you there:")
64
- print("\t" + col["blue"]("http://localhost:" + str(port)))
65
- print("\t" + col["blue"]("http://localhost:" + str(port) + "/admin_setup"))
66
- return True
67
- except Exception:
68
- print("Error while starting Maloja.")
69
- return False
70
-
71
-
72
- def stop():
73
-
74
- for attempt in [(signal.SIGTERM,2),(signal.SIGTERM,5),(signal.SIGKILL,3),(signal.SIGKILL,5)]:
75
-
76
- pid_sv = get_instance_supervisor()
77
- pid = get_instance()
78
-
79
- if pid is None and pid_sv is None:
80
- print("Maloja stopped!")
81
- return True
82
-
83
- if pid_sv is not None:
84
- os.kill(pid_sv,attempt[0])
85
- if pid is not None:
86
- os.kill(pid,attempt[0])
87
-
88
- time.sleep(attempt[1])
89
-
90
- return False
91
-
92
-
93
-
94
-
95
-
96
-
97
- print("Maloja stopped!")
98
- return True
99
-
100
29
  def onlysetup():
101
30
  print_header_info()
102
31
  setup()
@@ -109,24 +38,6 @@ def run_server():
109
38
  from . import server
110
39
  server.run_server()
111
40
 
112
- def run_supervisor():
113
- setproctitle("maloja_supervisor")
114
- while True:
115
- log("Maloja is not running, starting...",module="supervisor")
116
- try:
117
- process = subprocess.Popen(
118
- ["python3", "-m", "maloja","run"],
119
- stdout=subprocess.DEVNULL,
120
- stderr=subprocess.DEVNULL,
121
- )
122
- except Exception as e:
123
- log("Error starting Maloja: " + str(e),module="supervisor")
124
- else:
125
- try:
126
- process.wait()
127
- except Exception as e:
128
- log("Maloja crashed: " + str(e),module="supervisor")
129
-
130
41
  def debug():
131
42
  os.environ["MALOJA_DEV_MODE"] = 'true'
132
43
  conf.malojaconfig.load_environment()
@@ -135,7 +46,7 @@ def debug():
135
46
  def print_info():
136
47
  print_header_info()
137
48
  print(col['lightblue']("Configuration Directory:"),conf.dir_settings['config'])
138
- print(col['lightblue']("Data Directory: "),conf.dir_settings['state'])
49
+ print(col['lightblue']("State Directory: "),conf.dir_settings['state'])
139
50
  print(col['lightblue']("Log Directory: "),conf.dir_settings['logs'])
140
51
  print(col['lightblue']("Network: "),f"Dual Stack, Port {conf.malojaconfig['port']}" if conf.malojaconfig['host'] == "*" else f"IPv{ip_address(conf.malojaconfig['host']).version}, Port {conf.malojaconfig['port']}")
141
52
  print(col['lightblue']("Timezone: "),f"UTC{conf.malojaconfig['timezone']:+d}")
@@ -173,11 +84,7 @@ def main(*args,**kwargs):
173
84
 
174
85
  actions = {
175
86
  # server
176
- "start":start,
177
- "restart":restart,
178
- "stop":stop,
179
87
  "run":run_server,
180
- "supervisor":run_supervisor,
181
88
  "debug":debug,
182
89
  "setup":onlysetup,
183
90
  # admin scripts
maloja/__pkginfo__.py CHANGED
@@ -4,7 +4,7 @@
4
4
  # you know what f*ck it
5
5
  # this is hardcoded for now because of that damn project / package name discrepancy
6
6
  # i'll fix it one day
7
- VERSION = "3.2.2"
7
+ VERSION = "3.2.4"
8
8
  HOMEPAGE = "https://github.com/krateng/maloja"
9
9
 
10
10
 
maloja/apis/_base.py CHANGED
@@ -25,9 +25,20 @@ __logmodulename__ = "apis"
25
25
 
26
26
  cla = CleanerAgent()
27
27
 
28
+
29
+
30
+ # wrapper method: calls handle. final net to catch exceptions and map them to the handlers proper json / xml response
31
+ # handle method: finds the method for this path / query. can only raise InvalidMethodException
32
+ # scrobble: NOT the exposed scrobble method - helper for all APIs to scrobble their results with self-identification
33
+
34
+
28
35
  class APIHandler:
36
+
37
+ __apiname__: str
38
+ errors: dict
29
39
  # make these classes singletons
30
40
  _instance = None
41
+
31
42
  def __new__(cls, *args, **kwargs):
32
43
  if not isinstance(cls._instance, cls):
33
44
  cls._instance = object.__new__(cls, *args, **kwargs)
@@ -62,37 +73,33 @@ class APIHandler:
62
73
 
63
74
  try:
64
75
  response.status,result = self.handle(path,keys)
65
- except Exception:
66
- exceptiontype = sys.exc_info()[0]
67
- if exceptiontype in self.errors:
68
- response.status,result = self.errors[exceptiontype]
69
- log(f"Error with {self.__apiname__} API: {exceptiontype} (Request: {path})")
76
+ except Exception as e:
77
+ for exc_type, exc_response in self.errors.items():
78
+ if isinstance(e, exc_type):
79
+ response.status, result = exc_response
80
+ log(f"Error with {self.__apiname__} API: {e} (Request: {path})")
81
+ break
70
82
  else:
71
- response.status,result = 500,{"status":"Unknown error","code":500}
72
- log(f"Unhandled Exception with {self.__apiname__} API: {exceptiontype} (Request: {path})")
83
+ # THIS SHOULD NOT HAPPEN
84
+ response.status, result = 500, {"status": "Unknown error", "code": 500}
85
+ log(f"Unhandled Exception with {self.__apiname__} API: {e} (Request: {path})")
73
86
 
74
87
  return result
75
- #else:
76
- # result = {"error":"Invalid scrobble protocol"}
77
- # response.status = 500
78
88
 
79
89
 
80
90
  def handle(self,path,keys):
81
91
 
82
92
  try:
83
- methodname = self.get_method(path,keys)
93
+ methodname = self.get_method(path, keys)
84
94
  method = self.methods[methodname]
85
- except Exception:
86
- log("Could not find a handler for method " + str(methodname) + " in API " + self.__apiname__,module="debug")
87
- log("Keys: " + str(keys),module="debug")
95
+ except KeyError:
96
+ log(f"Could not find a handler for method {methodname} in API {self.__apiname__}", module="debug")
97
+ log(f"Keys: {keys}", module="debug")
88
98
  raise InvalidMethodException()
89
- return method(path,keys)
99
+ return method(path, keys)
90
100
 
91
101
 
92
102
  def scrobble(self,rawscrobble,client=None):
93
103
 
94
104
  # fixing etc is handled by the main scrobble function
95
- try:
96
- return database.incoming_scrobble(rawscrobble,api=self.__apiname__,client=client)
97
- except Exception:
98
- raise ScrobblingException()
105
+ return database.incoming_scrobble(rawscrobble,api=self.__apiname__,client=client)
@@ -3,4 +3,4 @@ class InvalidAuthException(Exception): pass
3
3
  class InvalidMethodException(Exception): pass
4
4
  class InvalidSessionKey(Exception): pass
5
5
  class MalformedJSONException(Exception): pass
6
- class ScrobblingException(Exception): pass
6
+
@@ -21,13 +21,22 @@ class Audioscrobbler(APIHandler):
21
21
  "track.scrobble":self.submit_scrobble
22
22
  }
23
23
  self.errors = {
24
- BadAuthException:(400,{"error":6,"message":"Requires authentication"}),
25
- InvalidAuthException:(401,{"error":4,"message":"Invalid credentials"}),
26
- InvalidMethodException:(200,{"error":3,"message":"Invalid method"}),
27
- InvalidSessionKey:(403,{"error":9,"message":"Invalid session key"}),
28
- ScrobblingException:(500,{"error":8,"message":"Operation failed"})
24
+ BadAuthException: (400, {"error": 6, "message": "Requires authentication"}),
25
+ InvalidAuthException: (401, {"error": 4, "message": "Invalid credentials"}),
26
+ InvalidMethodException: (200, {"error": 3, "message": "Invalid method"}),
27
+ InvalidSessionKey: (403, {"error": 9, "message": "Invalid session key"}),
28
+ Exception: (500, {"error": 8, "message": "Operation failed"})
29
29
  }
30
30
 
31
+ # xml string escaping: https://stackoverflow.com/a/28703510
32
+ def xml_escape(self, str_xml: str):
33
+ str_xml = str_xml.replace("&", "&")
34
+ str_xml = str_xml.replace("<", "&lt;")
35
+ str_xml = str_xml.replace("<", "&lt;")
36
+ str_xml = str_xml.replace("\"", "&quot;")
37
+ str_xml = str_xml.replace("'", "&apos;")
38
+ return str_xml
39
+
31
40
  def get_method(self,pathnodes,keys):
32
41
  return keys.get("method")
33
42
 
@@ -45,12 +54,22 @@ class Audioscrobbler(APIHandler):
45
54
  token = keys.get("authToken")
46
55
  user = keys.get("username")
47
56
  password = keys.get("password")
57
+ format = keys.get("format") or "xml" # Audioscrobbler 2.0 uses XML by default
48
58
  # either username and password
49
59
  if user is not None and password is not None:
50
60
  client = apikeystore.check_and_identify_key(password)
51
61
  if client:
52
62
  sessionkey = self.generate_key(client)
53
- return 200,{"session":{"key":sessionkey}}
63
+ if format == "json":
64
+ return 200,{"session":{"key":sessionkey}}
65
+ else:
66
+ return 200,"""<lfm status="ok">
67
+ <session>
68
+ <name>%s</name>
69
+ <key>%s</key>
70
+ <subscriber>0</subscriber>
71
+ </session>
72
+ </lfm>""" % (self.xml_escape(user), self.xml_escape(sessionkey))
54
73
  else:
55
74
  raise InvalidAuthException()
56
75
  # or username and token (deprecated by lastfm)
@@ -59,7 +78,16 @@ class Audioscrobbler(APIHandler):
59
78
  key = apikeystore[client]
60
79
  if md5(user + md5(key)) == token:
61
80
  sessionkey = self.generate_key(client)
62
- return 200,{"session":{"key":sessionkey}}
81
+ if format == "json":
82
+ return 200,{"session":{"key":sessionkey}}
83
+ else:
84
+ return 200,"""<lfm status="ok">
85
+ <session>
86
+ <name>%s</name>
87
+ <key>%s</key>
88
+ <subscriber>0</subscriber>
89
+ </session>
90
+ </lfm>""" % (self.xml_escape(user), self.xml_escape(sessionkey))
63
91
  raise InvalidAuthException()
64
92
  else:
65
93
  raise BadAuthException()
@@ -23,11 +23,11 @@ class AudioscrobblerLegacy(APIHandler):
23
23
  "scrobble":self.submit_scrobble
24
24
  }
25
25
  self.errors = {
26
- BadAuthException:(403,"BADAUTH\n"),
27
- InvalidAuthException:(403,"BADAUTH\n"),
28
- InvalidMethodException:(400,"FAILED\n"),
29
- InvalidSessionKey:(403,"BADSESSION\n"),
30
- ScrobblingException:(500,"FAILED\n")
26
+ BadAuthException: (403, "BADAUTH\n"),
27
+ InvalidAuthException: (403, "BADAUTH\n"),
28
+ InvalidMethodException: (400, "FAILED\n"),
29
+ InvalidSessionKey: (403, "BADSESSION\n"),
30
+ Exception: (500, "FAILED\n")
31
31
  }
32
32
 
33
33
  def get_method(self,pathnodes,keys):
@@ -3,6 +3,7 @@ from ._exceptions import *
3
3
  from .. import database
4
4
  import datetime
5
5
  from ._apikeys import apikeystore
6
+ from ..database.exceptions import DuplicateScrobble, DuplicateTimestamp
6
7
 
7
8
  from ..pkg_global.conf import malojaconfig
8
9
 
@@ -21,11 +22,13 @@ class Listenbrainz(APIHandler):
21
22
  "validate-token":self.validate_token
22
23
  }
23
24
  self.errors = {
24
- BadAuthException:(401,{"code":401,"error":"You need to provide an Authorization header."}),
25
- InvalidAuthException:(401,{"code":401,"error":"Incorrect Authorization"}),
26
- InvalidMethodException:(200,{"code":200,"error":"Invalid Method"}),
27
- MalformedJSONException:(400,{"code":400,"error":"Invalid JSON document submitted."}),
28
- ScrobblingException:(500,{"code":500,"error":"Unspecified server error."})
25
+ BadAuthException: (401, {"code": 401, "error": "You need to provide an Authorization header."}),
26
+ InvalidAuthException: (401, {"code": 401, "error": "Incorrect Authorization"}),
27
+ InvalidMethodException: (200, {"code": 200, "error": "Invalid Method"}),
28
+ MalformedJSONException: (400, {"code": 400, "error": "Invalid JSON document submitted."}),
29
+ DuplicateScrobble: (200, {"status": "ok"}),
30
+ DuplicateTimestamp: (409, {"error": "Scrobble with the same timestamp already exists."}),
31
+ Exception: (500, {"code": 500, "error": "Unspecified server error."})
29
32
  }
30
33
 
31
34
  def get_method(self,pathnodes,keys):
maloja/apis/native_v1.py CHANGED
@@ -7,7 +7,6 @@ from bottle import response, static_file, FormsDict
7
7
  from inspect import signature
8
8
 
9
9
  from doreah.logging import log
10
- from doreah.auth import authenticated_function
11
10
 
12
11
  # nimrodel API
13
12
  from nimrodel import EAPI as API
@@ -15,7 +14,7 @@ from nimrodel import Multi
15
14
 
16
15
 
17
16
  from .. import database
18
- from ..pkg_global.conf import malojaconfig, data_dir
17
+ from ..pkg_global.conf import malojaconfig, data_dir, auth
19
18
 
20
19
 
21
20
 
@@ -82,6 +81,24 @@ errors = {
82
81
  'desc':"This entity does not exist in the database."
83
82
  }
84
83
  }),
84
+ database.exceptions.DuplicateTimestamp: lambda e: (409,{
85
+ "status":"error",
86
+ "error":{
87
+ 'type':'duplicate_timestamp',
88
+ 'value':e.rejected_scrobble,
89
+ 'desc':"A scrobble is already registered with this timestamp."
90
+ }
91
+ }),
92
+ database.exceptions.DuplicateScrobble: lambda e: (200,{
93
+ "status": "success",
94
+ "desc": "The scrobble is present in the database.",
95
+ "track": {},
96
+ "warnings": [{
97
+ 'type': 'scrobble_exists',
98
+ 'value': None,
99
+ 'desc': 'This scrobble exists in the database (same timestamp and track). The submitted scrobble was not added.'
100
+ }]
101
+ }),
85
102
  images.MalformedB64: lambda e: (400,{
86
103
  "status":"failure",
87
104
  "error":{
@@ -474,7 +491,7 @@ def get_top_artists_external(k_filter, k_limit, k_delimit, k_amount):
474
491
  :rtype: Dictionary"""
475
492
 
476
493
  ckeys = {**k_limit, **k_delimit}
477
- results = database.get_top_artists(**ckeys)
494
+ results = database.get_top_artists(**ckeys,compatibility=True)
478
495
 
479
496
  return {
480
497
  "status":"ok",
@@ -493,7 +510,7 @@ def get_top_tracks_external(k_filter, k_limit, k_delimit, k_amount):
493
510
  :rtype: Dictionary"""
494
511
 
495
512
  ckeys = {**k_limit, **k_delimit}
496
- results = database.get_top_tracks(**ckeys)
513
+ results = database.get_top_tracks(**ckeys,compatibility=True)
497
514
  # IMPLEMENT THIS FOR TOP TRACKS OF ARTIST/ALBUM AS WELL?
498
515
 
499
516
  return {
@@ -513,7 +530,7 @@ def get_top_albums_external(k_filter, k_limit, k_delimit, k_amount):
513
530
  :rtype: Dictionary"""
514
531
 
515
532
  ckeys = {**k_limit, **k_delimit}
516
- results = database.get_top_albums(**ckeys)
533
+ results = database.get_top_albums(**ckeys,compatibility=True)
517
534
  # IMPLEMENT THIS FOR TOP ALBUMS OF ARTIST AS WELL?
518
535
 
519
536
  return {
@@ -567,7 +584,7 @@ def album_info_external(k_filter, k_limit, k_delimit, k_amount):
567
584
 
568
585
 
569
586
  @api.post("newscrobble")
570
- @authenticated_function(alternate=api_key_correct,api=True,pass_auth_result_as='auth_result')
587
+ @auth.authenticated_function(alternate=api_key_correct,api=True,pass_auth_result_as='auth_result')
571
588
  @catch_exceptions
572
589
  def post_scrobble(
573
590
  artist:Multi=None,
@@ -647,7 +664,7 @@ def post_scrobble(
647
664
 
648
665
 
649
666
  @api.post("addpicture")
650
- @authenticated_function(alternate=api_key_correct,api=True)
667
+ @auth.authenticated_function(alternate=api_key_correct,api=True)
651
668
  @catch_exceptions
652
669
  @convert_kwargs
653
670
  def add_picture(k_filter, k_limit, k_delimit, k_amount, k_special):
@@ -670,7 +687,7 @@ def add_picture(k_filter, k_limit, k_delimit, k_amount, k_special):
670
687
 
671
688
 
672
689
  @api.post("importrules")
673
- @authenticated_function(api=True)
690
+ @auth.authenticated_function(api=True)
674
691
  @catch_exceptions
675
692
  def import_rulemodule(**keys):
676
693
  """Internal Use Only"""
@@ -689,7 +706,7 @@ def import_rulemodule(**keys):
689
706
 
690
707
 
691
708
  @api.post("rebuild")
692
- @authenticated_function(api=True)
709
+ @auth.authenticated_function(api=True)
693
710
  @catch_exceptions
694
711
  def rebuild(**keys):
695
712
  """Internal Use Only"""
@@ -765,7 +782,7 @@ def search(**keys):
765
782
 
766
783
 
767
784
  @api.post("newrule")
768
- @authenticated_function(api=True)
785
+ @auth.authenticated_function(api=True)
769
786
  @catch_exceptions
770
787
  def newrule(**keys):
771
788
  """Internal Use Only"""
@@ -776,21 +793,21 @@ def newrule(**keys):
776
793
 
777
794
 
778
795
  @api.post("settings")
779
- @authenticated_function(api=True)
796
+ @auth.authenticated_function(api=True)
780
797
  @catch_exceptions
781
798
  def set_settings(**keys):
782
799
  """Internal Use Only"""
783
800
  malojaconfig.update(keys)
784
801
 
785
802
  @api.post("apikeys")
786
- @authenticated_function(api=True)
803
+ @auth.authenticated_function(api=True)
787
804
  @catch_exceptions
788
805
  def set_apikeys(**keys):
789
806
  """Internal Use Only"""
790
807
  apikeystore.update(keys)
791
808
 
792
809
  @api.post("import")
793
- @authenticated_function(api=True)
810
+ @auth.authenticated_function(api=True)
794
811
  @catch_exceptions
795
812
  def import_scrobbles(identifier):
796
813
  """Internal Use Only"""
@@ -798,7 +815,7 @@ def import_scrobbles(identifier):
798
815
  import_scrobbles(identifier)
799
816
 
800
817
  @api.get("backup")
801
- @authenticated_function(api=True)
818
+ @auth.authenticated_function(api=True)
802
819
  @catch_exceptions
803
820
  def get_backup(**keys):
804
821
  """Internal Use Only"""
@@ -811,7 +828,7 @@ def get_backup(**keys):
811
828
  return static_file(os.path.basename(archivefile),root=tmpfolder)
812
829
 
813
830
  @api.get("export")
814
- @authenticated_function(api=True)
831
+ @auth.authenticated_function(api=True)
815
832
  @catch_exceptions
816
833
  def get_export(**keys):
817
834
  """Internal Use Only"""
@@ -825,7 +842,7 @@ def get_export(**keys):
825
842
 
826
843
 
827
844
  @api.post("delete_scrobble")
828
- @authenticated_function(api=True)
845
+ @auth.authenticated_function(api=True)
829
846
  @catch_exceptions
830
847
  def delete_scrobble(timestamp):
831
848
  """Internal Use Only"""
@@ -837,7 +854,7 @@ def delete_scrobble(timestamp):
837
854
 
838
855
 
839
856
  @api.post("edit_artist")
840
- @authenticated_function(api=True)
857
+ @auth.authenticated_function(api=True)
841
858
  @catch_exceptions
842
859
  def edit_artist(id,name):
843
860
  """Internal Use Only"""
@@ -847,7 +864,7 @@ def edit_artist(id,name):
847
864
  }
848
865
 
849
866
  @api.post("edit_track")
850
- @authenticated_function(api=True)
867
+ @auth.authenticated_function(api=True)
851
868
  @catch_exceptions
852
869
  def edit_track(id,title):
853
870
  """Internal Use Only"""
@@ -857,7 +874,7 @@ def edit_track(id,title):
857
874
  }
858
875
 
859
876
  @api.post("edit_album")
860
- @authenticated_function(api=True)
877
+ @auth.authenticated_function(api=True)
861
878
  @catch_exceptions
862
879
  def edit_album(id,albumtitle):
863
880
  """Internal Use Only"""
@@ -868,7 +885,7 @@ def edit_album(id,albumtitle):
868
885
 
869
886
 
870
887
  @api.post("merge_tracks")
871
- @authenticated_function(api=True)
888
+ @auth.authenticated_function(api=True)
872
889
  @catch_exceptions
873
890
  def merge_tracks(target_id,source_ids):
874
891
  """Internal Use Only"""
@@ -879,7 +896,7 @@ def merge_tracks(target_id,source_ids):
879
896
  }
880
897
 
881
898
  @api.post("merge_artists")
882
- @authenticated_function(api=True)
899
+ @auth.authenticated_function(api=True)
883
900
  @catch_exceptions
884
901
  def merge_artists(target_id,source_ids):
885
902
  """Internal Use Only"""
@@ -890,7 +907,7 @@ def merge_artists(target_id,source_ids):
890
907
  }
891
908
 
892
909
  @api.post("merge_albums")
893
- @authenticated_function(api=True)
910
+ @auth.authenticated_function(api=True)
894
911
  @catch_exceptions
895
912
  def merge_artists(target_id,source_ids):
896
913
  """Internal Use Only"""
@@ -901,7 +918,7 @@ def merge_artists(target_id,source_ids):
901
918
  }
902
919
 
903
920
  @api.post("associate_albums_to_artist")
904
- @authenticated_function(api=True)
921
+ @auth.authenticated_function(api=True)
905
922
  @catch_exceptions
906
923
  def associate_albums_to_artist(target_id,source_ids,remove=False):
907
924
  result = database.associate_albums_to_artist(target_id,source_ids,remove=remove)
@@ -913,7 +930,7 @@ def associate_albums_to_artist(target_id,source_ids,remove=False):
913
930
  }
914
931
 
915
932
  @api.post("associate_tracks_to_artist")
916
- @authenticated_function(api=True)
933
+ @auth.authenticated_function(api=True)
917
934
  @catch_exceptions
918
935
  def associate_tracks_to_artist(target_id,source_ids,remove=False):
919
936
  result = database.associate_tracks_to_artist(target_id,source_ids,remove=remove)
@@ -925,7 +942,7 @@ def associate_tracks_to_artist(target_id,source_ids,remove=False):
925
942
  }
926
943
 
927
944
  @api.post("associate_tracks_to_album")
928
- @authenticated_function(api=True)
945
+ @auth.authenticated_function(api=True)
929
946
  @catch_exceptions
930
947
  def associate_tracks_to_album(target_id,source_ids):
931
948
  result = database.associate_tracks_to_album(target_id,source_ids)
@@ -937,7 +954,7 @@ def associate_tracks_to_album(target_id,source_ids):
937
954
 
938
955
 
939
956
  @api.post("reparse_scrobble")
940
- @authenticated_function(api=True)
957
+ @auth.authenticated_function(api=True)
941
958
  @catch_exceptions
942
959
  def reparse_scrobble(timestamp):
943
960
  """Internal Use Only"""
maloja/cleanup.py CHANGED
@@ -15,13 +15,15 @@ class CleanerAgent:
15
15
  def updateRules(self):
16
16
 
17
17
  rawrules = []
18
- for f in os.listdir(data_dir["rules"]()):
19
- if f.split('.')[-1].lower() != 'tsv': continue
20
- filepath = data_dir["rules"](f)
21
- with open(filepath,'r') as filed:
22
- reader = csv.reader(filed,delimiter="\t")
23
- rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
24
-
18
+ try:
19
+ for f in os.listdir(data_dir["rules"]()):
20
+ if f.split('.')[-1].lower() != 'tsv': continue
21
+ filepath = data_dir["rules"](f)
22
+ with open(filepath,'r') as filed:
23
+ reader = csv.reader(filed,delimiter="\t")
24
+ rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
25
+ except FileNotFoundError:
26
+ pass
25
27
 
26
28
  self.rules_belongtogether = [r[1] for r in rawrules if r[0]=="belongtogether"]
27
29
  self.rules_notanartist = [r[1] for r in rawrules if r[0]=="notanartist"]
@@ -160,8 +160,8 @@ replaceartist 여자친구 GFriend GFriend
160
160
  # Girl's Generation
161
161
  replaceartist 소녀시대 Girls' Generation
162
162
  replaceartist SNSD Girls' Generation
163
- replaceartist Girls' Generation-TTS TaeTiSeo
164
- countas TaeTiSeo Girls' Generation
163
+ replaceartist Girls' Generation-TTS TaeTiSeo
164
+ countas TaeTiSeo Girls' Generation
165
165
 
166
166
  # Apink
167
167
  replaceartist A Pink Apink