malojaserver 3.2.3__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.
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()
@@ -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.3"
7
+ VERSION = "3.2.4"
8
8
  HOMEPAGE = "https://github.com/krateng/maloja"
9
9
 
10
10
 
@@ -3,7 +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
6
+ from ..database.exceptions import DuplicateScrobble, DuplicateTimestamp
7
7
 
8
8
  from ..pkg_global.conf import malojaconfig
9
9
 
@@ -27,6 +27,7 @@ class Listenbrainz(APIHandler):
27
27
  InvalidMethodException: (200, {"code": 200, "error": "Invalid Method"}),
28
28
  MalformedJSONException: (400, {"code": 400, "error": "Invalid JSON document submitted."}),
29
29
  DuplicateScrobble: (200, {"status": "ok"}),
30
+ DuplicateTimestamp: (409, {"error": "Scrobble with the same timestamp already exists."}),
30
31
  Exception: (500, {"code": 500, "error": "Unspecified server error."})
31
32
  }
32
33
 
@@ -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
@@ -932,6 +934,9 @@ def get_predefined_rulesets(dbconn=None):
932
934
 
933
935
 
934
936
  def start_db():
937
+
938
+ conf.AUX_MODE = True # that is, without a doubt, the worst python code you have ever seen
939
+
935
940
  # Upgrade database
936
941
  from .. import upgrade
937
942
  upgrade.upgrade_db(sqldb.add_scrobbles)
@@ -941,8 +946,16 @@ def start_db():
941
946
  from . import associated
942
947
  associated.load_associated_rules()
943
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
+
944
955
  dbstatus['healthy'] = True
945
956
 
957
+ conf.AUX_MODE = False # but you have seen it
958
+
946
959
  # inform time module about begin of scrobbling
947
960
  try:
948
961
  firstscrobble = sqldb.get_scrobbles(limit=1)[0]
