From 7dbd704c5ded2a9fb0fd6222e0906c6bf046d420 Mon Sep 17 00:00:00 2001
From: duck <113956421+duckfromdiscord@users.noreply.github.com>
Date: Sat, 11 Nov 2023 17:56:40 -0500
Subject: [PATCH 1/6] make `auth.getMobileSession` return XML
---
maloja/apis/audioscrobbler.py | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/maloja/apis/audioscrobbler.py b/maloja/apis/audioscrobbler.py
index 6699618..350d4a3 100644
--- a/maloja/apis/audioscrobbler.py
+++ b/maloja/apis/audioscrobbler.py
@@ -28,6 +28,15 @@ class Audioscrobbler(APIHandler):
ScrobblingException:(500,{"error":8,"message":"Operation failed"})
}
+ # xml string escaping: https://stackoverflow.com/a/28703510
+ def xml_escape(self, str_xml: str):
+ str_xml = str_xml.replace("&", "&")
+ str_xml = str_xml.replace("<", "<")
+ str_xml = str_xml.replace("<", "<")
+ str_xml = str_xml.replace("\"", """)
+ str_xml = str_xml.replace("'", "'")
+ return str_xml
+
def get_method(self,pathnodes,keys):
return keys.get("method")
@@ -50,7 +59,14 @@ class Audioscrobbler(APIHandler):
client = apikeystore.check_and_identify_key(password)
if client:
sessionkey = self.generate_key(client)
- return 200,{"session":{"key":sessionkey}}
+ return 200,"""
+
+ %s
+ %s
+ 0
+
+
+""" % (self.xml_escape(user), self.xml_escape(sessionkey))
else:
raise InvalidAuthException()
# or username and token (deprecated by lastfm)
From be6b796b20c950bdc3fe7e3142dd9a3b8fdd55bf Mon Sep 17 00:00:00 2001
From: duck <113956421+duckfromdiscord@users.noreply.github.com>
Date: Sat, 11 Nov 2023 18:14:20 -0500
Subject: [PATCH 2/6] `auth.getMobileSession` return XML with token provided
---
maloja/apis/audioscrobbler.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/maloja/apis/audioscrobbler.py b/maloja/apis/audioscrobbler.py
index 350d4a3..38f3fd3 100644
--- a/maloja/apis/audioscrobbler.py
+++ b/maloja/apis/audioscrobbler.py
@@ -75,7 +75,14 @@ class Audioscrobbler(APIHandler):
key = apikeystore[client]
if md5(user + md5(key)) == token:
sessionkey = self.generate_key(client)
- return 200,{"session":{"key":sessionkey}}
+ return 200,"""
+
+ %s
+ %s
+ 0
+
+
+""" % (self.xml_escape(user), self.xml_escape(sessionkey))
raise InvalidAuthException()
else:
raise BadAuthException()
From 16b977d874fe2a65ce17df160228acbcefa18807 Mon Sep 17 00:00:00 2001
From: duck <113956421+duckfromdiscord@users.noreply.github.com>
Date: Wed, 3 Jan 2024 21:57:42 -0500
Subject: [PATCH 3/6] allow json format for `authmobile`, default to XML
---
maloja/apis/audioscrobbler.py | 37 ++++++++++++++++++++---------------
1 file changed, 21 insertions(+), 16 deletions(-)
diff --git a/maloja/apis/audioscrobbler.py b/maloja/apis/audioscrobbler.py
index 38f3fd3..7b7194a 100644
--- a/maloja/apis/audioscrobbler.py
+++ b/maloja/apis/audioscrobbler.py
@@ -54,19 +54,22 @@ class Audioscrobbler(APIHandler):
token = keys.get("authToken")
user = keys.get("username")
password = keys.get("password")
+ format = keys.get("format") or "xml" # Audioscrobbler 2.0 uses XML by default
# either username and password
if user is not None and password is not None:
client = apikeystore.check_and_identify_key(password)
if client:
sessionkey = self.generate_key(client)
- return 200,"""
-
- %s
- %s
- 0
-
-
-""" % (self.xml_escape(user), self.xml_escape(sessionkey))
+ if format == "json":
+ return 200,{"session":{"key":sessionkey}}
+ else:
+ return 200,"""
+
+ %s
+ %s
+ 0
+
+""" % (self.xml_escape(user), self.xml_escape(sessionkey))
else:
raise InvalidAuthException()
# or username and token (deprecated by lastfm)
@@ -75,14 +78,16 @@ class Audioscrobbler(APIHandler):
key = apikeystore[client]
if md5(user + md5(key)) == token:
sessionkey = self.generate_key(client)
- return 200,"""
-
- %s
- %s
- 0
-
-
-""" % (self.xml_escape(user), self.xml_escape(sessionkey))
+ if format == "json":
+ return 200,{"session":{"key":sessionkey}}
+ else:
+ return 200,"""
+
+ %s
+ %s
+ 0
+
+""" % (self.xml_escape(user), self.xml_escape(sessionkey))
raise InvalidAuthException()
else:
raise BadAuthException()
From a816147e2e366d2757fb3d60196e6522666fe988 Mon Sep 17 00:00:00 2001
From: ThinkChaos
Date: Mon, 5 Feb 2024 21:27:26 -0500
Subject: [PATCH 4/6] feat: readonly config support read-only filesystem
---
maloja/cleanup.py | 16 +++++++++-------
maloja/database/associated.py | 16 ++++++++++------
maloja/pkg_global/conf.py | 14 +++++++++++---
maloja/setup.py | 9 ++++++++-
4 files changed, 38 insertions(+), 17 deletions(-)
diff --git a/maloja/cleanup.py b/maloja/cleanup.py
index 62120b9..7395448 100644
--- a/maloja/cleanup.py
+++ b/maloja/cleanup.py
@@ -15,13 +15,15 @@ class CleanerAgent:
def updateRules(self):
rawrules = []
- for f in os.listdir(data_dir["rules"]()):
- if f.split('.')[-1].lower() != 'tsv': continue
- filepath = data_dir["rules"](f)
- with open(filepath,'r') as filed:
- reader = csv.reader(filed,delimiter="\t")
- rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
-
+ try:
+ for f in os.listdir(data_dir["rules"]()):
+ if f.split('.')[-1].lower() != 'tsv': continue
+ filepath = data_dir["rules"](f)
+ with open(filepath,'r') as filed:
+ reader = csv.reader(filed,delimiter="\t")
+ rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
+ except FileNotFoundError:
+ pass
self.rules_belongtogether = [r[1] for r in rawrules if r[0]=="belongtogether"]
self.rules_notanartist = [r[1] for r in rawrules if r[0]=="notanartist"]
diff --git a/maloja/database/associated.py b/maloja/database/associated.py
index 69ccc61..d0bb05f 100644
--- a/maloja/database/associated.py
+++ b/maloja/database/associated.py
@@ -19,12 +19,16 @@ def load_associated_rules():
# load from file
rawrules = []
- for f in os.listdir(data_dir["rules"]()):
- if f.split('.')[-1].lower() != 'tsv': continue
- filepath = data_dir["rules"](f)
- with open(filepath,'r') as filed:
- reader = csv.reader(filed,delimiter="\t")
- rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
+ try:
+ for f in os.listdir(data_dir["rules"]()):
+ if f.split('.')[-1].lower() != 'tsv': continue
+ filepath = data_dir["rules"](f)
+ with open(filepath,'r') as filed:
+ reader = csv.reader(filed,delimiter="\t")
+ rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
+ except FileNotFoundError:
+ return
+
rules = [{'source_artist':r[1],'target_artist':r[2]} for r in rawrules if r[0]=="countas"]
#for rule in rules:
diff --git a/maloja/pkg_global/conf.py b/maloja/pkg_global/conf.py
index bd152d3..876ce95 100644
--- a/maloja/pkg_global/conf.py
+++ b/maloja/pkg_global/conf.py
@@ -311,6 +311,12 @@ data_directories = {
}
for identifier,path in data_directories.items():
+ if path is None:
+ continue
+
+ if malojaconfig.readonly and (path == dir_settings['config'] or path.startswith(dir_settings['config']+'/')):
+ continue
+
try:
os.makedirs(path,exist_ok=True)
if not is_dir_usable(path): raise PermissionError(f"Directory {path} is not usable!")
@@ -321,7 +327,7 @@ for identifier,path in data_directories.items():
print("Cannot use",path,"for cache, finding new folder...")
data_directories['cache'] = dir_settings['cache'] = malojaconfig['DIRECTORY_CACHE'] = find_good_folder('cache')
else:
- print("Directory",path,"is not usable.")
+ print(f"Directory for {identifier} ({path}) is not writeable.")
print("Please change permissions or settings!")
print("Make sure Maloja has write and execute access to this directory.")
raise
@@ -344,8 +350,10 @@ auth = doreah.auth.AuthManager(singleuser=True,cookieprefix='maloja',stylesheets
doreah.logging.defaultlogger.logfolder = data_dir['logs']() if malojaconfig["LOGGING"] else None
-
-custom_css_files = [f for f in os.listdir(data_dir['css']()) if f.lower().endswith('.css')]
+try:
+ custom_css_files = [f for f in os.listdir(data_dir['css']()) if f.lower().endswith('.css')]
+except FileNotFoundError:
+ custom_css_files = []
from ..database.sqldb import set_maloja_info
set_maloja_info({'last_run_version':VERSION})
diff --git a/maloja/setup.py b/maloja/setup.py
index a8652f1..9f9ba60 100644
--- a/maloja/setup.py
+++ b/maloja/setup.py
@@ -24,6 +24,12 @@ ext_apikeys = [
def copy_initial_local_files():
with resources.files("maloja") / 'data_files' as folder:
for cat in dir_settings:
+ if dir_settings[cat] is None:
+ continue
+
+ if cat == 'config' and malojaconfig.readonly:
+ continue
+
distutils.dir_util.copy_tree(os.path.join(folder,cat),dir_settings[cat],update=False)
charset = list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
@@ -37,7 +43,6 @@ def setup():
SKIP = malojaconfig["SKIP_SETUP"]
try:
-
print("Various external services can be used to display images. If not enough of them are set up, only local images will be used.")
for k in ext_apikeys:
keyname = malojaconfig.get_setting_info(k)['name']
@@ -45,6 +50,8 @@ def setup():
if key is False:
print(f"\tCurrently not using a {col['red'](keyname)} for image display.")
elif key is None or key == "ASK":
+ if malojaconfig.readonly:
+ continue
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."
key = prompt(promptmsg,types=(str,),default=False,skip=SKIP)
malojaconfig[k] = key
From efd7838b02f30b3c99611fc16c3f05a95fe8aadf Mon Sep 17 00:00:00 2001
From: ThinkChaos
Date: Mon, 5 Feb 2024 21:29:45 -0500
Subject: [PATCH 5/6] fix(conf): don't use `cache` dir as base for all data
dirs
This is a bit tricky but the issue is that in a `for` loop, the loop
variable(s) are shared during the whole iteration. So capturing its
value in the `lambda` as `k=k` doesn't capture the value of the current
iteration, but the value of the last one.
In this code that happened to be `cache`, so all `data_dirs` usage was
ending up with a path under `directory_cache`.
---
maloja/pkg_global/conf.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/maloja/pkg_global/conf.py b/maloja/pkg_global/conf.py
index 876ce95..46f2eab 100644
--- a/maloja/pkg_global/conf.py
+++ b/maloja/pkg_global/conf.py
@@ -332,12 +332,14 @@ for identifier,path in data_directories.items():
print("Make sure Maloja has write and execute access to this directory.")
raise
+class DataDirs:
+ def __init__(self, dirs):
+ self.dirs = dirs
-data_dir = {
- k:lambda *x,k=k: pthj(data_directories[k],*x) for k in data_directories
-}
-
+ def __getitem__(self, key):
+ return lambda *x, k=key: pthj(self.dirs[k], *x)
+data_dir = DataDirs(data_directories)
### DOREAH OBJECTS
From a99831d453107a7470847c40080d71859f37ae07 Mon Sep 17 00:00:00 2001
From: ThinkChaos
Date: Mon, 5 Feb 2024 21:30:51 -0500
Subject: [PATCH 6/6] fix(log): replace "Data" with "State" to match printed
value
---
maloja/__main__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/maloja/__main__.py b/maloja/__main__.py
index 0fa7e7d..fae9108 100644
--- a/maloja/__main__.py
+++ b/maloja/__main__.py
@@ -135,7 +135,7 @@ def debug():
def print_info():
print_header_info()
print(col['lightblue']("Configuration Directory:"),conf.dir_settings['config'])
- print(col['lightblue']("Data Directory: "),conf.dir_settings['state'])
+ print(col['lightblue']("State Directory: "),conf.dir_settings['state'])
print(col['lightblue']("Log Directory: "),conf.dir_settings['logs'])
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']}")
print(col['lightblue']("Timezone: "),f"UTC{conf.malojaconfig['timezone']:+d}")