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 +0 -93
- maloja/__pkginfo__.py +1 -1
- maloja/apis/listenbrainz.py +2 -1
- maloja/database/__init__.py +13 -0
- maloja/pkg_global/conf.py +1 -0
- maloja/proccontrol/tasks/import_scrobbles.py +58 -37
- maloja/server.py +7 -5
- maloja/setup.py +21 -15
- maloja/web/jinja/admin_issues.jinja +2 -2
- maloja/web/jinja/admin_setup.jinja +2 -2
- maloja/web/static/js/manualscrobble.js +1 -1
- maloja/web/static/js/search.js +18 -12
- maloja/web/static/js/upload.js +1 -1
- {malojaserver-3.2.3.dist-info → malojaserver-3.2.4.dist-info}/METADATA +20 -32
- {malojaserver-3.2.3.dist-info → malojaserver-3.2.4.dist-info}/RECORD +19 -19
- /maloja/data_files/state/{scrobbles → import}/dummy +0 -0
- {malojaserver-3.2.3.dist-info → malojaserver-3.2.4.dist-info}/LICENSE +0 -0
- {malojaserver-3.2.3.dist-info → malojaserver-3.2.4.dist-info}/WHEEL +0 -0
- {malojaserver-3.2.3.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()
|
@@ -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
maloja/apis/listenbrainz.py
CHANGED
@@ -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
|
|
maloja/database/__init__.py
CHANGED
@@ -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
|
-
|
39
|
-
typeid,typedesc = "lastfm", "Last.fm (benjaminbenben
|
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
|
69
|
-
importfunc =
|
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("
|
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
|
-
|
161
|
-
|
163
|
+
#if len(inputfiles) == 0:
|
164
|
+
# print("No files found!")
|
165
|
+
# return
|
162
166
|
|
163
|
-
if inputfiles != [inputf]:
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
214
|
-
|
217
|
+
#if len(inputfiles) == 0:
|
218
|
+
# print("No files found!")
|
219
|
+
# return
|
215
220
|
|
216
|
-
if inputfiles != [inputf]:
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
274
|
-
|
278
|
+
#if len(inputfiles) == 0:
|
279
|
+
# print("No files found!")
|
280
|
+
# return
|
275
281
|
|
276
|
-
if inputfiles != [inputf]:
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
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
|
-
|
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
|
-
|
169
|
-
|
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
|
-
|
177
|
-
|
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
|
-
|
5
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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","/
|
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","/
|
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","/
|
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","/
|
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","/
|
189
|
+
xhttp.open("GET","/apis/mlj_1/search?max=5&query=" + encodeURIComponent(txt), true);
|
190
190
|
xhttp.send();
|
191
191
|
}
|
192
192
|
}
|
maloja/web/static/js/search.js
CHANGED
@@ -1,17 +1,23 @@
|
|
1
|
-
var searches = []
|
1
|
+
var searches = [];
|
2
|
+
var debounceTimer;
|
2
3
|
|
3
4
|
function search(searchfield) {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
|
maloja/web/static/js/upload.js
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: malojaserver
|
3
|
-
Version: 3.2.
|
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:
|
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
|
13
|
-
Requires-Dist: waitress
|
14
|
-
Requires-Dist: doreah
|
15
|
-
Requires-Dist: nimrodel
|
16
|
-
Requires-Dist: setproctitle
|
17
|
-
Requires-Dist: jinja2
|
18
|
-
Requires-Dist: lru-dict
|
19
|
-
Requires-Dist: psutil
|
20
|
-
Requires-Dist: sqlalchemy
|
21
|
-
Requires-Dist: python-datauri
|
22
|
-
Requires-Dist:
|
23
|
-
Requires-Dist:
|
24
|
-
Requires-Dist: toml
|
25
|
-
Requires-Dist: PyYAML
|
26
|
-
Requires-Dist: pyvips
|
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`.
|
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,
|
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=
|
3
|
-
maloja/__pkginfo__.py,sha256=
|
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=
|
9
|
-
maloja/setup.py,sha256=
|
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=
|
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/
|
53
|
-
maloja/database/__init__.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
201
|
-
malojaserver-3.2.
|
202
|
-
malojaserver-3.2.
|
203
|
-
malojaserver-3.2.
|
204
|
-
malojaserver-3.2.
|
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
|
File without changes
|
File without changes
|
File without changes
|