maloja/pkg_global/conf.py CHANGED
@@ -298,6 +298,7 @@ data_directories = {
298
298
  "auth":pthj(dir_settings['state'],"auth"),
299
299
  "backups":pthj(dir_settings['state'],"backups"),
300
300
  "images":pthj(dir_settings['state'],"images"),
301
+ "import":pthj(dir_settings['state'],"import"),
301
302
  "scrobbles":pthj(dir_settings['state']),
302
303
  "rules":pthj(dir_settings['config'],"rules"),
303
304
  "clients":pthj(dir_settings['config']),
@@ -34,9 +34,12 @@ def import_scrobbles(inputf):
34
34
  filename = os.path.basename(inputf)
35
35
  importfunc = None
36
36
 
37
+ if re.match(r"recenttracks-.*\.csv", filename):
38
+ typeid, typedesc = "lastfm", "Last.fm (ghan CSV)"
39
+ importfunc = parse_lastfm_ghan_csv
37
40
 
38
- if re.match(r".*\.csv", filename):
39
- typeid,typedesc = "lastfm", "Last.fm (benjaminbenben export)"
41
+ elif re.match(r".*\.csv", filename):
42
+ typeid,typedesc = "lastfm", "Last.fm (benjaminbenben CSV)"
40
43
  importfunc = parse_lastfm
41
44
 
42
45
  elif re.match(r"Streaming_History_Audio.+\.json", filename):
@@ -65,8 +68,8 @@ def import_scrobbles(inputf):
65
68
  importfunc = parse_rockbox
66
69
 
67
70
  elif re.match(r"recenttracks-.*\.json", filename):
68
- typeid, typedesc = "lastfm", "Last.fm (ghan export)"
69
- importfunc = parse_lastfm_ghan
71
+ typeid, typedesc = "lastfm", "Last.fm (ghan JSON)"
72
+ importfunc = parse_lastfm_ghan_json
70
73
 
71
74
  elif re.match(r".*\.json",filename):
72
75
  try:
@@ -83,8 +86,8 @@ def import_scrobbles(inputf):
83
86
  return result
84
87
 
85
88
 
86
- print(f"Parsing {col['yellow'](inputf)} as {col['cyan'](typedesc)} export")
87
- print("This could take a while...")
89
+ print(f"Parsing {col['yellow'](inputf)} as {col['cyan'](typedesc)} export.")
90
+ print(col['red']("Please double-check if this is correct - if the import fails, the file might have been interpreted as the wrong type."))
88
91
 
89
92
  timestamps = set()
90
93
  scrobblebuffer = []
@@ -154,21 +157,22 @@ def parse_spotify_lite_legacy(inputf):
154
157
  inputf = pth.abspath(inputf)
155
158
  inputfolder = pth.dirname(inputf)
156
159
  filenames = re.compile(r'StreamingHistory[0-9]+\.json')
157
- inputfiles = [os.path.join(inputfolder,f) for f in os.listdir(inputfolder) if filenames.match(f)]
160
+ #inputfiles = [os.path.join(inputfolder,f) for f in os.listdir(inputfolder) if filenames.match(f)]
161
+ inputfiles = [inputf]
158
162
 
159
- if len(inputfiles) == 0:
160
- print("No files found!")
161
- return
163
+ #if len(inputfiles) == 0:
164
+ # print("No files found!")
165
+ # return
162
166
 
163
- if inputfiles != [inputf]:
164
- print("Spotify files should all be imported together to identify duplicates across the whole dataset.")
165
- if not ask("Import " + ", ".join(col['yellow'](pth.basename(i)) for i in inputfiles) + "?",default=True):
166
- inputfiles = [inputf]
167
- print("Only importing", col['yellow'](pth.basename(inputf)))
167
+ #if inputfiles != [inputf]:
168
+ # print("Spotify files should all be imported together to identify duplicates across the whole dataset.")
169
+ # if not ask("Import " + ", ".join(col['yellow'](pth.basename(i)) for i in inputfiles) + "?",default=True):
170
+ # inputfiles = [inputf]
171
+ # print("Only importing", col['yellow'](pth.basename(inputf)))
168
172
 
169
173
  for inputf in inputfiles:
170
174
 
171
- print("Importing",col['yellow'](inputf),"...")
175
+ #print("Importing",col['yellow'](inputf),"...")
172
176
  with open(inputf,'r') as inputfd:
173
177
  data = json.load(inputfd)
174
178
 
@@ -207,21 +211,22 @@ def parse_spotify_lite(inputf):
207
211
  inputf = pth.abspath(inputf)
208
212
  inputfolder = pth.dirname(inputf)
209
213
  filenames = re.compile(r'Streaming_History_Audio.+\.json')
210
- inputfiles = [os.path.join(inputfolder,f) for f in os.listdir(inputfolder) if filenames.match(f)]
214
+ #inputfiles = [os.path.join(inputfolder,f) for f in os.listdir(inputfolder) if filenames.match(f)]
215
+ inputfiles = [inputf]
211
216
 
212
- if len(inputfiles) == 0:
213
- print("No files found!")
214
- return
217
+ #if len(inputfiles) == 0:
218
+ # print("No files found!")
219
+ # return
215
220
 
216
- if inputfiles != [inputf]:
217
- print("Spotify files should all be imported together to identify duplicates across the whole dataset.")
218
- if not ask("Import " + ", ".join(col['yellow'](pth.basename(i)) for i in inputfiles) + "?",default=True):
219
- inputfiles = [inputf]
220
- print("Only importing", col['yellow'](pth.basename(inputf)))
221
+ #if inputfiles != [inputf]:
222
+ # print("Spotify files should all be imported together to identify duplicates across the whole dataset.")
223
+ # if not ask("Import " + ", ".join(col['yellow'](pth.basename(i)) for i in inputfiles) + "?",default=True):
224
+ # inputfiles = [inputf]
225
+ # print("Only importing", col['yellow'](pth.basename(inputf)))
221
226
 
222
227
  for inputf in inputfiles:
223
228
 
224
- print("Importing",col['yellow'](inputf),"...")
229
+ #print("Importing",col['yellow'](inputf),"...")
225
230
  with open(inputf,'r') as inputfd:
226
231
  data = json.load(inputfd)
227
232
 
@@ -267,17 +272,18 @@ def parse_spotify(inputf):
267
272
  inputf = pth.abspath(inputf)
268
273
  inputfolder = pth.dirname(inputf)
269
274
  filenames = re.compile(r'endsong_[0-9]+\.json')
270
- inputfiles = [os.path.join(inputfolder,f) for f in os.listdir(inputfolder) if filenames.match(f)]
275
+ #inputfiles = [os.path.join(inputfolder,f) for f in os.listdir(inputfolder) if filenames.match(f)]
276
+ inputfiles = [inputf]
271
277
 
272
- if len(inputfiles) == 0:
273
- print("No files found!")
274
- return
278
+ #if len(inputfiles) == 0:
279
+ # print("No files found!")
280
+ # return
275
281
 
276
- if inputfiles != [inputf]:
277
- print("Spotify files should all be imported together to identify duplicates across the whole dataset.")
278
- if not ask("Import " + ", ".join(col['yellow'](pth.basename(i)) for i in inputfiles) + "?",default=True):
279
- inputfiles = [inputf]
280
- print("Only importing", col['yellow'](pth.basename(inputf)))
282
+ #if inputfiles != [inputf]:
283
+ # print("Spotify files should all be imported together to identify duplicates across the whole dataset.")
284
+ # if not ask("Import " + ", ".join(col['yellow'](pth.basename(i)) for i in inputfiles) + "?",default=True):
285
+ # inputfiles = [inputf]
286
+ # print("Only importing", col['yellow'](pth.basename(inputf)))
281
287
 
282
288
  # we keep timestamps here as well to remove duplicates because spotify's export
283
289
  # is messy - this is specific to this import type and should not be mixed with
@@ -288,7 +294,7 @@ def parse_spotify(inputf):
288
294
 
289
295
  for inputf in inputfiles:
290
296
 
291
- print("Importing",col['yellow'](inputf),"...")
297
+ #print("Importing",col['yellow'](inputf),"...")
292
298
  with open(inputf,'r') as inputfd:
293
299
  data = json.load(inputfd)
294
300
 
@@ -408,7 +414,7 @@ def parse_lastfm(inputf):
408
414
  continue
409
415
 
410
416
 
411
- def parse_lastfm_ghan(inputf):
417
+ def parse_lastfm_ghan_json(inputf):
412
418
  with open(inputf, 'r') as inputfd:
413
419
  data = json.load(inputfd)
414
420
 
@@ -430,6 +436,21 @@ def parse_lastfm_ghan(inputf):
430
436
  }, '')
431
437
 
432
438
 
439
+ def parse_lastfm_ghan_csv(inputf):
440
+ with open(inputf, 'r') as inputfd:
441
+ reader = csv.DictReader(inputfd)
442
+
443
+ for row in reader:
444
+ yield ('CONFIDENT_IMPORT', {
445
+ 'track_title': row['track'],
446
+ 'track_artists': row['artist'],
447
+ 'track_length': None,
448
+ 'album_name': row['album'],
449
+ 'scrobble_time': int(row['uts']),
450
+ 'scrobble_duration': None
451
+ }, '')
452
+
453
+
433
454
  def parse_listenbrainz(inputf):
434
455
 
435
456
  with open(inputf,'r') as inputfd:
maloja/server.py CHANGED
@@ -3,6 +3,7 @@ import os
3
3
  from threading import Thread
4
4
  from importlib import resources
5
5
  import time
6
+ from magic import from_file
6
7
 
7
8
 
8
9
  # server stuff
@@ -154,7 +155,8 @@ def static_image(pth):
154
155
 
155
156
  @webserver.route("/cacheimages/<uuid>")
156
157
  def static_proxied_image(uuid):
157
- return static_file(uuid,root=data_dir['cache']('images'))
158
+ mimetype = from_file(os.path.join(data_dir['cache']('images'),uuid),True)
159
+ return static_file(uuid,root=data_dir['cache']('images'),mimetype=mimetype)
158
160
 
159
161
  @webserver.route("/login")
160
162
  def login():
@@ -165,16 +167,16 @@ def login():
165
167
  @webserver.route("/media/<name>.<ext>")
166
168
  def static(name,ext):
167
169
  assert ext in ["txt","ico","jpeg","jpg","png","less","js","ttf","css"]
168
- with resources.files('maloja') / 'web' / 'static' as staticfolder:
169
- response = static_file(ext + "/" + name + "." + ext,root=staticfolder)
170
+ staticfolder = resources.files('maloja') / 'web' / 'static'
171
+ response = static_file(ext + "/" + name + "." + ext,root=staticfolder)
170
172
  response.set_header("Cache-Control", "public, max-age=3600")
171
173
  return response
172
174
 
173
175
  # new, direct reference
174
176
  @webserver.route("/static/<path:path>")
175
177
  def static(path):
176
- with resources.files('maloja') / 'web' / 'static' as staticfolder:
177
- response = static_file(path,root=staticfolder)
178
+ staticfolder = resources.files('maloja') / 'web' / 'static'
179
+ response = static_file(path,root=staticfolder)
178
180
  response.set_header("Cache-Control", "public, max-age=3600")
179
181
  return response
180
182
 
maloja/setup.py CHANGED
@@ -1,10 +1,9 @@
1
1
  import os
2
+ import shutil
2
3
 
3
4
  from importlib import resources
4
- try:
5
- from setuptools import distutils
6
- except ImportError:
7
- import distutils
5
+ from pathlib import PosixPath
6
+
8
7
  from doreah.io import col, ask, prompt
9
8
 
10
9
  from .pkg_global.conf import data_dir, dir_settings, malojaconfig, auth
@@ -22,21 +21,27 @@ ext_apikeys = [
22
21
 
23
22
 
24
23
  def copy_initial_local_files():
25
- with resources.files("maloja") / 'data_files' as folder:
26
- for cat in dir_settings:
27
- if dir_settings[cat] is None:
28
- continue
24
+ data_file_source = resources.files("maloja") / 'data_files'
25
+ for cat in dir_settings:
26
+ if dir_settings[cat] is None:
27
+ continue
28
+ if cat == 'config' and malojaconfig.readonly:
29
+ continue
29
30
 
30
- if cat == 'config' and malojaconfig.readonly:
31
- continue
31
+ # to avoid permission problems with the root dir
32
+ for subfolder in os.listdir(data_file_source / cat):
33
+ src = data_file_source / cat / subfolder
34
+ dst = PosixPath(dir_settings[cat]) / subfolder
35
+ if os.path.isdir(src):
36
+ shutil.copytree(src, dst, dirs_exist_ok=True)
32
37
 
33
- distutils.dir_util.copy_tree(os.path.join(folder,cat),dir_settings[cat],update=False)
34
38
 
35
39
  charset = list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
36
40
  def randomstring(length=32):
37
41
  import random
38
42
  return "".join(str(random.choice(charset)) for _ in range(length))
39
43
 
44
+
40
45
  def setup():
41
46
 
42
47
  copy_initial_local_files()
@@ -51,10 +56,11 @@ def setup():
51
56
  print(f"\tCurrently not using a {col['red'](keyname)} for image display.")
52
57
  elif key is None or key == "ASK":
53
58
  if malojaconfig.readonly:
54
- continue
55
- promptmsg = f"\tPlease enter your {col['gold'](keyname)}. If you do not want to use one at this moment, simply leave this empty and press Enter."
56
- key = prompt(promptmsg,types=(str,),default=False,skip=SKIP)
57
- malojaconfig[k] = key
59
+ print(f"\tCurrently not using a {col['red'](keyname)} for image display - config is read only.")
60
+ else:
61
+ promptmsg = f"\tPlease enter your {col['gold'](keyname)}. If you do not want to use one at this moment, simply leave this empty and press Enter."
62
+ key = prompt(promptmsg,types=(str,),default=False,skip=SKIP)
63
+ malojaconfig[k] = key
58
64
  else:
59
65
  print(f"\t{col['lawngreen'](keyname)} found.")
60
66
 
@@ -15,7 +15,7 @@
15
15
 
16
16
 
17
17
  var xhttp = new XMLHttpRequest();
18
- xhttp.open("POST","/api/newrule?", true);
18
+ xhttp.open("POST","/apis/mlj_1/newrule?", true);
19
19
  xhttp.send(keys);
20
20
  e = arguments[0];
21
21
  line = e.parentNode;
@@ -25,7 +25,7 @@
25
25
  function fullrebuild() {
26
26
 
27
27
  var xhttp = new XMLHttpRequest();
28
- xhttp.open("POST","/api/rebuild", true);
28
+ xhttp.open("POST","/apis/mlj_1/rebuild", true);
29
29
  xhttp.send();
30
30
  window.location = "/wait";
31
31
 
@@ -24,7 +24,7 @@
24
24
  keys = "filename=" + encodeURIComponent(filename);
25
25
  console.log(keys);
26
26
  var xhttp = new XMLHttpRequest();
27
- xhttp.open("POST","/api/importrules", true);
27
+ xhttp.open("POST","/apis/mlj_1/importrules", true);
28
28
  xhttp.send(keys);
29
29
 
30
30
  e.innerHTML = e.innerHTML.replace("Add","Remove");
@@ -36,7 +36,7 @@
36
36
  keys = "remove&filename=" + encodeURIComponent(filename);
37
37
 
38
38
  var xhttp = new XMLHttpRequest();
39
- xhttp.open("POST","/api/importrules", true);
39
+ xhttp.open("POST","/apis/mlj_1/importrules", true);
40
40
  xhttp.send(keys);
41
41
 
42
42
  e.innerHTML = e.innerHTML.replace("Remove","Add");
@@ -186,7 +186,7 @@ function search_manualscrobbling(searchfield) {
186
186
  else {
187
187
  xhttp = new XMLHttpRequest();
188
188
  xhttp.onreadystatechange = searchresult_manualscrobbling;
189
- xhttp.open("GET","/api/search?max=5&query=" + encodeURIComponent(txt), true);
189
+ xhttp.open("GET","/apis/mlj_1/search?max=5&query=" + encodeURIComponent(txt), true);
190
190
  xhttp.send();
191
191
  }
192
192
  }
@@ -1,17 +1,23 @@
1
- var searches = []
1
+ var searches = [];
2
+ var debounceTimer;
2
3
 
3
4
  function search(searchfield) {
4
- txt = searchfield.value;
5
- if (txt == "") {
6
- reallyclear()
7
- }
8
- else {
9
- xhttp = new XMLHttpRequest();
10
- searches.push(xhttp)
11
- xhttp.onreadystatechange = searchresult
12
- xhttp.open("GET","/api/search?max=5&query=" + encodeURIComponent(txt), true);
13
- xhttp.send();
14
- }
5
+ clearTimeout(debounceTimer);
6
+ debounceTimer = setTimeout(() => {
7
+ const txt = searchfield.value;
8
+ if (txt == "") {
9
+ reallyclear();
10
+ }
11
+ else {
12
+ const xhttp = new XMLHttpRequest();
13
+ searches.push(xhttp);
14
+ xhttp.onreadystatechange = searchresult
15
+ xhttp.open("GET","/apis/mlj_1/search?max=5&query=" + encodeURIComponent(txt), true);
16
+ xhttp.send();
17
+ }
18
+ }, 1000);
19
+
20
+
15
21
  }
16
22
 
17
23
 
@@ -1,3 +1,3 @@
1
1
  function upload(encodedentity,b64) {
2
- neo.xhttprequest("/api/addpicture?" + encodedentity,{"b64":b64},"POST")
2
+ neo.xhttprequest("/apis/mlj_1/addpicture?" + encodedentity,{"b64":b64},"POST")
3
3
  }
@@ -1,29 +1,29 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: malojaserver
3
- Version: 3.2.3
3
+ Version: 3.2.4
4
4
  Summary: Self-hosted music scrobble database
5
5
  Keywords: scrobbling,music,selfhosted,database,charts,statistics
6
6
  Author-email: Johannes Krattenmacher <maloja@dev.krateng.ch>
7
- Requires-Python: >=3.11
7
+ Requires-Python: ==3.12.*
8
8
  Description-Content-Type: text/markdown
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
11
11
  Classifier: Operating System :: OS Independent
12
- Requires-Dist: bottle>=0.12.16
13
- Requires-Dist: waitress>=2.1.0
14
- Requires-Dist: doreah>=2.0.1, <3
15
- Requires-Dist: nimrodel>=0.8.0
16
- Requires-Dist: setproctitle>=1.1.10
17
- Requires-Dist: jinja2>=3.0.0
18
- Requires-Dist: lru-dict>=1.1.6
19
- Requires-Dist: psutil>=5.8.0
20
- Requires-Dist: sqlalchemy>=2.0
21
- Requires-Dist: python-datauri>=1.1.0
22
- Requires-Dist: requests>=2.27.1
23
- Requires-Dist: setuptools>68.0.0
24
- Requires-Dist: toml>=0.10.2
25
- Requires-Dist: PyYAML>=6.0.1
26
- Requires-Dist: pyvips>=2.1 ; extra == "full"
12
+ Requires-Dist: bottle==0.13.*
13
+ Requires-Dist: waitress==3.0.*
14
+ Requires-Dist: doreah==2.0.*
15
+ Requires-Dist: nimrodel==0.8.*
16
+ Requires-Dist: setproctitle==1.3.*
17
+ Requires-Dist: jinja2==3.1.*
18
+ Requires-Dist: lru-dict==1.3.*
19
+ Requires-Dist: psutil==5.9.*
20
+ Requires-Dist: sqlalchemy==2.0
21
+ Requires-Dist: python-datauri==3.0.*
22
+ Requires-Dist: python-magic==0.4.*
23
+ Requires-Dist: requests==2.32.*
24
+ Requires-Dist: toml==0.10.*
25
+ Requires-Dist: PyYAML==6.0.*
26
+ Requires-Dist: pyvips==2.2.* ; extra == "full"
27
27
  Project-URL: documentation, https://github.com/krateng/maloja
28
28
  Project-URL: homepage, https://github.com/krateng/maloja
29
29
  Project-URL: repository, https://github.com/krateng/maloja
@@ -72,7 +72,7 @@ You can check [my own Maloja page](https://maloja.krateng.ch) as an example inst
72
72
  ## How to install
73
73
 
74
74
  To avoid issues with version / dependency mismatches, Maloja should only be used in **Docker** or **Podman**, not on bare metal.
75
- I cannot offer any help for bare metal installations.
75
+ I cannot offer any help for bare metal installations (but using venv should help).
76
76
 
77
77
  Pull the [latest image](https://hub.docker.com/r/krateng/maloja) or check out the repository and use the included Containerfile.
78
78
 
@@ -116,30 +116,18 @@ The modified run command with these variables would look like:
116
116
 
117
117
  ### Basic control
118
118
 
119
- When not running in a container, you can run the application with `maloja run`. You can also run it in the background with
120
- `maloja start` and `maloja stop`, but this might not be supported in the future.
119
+ When not running in a container, you can run the application with `maloja run`.
121
120
 
122
121
 
123
122
  ### Data
124
123
 
125
- If you would like to import your previous scrobbles, use the command `maloja import *filename*`. This works on:
124
+ If you would like to import your previous scrobbles, copy them into the import folder in your data directory. This works on:
126
125
 
127
126
  * a Last.fm export generated by [ghan64's website](https://lastfm.ghan.nl/export/)
128
127
  * an official [Spotify data export file](https://www.spotify.com/us/account/privacy/)
129
128
  * an official [ListenBrainz export file](https://listenbrainz.org/profile/export/)
130
129
  * the export of another Maloja instance
131
130
 
132
- ⚠️ Never import your data while maloja is running. When you need to do import inside docker container start it in shell mode instead and perform import before starting the container as mentioned above.
133
-
134
- ```console
135
- docker run -it --entrypoint sh -v $PWD/malojadata:/mljdata -e MALOJA_DATA_DIRECTORY=/mljdata krateng/maloja
136
- cd /mljdata
137
- maloja import my_last_fm_export.csv
138
- ```
139
-
140
-
141
- To backup your data, run `maloja backup`, optional with `--include_images`.
142
-
143
131
  ### Customization
144
132
 
145
133
  * Have a look at the [available settings](settings.md) and specifiy your choices in `/etc/maloja/settings.ini`. You can also set each of these settings as an environment variable with the prefix `MALOJA_` (e.g. `MALOJA_SKIP_SETUP`).
@@ -1,12 +1,12 @@
1
1
  maloja/__init__.py,sha256=ZMck_-0sLKrjUCt5PTqxFEnJ5L5Qq6K0QSEHz-B_77s,110
2
- maloja/__main__.py,sha256=WcsL9heCrO_8wrhZWlZ21JCCq-TBLU7cWR93few-zlk,5606
3
- maloja/__pkginfo__.py,sha256=vQ6ApGGl5cENg3EAHGw2tD3g_5OsoSjraceN7XYiqw0,392
2
+ maloja/__main__.py,sha256=jCdvg3zKYuCXsoesBBPFgisH-TOr5bzGXk-gm2dRwg0,3321
3
+ maloja/__pkginfo__.py,sha256=rGbkAMCF8qB1ViE8X9Dw5TO1oV6ILoAVgONVoSugem8,392
4
4
  maloja/cleanup.py,sha256=bLwYYVtGPxnrZFQnl0vvbeBFrgwcbe-YJn5in9CDLwo,6889
5
5
  maloja/images.py,sha256=yz8XxP8WKMbcxSkuz7RXGhN2mr6K4RY4yaWpB-MLQnc,13455
6
6
  maloja/malojatime.py,sha256=dp-Q8ienlfxyFUyCQYK6H9wDx8VBrW70H0jCpLncuzs,16692
7
7
  maloja/malojauri.py,sha256=jbYsvpZjLlKz8kU3jNytzupsYxJf-V-HYsHSnUa6QBA,6482
8
- maloja/server.py,sha256=X7LSVBnmCZS22QnvOfr6FQDvg06c0qBgOOjPrFEx-n8,7318
9
- maloja/setup.py,sha256=hKEK31jiFTNEA1TvKZpJJ4cVK13OFWnjEGsGzLGwo0U,3157
8
+ maloja/server.py,sha256=zwg_WnUoJvrgxHU8hH1r6xKBXLvxyWwHfxfhGtCBbN4,7423
9
+ maloja/setup.py,sha256=LKWyJFfOGVhM2RRScsR_RuOyzkLBdf4ZF-6-GWtHqhc,3412
10
10
  maloja/upgrade.py,sha256=2OtUMobInRRFRN0p7znGkWZ0Dq0wbN29xi06EiKA2k8,3291
11
11
  maloja/apis/__init__.py,sha256=2EhXgqfFRcTo7-71dR20rZ4P1mW1g_LVmmkd0GT5bv4,1441
12
12
  maloja/apis/_apikeys.py,sha256=htt8bnwfhpmhJAXcldmyXTT1QFlYR7LxnT4vG6duxzY,751
@@ -14,7 +14,7 @@ maloja/apis/_base.py,sha256=fCe_8qVQQu_Cxp7VpbFZiIYuR805yRnOwPq-dRqGkeg,3192
14
14
  maloja/apis/_exceptions.py,sha256=LoTHoTkG_ZQDnUmKeF1ngENOY1hyzrUVYq05cOYJqgk,218
15
15
  maloja/apis/audioscrobbler.py,sha256=2gFSAMmA_9z5A3-8VVQ7dKb3hqe4dvwcMfti4l6uoMk,4097
16
16
  maloja/apis/audioscrobbler_legacy.py,sha256=pJIIK-MpNahAEzVjNyULcFsnRnFBAVskfS8432AvZs8,3507
17
- maloja/apis/listenbrainz.py,sha256=xkblyHEedY4hroTXWH4lrFtspvTqeogBhW1TS0WPtx0,3166
17
+ maloja/apis/listenbrainz.py,sha256=5HEGv2Qd-Q6CpPI3DeoJUXW9kc85EDePMxWp0xBVl5Y,3279
18
18
  maloja/apis/native_v1.py,sha256=I_BkWM_mvo9fxtSRqbbjWBiQbdUieCDz1xB1lRC5xFE,26438
19
19
  maloja/data_files/cache/.maloja_cache_sentinel,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  maloja/data_files/cache/images/dummy,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -49,8 +49,8 @@ maloja/data_files/state/images/images.info,sha256=fnzMHHdpmWBRfZTXOFIYoMguunAyRO
49
49
  maloja/data_files/state/images/albums/dummy,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  maloja/data_files/state/images/artists/dummy,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  maloja/data_files/state/images/tracks/dummy,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- maloja/data_files/state/scrobbles/dummy,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- maloja/database/__init__.py,sha256=UuvbvLKuwYgysyNnjbYjOEGD5cmag4v_GcNFESsc4EE,31404
52
+ maloja/data_files/state/import/dummy,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ maloja/database/__init__.py,sha256=JdR4OGFI7byuboFKCo-X-1fw8516aAvjENdwEDQU-3U,31802
54
54
  maloja/database/associated.py,sha256=xde_CCDpHBSOOwgaNa7hc-4nH8PhKjdHQJaXIe3w4Wg,1680
55
55
  maloja/database/dbcache.py,sha256=TbppBv7NoMZcpmNycdoxKyQ3eNGGcg38THOF9GmsM2Q,4455
56
56
  maloja/database/exceptions.py,sha256=7e4n85Ed7fn-oECVy8oleX7PYjGoZMReUWiCRXI2lTI,1436
@@ -62,13 +62,13 @@ maloja/dev/generate.py,sha256=MlQYBts0f6OSVb063YxeDTWsODG49p-_aKRrRjm0PTY,2953
62
62
  maloja/dev/profiler.py,sha256=m2tagSaXe1ngGPdjmCZn7ZKEg2O9nr0qtCsUL6TJ2TI,1611
63
63
  maloja/jinjaenv/context.py,sha256=tVGVgW1g4LeYA5VmPlUmRy27fqV2tWb-bRTpJZcw8vk,3838
64
64
  maloja/jinjaenv/filters.py,sha256=IgUB4PeqvEqY1UpXwfmLyEvQVTMn6vYAav0L_6aY8_s,1477
65
- maloja/pkg_global/conf.py,sha256=kkDVG1qURdFFBRm1uMJaCebA5Jaw_9i2JcTb3TK-WfA,17246
65
+ maloja/pkg_global/conf.py,sha256=qjZ61n5Abbysdgf_EHUWXmu-tEUyrh0Dves40OOpdqE,17294
66
66
  maloja/pkg_global/monkey.py,sha256=ocxp6wpyYuUcdF4g4Tz2Pmo_pysxYrmWunOPEw8cZrU,585
67
67
  maloja/proccontrol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  maloja/proccontrol/tasks/__init__.py,sha256=-DRlzLIkYqbeDCWPaFXleOcceGXj-OUQWm6_4T8s1ds,166
69
69
  maloja/proccontrol/tasks/backup.py,sha256=sLqrX5Zzf7vYfsv4YG7yv0WS7E55gggo-nIxg8Fz05I,1322
70
70
  maloja/proccontrol/tasks/export.py,sha256=uloufUcqEzhedpANBwohkcPPwYboXRjqTCoo0YUdsDo,794
71
- maloja/proccontrol/tasks/import_scrobbles.py,sha256=A1j7m-ZH-0YPAWr4vEUOR3EvPl1-qW0nn3Dwe2pK9-o,15654
71
+ maloja/proccontrol/tasks/import_scrobbles.py,sha256=MrRWs8yMoLfHXBVveD5TINzMveYScqQ07meZ5rO8UaU,16369
72
72
  maloja/proccontrol/tasks/parse_albums.py,sha256=co09x9lyi0ZCqJ2QRtUHbmNgku_VNEIXCWCM7nYSals,4111
73
73
  maloja/thirdparty/__init__.py,sha256=FMnnHatVj5flaio7J8o01MwPz6TyiY_ngwxtEqgOruI,9571
74
74
  maloja/thirdparty/audiodb.py,sha256=2vmyZmKZNL4Um4JVC200oN9mrvXIIeYXhny5AeOuF-I,814
@@ -81,11 +81,11 @@ maloja/web/jinja/about.jinja,sha256=2idNqp8k43KKda94sWyD1fN9YB3TFCSgRE7ro2zdf3s,
81
81
  maloja/web/jinja/admin_albumless.jinja,sha256=QBYEwIFSagh-AAQ5BeNJpG1x4Aqgo4qe-OCX4RhvnLA,400
82
82
  maloja/web/jinja/admin_apikeys.jinja,sha256=QFWZdkcVks6_80-N_h2sxABkdD8XD9Wgj6DbnoQSDk0,314
83
83
  maloja/web/jinja/admin_import.jinja,sha256=VlfGqfisYkDXnSRPrUvuUheFw341RFGQXcCNrmi95ag,809
84
- maloja/web/jinja/admin_issues.jinja,sha256=1ZhxhwC0VSLQL5LftmP500GO150IMiY3MGPgu1zHDiQ,2471
84
+ maloja/web/jinja/admin_issues.jinja,sha256=gXXU6Oyrn_lK-X5qJrHr7XfQlopwtglQ0Oq1JivOUD0,2485
85
85
  maloja/web/jinja/admin_manual.jinja,sha256=H8txrBoPPao_8TKAgADxShc7wSSvV2Qp7rmHBpPYwks,2317
86
86
  maloja/web/jinja/admin_overview.jinja,sha256=SV3R7timLMquMZ_m_Qtb6fu_YNOk5RJ74sXEZ2m7GbQ,3626
87
87
  maloja/web/jinja/admin_settings.jinja,sha256=6jNVteSN-2PQalDn8e-pWW7xAN0vIWESgQjaYRN2CRw,183
88
- maloja/web/jinja/admin_setup.jinja,sha256=rDFgmz1c0DZAyF-Z90XyGpm4dzc-GRSFQ-wa44Gvoyo,5268
88
+ maloja/web/jinja/admin_setup.jinja,sha256=vEalQCiSIoXS-PAr_n2bXZtuZpRtRVybgdZvQPbCsMY,5282
89
89
  maloja/web/jinja/album.jinja,sha256=KjPuTxVy_p94k7cmWI0JWPCdyXjJeXdNczAtCJK4pDw,4158
90
90
  maloja/web/jinja/artist.jinja,sha256=DkekMGX5bfvy8MWCgFZi9mwuhHQB--7BoztbiAdYmKs,4914
91
91
  maloja/web/jinja/charts_albums.jinja,sha256=uJTS8oy1-pMTG6NuHQPsrqNuJBfEoa7Q_BQOzz16lPM,1389
@@ -169,12 +169,12 @@ maloja/web/static/ico/favicon_old.ico,sha256=Xch-fUCuGwBdUP3-kFgQZgXBj-jvVTW7tii
169
169
  maloja/web/static/js/datechange.js,sha256=TZfsi-9nzGHmieJALipW6qrFKNsmkOCQW1xX1UwJ_KA,921
170
170
  maloja/web/static/js/edit.js,sha256=cF9JyB_3I8pYlWzC-s7zBsGaREvFvI2M9cEWxCXSw_A,13544
171
171
  maloja/web/static/js/lazyload17-8-2.min.js,sha256=sO6HPavoXo796Zp_bq6ts4qHOoEqfydXEXSX5NojP48,8893
172
- maloja/web/static/js/manualscrobble.js,sha256=8BSHrh1LWoOHXkcpUcnEZlXyfhKDh83deiJks15YyqI,6350
172
+ maloja/web/static/js/manualscrobble.js,sha256=3R-QqEpa1rNpUjTng5dc4BH1XLgEDQDeO1ObST5uR4A,6357
173
173
  maloja/web/static/js/neopolitan.js,sha256=KHUpclvGsvGWc6OHu4e-UNwOmt_A-leNgbY1e3mtb00,5721
174
174
  maloja/web/static/js/notifications.js,sha256=Svf3u9XcJ0bUlDZca4Z2BEblK7DdKs3wR4EVZaG7-70,1405
175
- maloja/web/static/js/search.js,sha256=2BedwlSbE3L1Q7InX_RoC6SigJmJQ4wLpYEzh2iWt40,3545
175
+ maloja/web/static/js/search.js,sha256=2kXEWwzVBP5B3n_yU7vDv8X5U_o3QGgw_HbVHvhRUfo,3769
176
176
  maloja/web/static/js/statselect.js,sha256=UyHo7bjXKwhG_q5DgJDaoEgCRcsYRIQoV8S2beIBo_0,1750
177
- maloja/web/static/js/upload.js,sha256=z0LjPSGw86Up1ozq-EsZkmJX_hKlgPSirgH9-qMYwac,112
177
+ maloja/web/static/js/upload.js,sha256=DtoNOxs2EjViIO4QI7SkrWC6NC5yIaebMTybGARNiI0,119
178
178
  maloja/web/static/png/chartpos_bronze.png,sha256=ObhVuxMfM5LQZoMIjHaBfTHxrUsbKpmDKUHI4zfDmBQ,244
179
179
  maloja/web/static/png/chartpos_gold.png,sha256=1zzoKe8QVBnTyoirF-d4s-uyZdeFzZ5jN9Rmjcmxy2o,239
180
180
  maloja/web/static/png/chartpos_normal.png,sha256=SbdVtmwCezOnCoKyID5N_E0BGJn3MCD90209qOGFamI,245
@@ -197,8 +197,8 @@ maloja/web/static/ttf/Ubuntu-Medium.ttf,sha256=DWhfUcO8OU8CyMIYVlukI5cs-zXCVChx4
197
197
  maloja/web/static/ttf/Ubuntu-MediumItalic.ttf,sha256=bcsuSGcaFmv1YGT0BikSt61Bov3ylg786wJC2_N8pUs,309648
198
198
  maloja/web/static/ttf/Ubuntu-Regular.ttf,sha256=Zv6pwACR8l64pSZUgCO2FUeFh2qQCvLY9HKSJolpgWM,299684
199
199
  maloja/web/static/txt/robots.txt,sha256=Mx6pCQ2wyfb1l72YQP1bFxgw9uCzuhyyTfqR8Mla7cE,26
200
- malojaserver-3.2.3.dist-info/entry_points.txt,sha256=LrZkF8oW67Jx5Gp2GBG-WhUqW_ALwmCgfr7sVMTOu0k,47
201
- malojaserver-3.2.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
202
- malojaserver-3.2.3.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
203
- malojaserver-3.2.3.dist-info/METADATA,sha256=PuvbYy12B1eDhzoS1nCisspM9vEwuDLQlzTogOD40lc,9068
204
- malojaserver-3.2.3.dist-info/RECORD,,
200
+ malojaserver-3.2.4.dist-info/entry_points.txt,sha256=LrZkF8oW67Jx5Gp2GBG-WhUqW_ALwmCgfr7sVMTOu0k,47
201
+ malojaserver-3.2.4.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
202
+ malojaserver-3.2.4.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
203
+ malojaserver-3.2.4.dist-info/METADATA,sha256=InxNxathNK9a2Ix2OGeFdpGPsCd-WLNGm1WpLctdPyY,8526
204
+ malojaserver-3.2.4.dist-info/RECORD,,
File without changes