mirror of
https://github.com/krateng/maloja.git
synced 2025-06-18 07:53:00 +03:00
Merge branch 'krateng:master' into as2.0-xml
This commit is contained in:
commit
f7a9df7446
@ -70,6 +70,7 @@ COPY container/root/ /
|
|||||||
ENV \
|
ENV \
|
||||||
# Docker-specific configuration
|
# Docker-specific configuration
|
||||||
MALOJA_SKIP_SETUP=yes \
|
MALOJA_SKIP_SETUP=yes \
|
||||||
|
MALOJA_CONTAINER=yes \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
# Prevents breaking change for previous container that ran maloja as root
|
# Prevents breaking change for previous container that ran maloja as root
|
||||||
# On linux hosts (non-podman rootless) these variables should be set to the
|
# On linux hosts (non-podman rootless) these variables should be set to the
|
||||||
|
@ -33,5 +33,7 @@ minor_release_name: "Nicole"
|
|||||||
- "[Technical] Upgraded all third party modules to use requests module and send User Agent"
|
- "[Technical] Upgraded all third party modules to use requests module and send User Agent"
|
||||||
3.2.2:
|
3.2.2:
|
||||||
notes:
|
notes:
|
||||||
|
- "[Security] Fixed XSS vulnerability in error page (Disclosed by https://github.com/NULLYUKI)"
|
||||||
|
- "[Architecture] Reworked the default directory selection"
|
||||||
- "[Feature] Added option to show scrobbles on tile charts"
|
- "[Feature] Added option to show scrobbles on tile charts"
|
||||||
- "[Bugfix] Fixed Last.fm authentication"
|
- "[Bugfix] Fixed Last.fm authentication"
|
@ -4,7 +4,7 @@
|
|||||||
# you know what f*ck it
|
# you know what f*ck it
|
||||||
# this is hardcoded for now because of that damn project / package name discrepancy
|
# this is hardcoded for now because of that damn project / package name discrepancy
|
||||||
# i'll fix it one day
|
# i'll fix it one day
|
||||||
VERSION = "3.2.1"
|
VERSION = "3.2.2"
|
||||||
HOMEPAGE = "https://github.com/krateng/maloja"
|
HOMEPAGE = "https://github.com/krateng/maloja"
|
||||||
|
|
||||||
|
|
||||||
|
@ -160,8 +160,8 @@ replaceartist 여자친구 GFriend GFriend
|
|||||||
# Girl's Generation
|
# Girl's Generation
|
||||||
replaceartist 소녀시대 Girls' Generation
|
replaceartist 소녀시대 Girls' Generation
|
||||||
replaceartist SNSD Girls' Generation
|
replaceartist SNSD Girls' Generation
|
||||||
replaceartist Girls' Generation-TTS TaeTiSeo
|
replaceartist Girls' Generation-TTS TaeTiSeo
|
||||||
countas TaeTiSeo Girls' Generation
|
countas TaeTiSeo Girls' Generation
|
||||||
|
|
||||||
# Apink
|
# Apink
|
||||||
replaceartist A Pink Apink
|
replaceartist A Pink Apink
|
||||||
@ -217,6 +217,8 @@ countas Pristin V Pristin
|
|||||||
|
|
||||||
# CLC
|
# CLC
|
||||||
countas Sorn CLC
|
countas Sorn CLC
|
||||||
|
countas Yeeun CLC
|
||||||
|
countas Seungyeon CLC
|
||||||
|
|
||||||
# Popular Remixes
|
# Popular Remixes
|
||||||
artistintitle Areia Remix Areia
|
artistintitle Areia Remix Areia
|
||||||
|
Can't render this file because it has a wrong number of fields in line 5.
|
@ -17,9 +17,11 @@ AUX_MODE = True
|
|||||||
# DIRECRORY_CONFIG, DIRECRORY_STATE, DIRECTORY_LOGS and DIRECTORY_CACHE
|
# DIRECRORY_CONFIG, DIRECRORY_STATE, DIRECTORY_LOGS and DIRECTORY_CACHE
|
||||||
# config can only be determined by environment variable, the others can be loaded
|
# config can only be determined by environment variable, the others can be loaded
|
||||||
# from the config files
|
# from the config files
|
||||||
# explicit settings will always be respected, fallback to default
|
|
||||||
|
|
||||||
# if default isn't usable, and config writable, find alternative and fix it in settings
|
# we don't specify 'default' values in the normal sense of the config object
|
||||||
|
# the default is none, meaning the app should figure it out (depending on environment)
|
||||||
|
# the actual 'default' values of our folders are simply in code since they are dependent on environment (container?)
|
||||||
|
# and we need to actually distinguish them from the user having specified something
|
||||||
|
|
||||||
# USEFUL FUNCS
|
# USEFUL FUNCS
|
||||||
pthj = os.path.join
|
pthj = os.path.join
|
||||||
@ -27,9 +29,7 @@ pthj = os.path.join
|
|||||||
def is_dir_usable(pth):
|
def is_dir_usable(pth):
|
||||||
try:
|
try:
|
||||||
os.makedirs(pth,exist_ok=True)
|
os.makedirs(pth,exist_ok=True)
|
||||||
os.mknod(pthj(pth,".test"))
|
return os.access(pth,os.W_OK)
|
||||||
os.remove(pthj(pth,".test"))
|
|
||||||
return True
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -41,6 +41,9 @@ def get_env_vars(key,pathsuffix=[]):
|
|||||||
directory_info = {
|
directory_info = {
|
||||||
"config":{
|
"config":{
|
||||||
"sentinel":".maloja_config_sentinel",
|
"sentinel":".maloja_config_sentinel",
|
||||||
|
"possible_folders_container":[
|
||||||
|
"/config/config"
|
||||||
|
],
|
||||||
"possible_folders":[
|
"possible_folders":[
|
||||||
"/etc/maloja",
|
"/etc/maloja",
|
||||||
os.path.expanduser("~/.local/share/maloja")
|
os.path.expanduser("~/.local/share/maloja")
|
||||||
@ -49,14 +52,21 @@ directory_info = {
|
|||||||
},
|
},
|
||||||
"cache":{
|
"cache":{
|
||||||
"sentinel":".maloja_cache_sentinel",
|
"sentinel":".maloja_cache_sentinel",
|
||||||
|
"possible_folders_container":[
|
||||||
|
"/config/cache"
|
||||||
|
],
|
||||||
"possible_folders":[
|
"possible_folders":[
|
||||||
"/var/cache/maloja",
|
"/var/cache/maloja",
|
||||||
os.path.expanduser("~/.local/share/maloja/cache")
|
os.path.expanduser("~/.local/share/maloja/cache"),
|
||||||
|
"/tmp/maloja"
|
||||||
],
|
],
|
||||||
"setting":"directory_cache"
|
"setting":"directory_cache"
|
||||||
},
|
},
|
||||||
"state":{
|
"state":{
|
||||||
"sentinel":".maloja_state_sentinel",
|
"sentinel":".maloja_state_sentinel",
|
||||||
|
"possible_folders_container":[
|
||||||
|
"/config/state"
|
||||||
|
],
|
||||||
"possible_folders":[
|
"possible_folders":[
|
||||||
"/var/lib/maloja",
|
"/var/lib/maloja",
|
||||||
os.path.expanduser("~/.local/share/maloja")
|
os.path.expanduser("~/.local/share/maloja")
|
||||||
@ -65,6 +75,9 @@ directory_info = {
|
|||||||
},
|
},
|
||||||
"logs":{
|
"logs":{
|
||||||
"sentinel":".maloja_logs_sentinel",
|
"sentinel":".maloja_logs_sentinel",
|
||||||
|
"possible_folders_container":[
|
||||||
|
"/config/logs"
|
||||||
|
],
|
||||||
"possible_folders":[
|
"possible_folders":[
|
||||||
"/var/log/maloja",
|
"/var/log/maloja",
|
||||||
os.path.expanduser("~/.local/share/maloja/logs")
|
os.path.expanduser("~/.local/share/maloja/logs")
|
||||||
@ -77,25 +90,27 @@ directory_info = {
|
|||||||
# checks if one has been in use before and writes it to dict/config
|
# checks if one has been in use before and writes it to dict/config
|
||||||
# if not, determines which to use and writes it to dict/config
|
# if not, determines which to use and writes it to dict/config
|
||||||
# returns determined folder
|
# returns determined folder
|
||||||
def find_good_folder(datatype,configobject):
|
def find_good_folder(datatype):
|
||||||
info = directory_info[datatype]
|
info = directory_info[datatype]
|
||||||
|
|
||||||
|
possible_folders = info['possible_folders']
|
||||||
|
if os.environ.get("MALOJA_CONTAINER"):
|
||||||
|
possible_folders = info['possible_folders_container'] + possible_folders
|
||||||
|
|
||||||
# check each possible folder if its used
|
# check each possible folder if its used
|
||||||
for p in info['possible_folders']:
|
for p in possible_folders:
|
||||||
if os.path.exists(pthj(p,info['sentinel'])):
|
if os.path.exists(pthj(p,info['sentinel'])):
|
||||||
if is_dir_usable(p):
|
if is_dir_usable(p):
|
||||||
print(p,"was apparently used as maloja's folder for",datatype,"- fixing in settings")
|
#print(p,"was apparently used as maloja's folder for",datatype,"- fixing in settings")
|
||||||
configobject[info['setting']] = p
|
|
||||||
return p
|
return p
|
||||||
else:
|
else:
|
||||||
raise PermissionError(f"Can no longer use previously used path {p}")
|
raise PermissionError(f"Can no longer use previously used {datatype} folder {p}")
|
||||||
|
|
||||||
#print("Could not find previous",datatype,"folder")
|
#print("Could not find previous",datatype,"folder")
|
||||||
# check which one we can use
|
# check which one we can use
|
||||||
for p in info['possible_folders']:
|
for p in possible_folders:
|
||||||
if is_dir_usable(p):
|
if is_dir_usable(p):
|
||||||
print(p,"has been selected as maloja's folder for",datatype)
|
#print(p,"has been selected as maloja's folder for",datatype)
|
||||||
configobject[info['setting']] = p
|
|
||||||
return p
|
return p
|
||||||
#print("No folder can be used for",datatype)
|
#print("No folder can be used for",datatype)
|
||||||
#print("This should not happen!")
|
#print("This should not happen!")
|
||||||
@ -106,26 +121,20 @@ def find_good_folder(datatype,configobject):
|
|||||||
|
|
||||||
|
|
||||||
### STEP 1 - find out where the settings file is
|
### STEP 1 - find out where the settings file is
|
||||||
# environment variables
|
|
||||||
maloja_dir_config = os.environ.get("MALOJA_DATA_DIRECTORY") or os.environ.get("MALOJA_DIRECTORY_CONFIG")
|
maloja_dir_config = os.environ.get("MALOJA_DATA_DIRECTORY") or os.environ.get("MALOJA_DIRECTORY_CONFIG")
|
||||||
|
|
||||||
|
|
||||||
if maloja_dir_config is None:
|
if maloja_dir_config is None:
|
||||||
maloja_dir_config = find_good_folder('config',{})
|
# if nothing is set, we set our own
|
||||||
found_new_config_dir = True
|
maloja_dir_config = find_good_folder('config')
|
||||||
else:
|
else:
|
||||||
found_new_config_dir = False
|
pass
|
||||||
# remember whether we had to find our config dir or it was user-specified
|
# if there is an environment variable, this is 100% explicitly defined by the user, so we respect it
|
||||||
|
# the user might run more than one instances on the same machine, so we don't do any heuristics here
|
||||||
|
# if you define this, we believe it!
|
||||||
|
|
||||||
os.makedirs(maloja_dir_config,exist_ok=True)
|
os.makedirs(maloja_dir_config,exist_ok=True)
|
||||||
|
settingsfile = pthj(maloja_dir_config,"settings.ini")
|
||||||
oldsettingsfile = pthj(maloja_dir_config,"settings","settings.ini")
|
|
||||||
newsettingsfile = pthj(maloja_dir_config,"settings.ini")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists(oldsettingsfile):
|
|
||||||
os.rename(oldsettingsfile,newsettingsfile)
|
|
||||||
|
|
||||||
|
|
||||||
### STEP 2 - create settings object
|
### STEP 2 - create settings object
|
||||||
@ -135,10 +144,10 @@ malojaconfig = Configuration(
|
|||||||
settings={
|
settings={
|
||||||
"Setup":{
|
"Setup":{
|
||||||
"data_directory":(tp.String(), "Data Directory", None, "Folder for all user data. Overwrites all choices for specific directories."),
|
"data_directory":(tp.String(), "Data Directory", None, "Folder for all user data. Overwrites all choices for specific directories."),
|
||||||
"directory_config":(tp.String(), "Config Directory", "/etc/maloja", "Folder for config data. Only applied when global data directory is not set."),
|
"directory_config":(tp.String(), "Config Directory", None, "Folder for config data. Only applied when global data directory is not set."),
|
||||||
"directory_state":(tp.String(), "State Directory", "/var/lib/maloja", "Folder for state data. Only applied when global data directory is not set."),
|
"directory_state":(tp.String(), "State Directory", None, "Folder for state data. Only applied when global data directory is not set."),
|
||||||
"directory_logs":(tp.String(), "Log Directory", "/var/log/maloja", "Folder for log data. Only applied when global data directory is not set."),
|
"directory_logs":(tp.String(), "Log Directory", None, "Folder for log data. Only applied when global data directory is not set."),
|
||||||
"directory_cache":(tp.String(), "Cache Directory", "/var/cache/maloja", "Folder for cache data. Only applied when global data directory is not set."),
|
"directory_cache":(tp.String(), "Cache Directory", None, "Folder for cache data. Only applied when global data directory is not set."),
|
||||||
"skip_setup":(tp.Boolean(), "Skip Setup", False, "Make server setup process non-interactive. Vital for Docker."),
|
"skip_setup":(tp.Boolean(), "Skip Setup", False, "Make server setup process non-interactive. Vital for Docker."),
|
||||||
"force_password":(tp.String(), "Force Password", None, "On startup, overwrite admin password with this one. This should usually only be done via environment variable in Docker."),
|
"force_password":(tp.String(), "Force Password", None, "On startup, overwrite admin password with this one. This should usually only be done via environment variable in Docker."),
|
||||||
"clean_output":(tp.Boolean(), "Avoid Mutable Console Output", False, "Use if console output will be redirected e.g. to a web interface.")
|
"clean_output":(tp.Boolean(), "Avoid Mutable Console Output", False, "Use if console output will be redirected e.g. to a web interface.")
|
||||||
@ -214,18 +223,15 @@ malojaconfig = Configuration(
|
|||||||
"theme":(tp.String(), "Theme", "maloja")
|
"theme":(tp.String(), "Theme", "maloja")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
configfile=newsettingsfile,
|
configfile=settingsfile,
|
||||||
save_endpoint="/apis/mlj_1/settings",
|
save_endpoint="/apis/mlj_1/settings",
|
||||||
env_prefix="MALOJA_",
|
env_prefix="MALOJA_",
|
||||||
extra_files=["/run/secrets/maloja.yml","/run/secrets/maloja.ini"]
|
extra_files=["/run/secrets/maloja.yml","/run/secrets/maloja.ini"]
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if found_new_config_dir:
|
if not malojaconfig.readonly:
|
||||||
try:
|
malojaconfig["DIRECTORY_CONFIG"] = maloja_dir_config
|
||||||
malojaconfig["DIRECTORY_CONFIG"] = maloja_dir_config
|
|
||||||
except PermissionError as e:
|
|
||||||
pass
|
|
||||||
# this really doesn't matter because when are we gonna load info about where
|
# this really doesn't matter because when are we gonna load info about where
|
||||||
# the settings file is stored from the settings file
|
# the settings file is stored from the settings file
|
||||||
# but oh well
|
# but oh well
|
||||||
@ -247,17 +253,17 @@ except PermissionError as e:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
### STEP 3 - check all possible folders for files (old installation)
|
### STEP 3 - now check the other directories
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if not malojaconfig.readonly:
|
if not malojaconfig.readonly:
|
||||||
for datatype in ("state","cache","logs"):
|
for datatype in ("state","cache","logs"):
|
||||||
# obviously default values shouldn't trigger this
|
# if the setting is specified in the file or via a user environment variable, we accept it (we'll check later if it's usable)
|
||||||
# if user has nothing specified, we need to use this
|
if malojaconfig[directory_info[datatype]['setting']] or malojaconfig['DATA_DIRECTORY']:
|
||||||
if malojaconfig.get_specified(directory_info[datatype]['setting']) is None and malojaconfig.get_specified('DATA_DIRECTORY') is None:
|
pass
|
||||||
find_good_folder(datatype,malojaconfig)
|
# otherwise, find a good one
|
||||||
|
else:
|
||||||
|
malojaconfig[directory_info[datatype]['setting']] = find_good_folder(datatype)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -310,11 +316,11 @@ for identifier,path in data_directories.items():
|
|||||||
# just move to the next one
|
# just move to the next one
|
||||||
if identifier in ['cache']:
|
if identifier in ['cache']:
|
||||||
print("Cannot use",path,"for cache, finding new folder...")
|
print("Cannot use",path,"for cache, finding new folder...")
|
||||||
find_good_folder('cache',malojaconfig)
|
data_directories['cache'] = dir_settings['cache'] = malojaconfig['DIRECTORY_CACHE'] = find_good_folder('cache')
|
||||||
data_directories['cache'] = dir_settings['cache'] = malojaconfig['DIRECTORY_CACHE']
|
|
||||||
else:
|
else:
|
||||||
print("Directory",path,"is not usable.")
|
print("Directory",path,"is not usable.")
|
||||||
print("Please change permissions or settings!")
|
print("Please change permissions or settings!")
|
||||||
|
print("Make sure Maloja has write and execute access to this directory.")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
<a href="/"><img style="display:block;" src="/favicon.png" /></a>
|
<a href="/"><img style="display:block;" src="/favicon.png" /></a>
|
||||||
</div>
|
</div>
|
||||||
<div id="right-side">
|
<div id="right-side">
|
||||||
<span><input id="searchinput" placeholder="Search for an artist or track..." oninput="search(this)" onblur="clearresults()" /></span>
|
<span><input id="searchinput" placeholder="Search for an album, artist or track..." oninput="search(this)" onblur="clearresults()" /></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
Here you can find tracks that currently have no album.<br/><br/>
|
Here you can find tracks that currently have no album.<br/><br/>
|
||||||
|
|
||||||
{% with list = dbc.get_tracks_without_album() %}
|
{% with list = dbc.get_tracks_without_album() %}
|
||||||
|
You have {{list|length}} tracks with no album.<br/><br/>
|
||||||
|
|
||||||
{% include 'partials/list_tracks.jinja' %}
|
{% include 'partials/list_tracks.jinja' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
@ -67,9 +67,9 @@
|
|||||||
<li>manually scrobble from track pages</li>
|
<li>manually scrobble from track pages</li>
|
||||||
<li>delete scrobbles</li>
|
<li>delete scrobbles</li>
|
||||||
<li>reparse scrobbles</li>
|
<li>reparse scrobbles</li>
|
||||||
<li>edit tracks and artists</li>
|
<li>edit tracks, albums and artists</li>
|
||||||
<li>merge tracks and artists</li>
|
<li>merge tracks, albums and artists</li>
|
||||||
<li>upload artist and track art by dropping a file on the existing image on an artist or track page</li>
|
<li>upload artist, album and track art by dropping a file on the existing image on an artist or track page</li>
|
||||||
<li>see more detailed error pages</li>
|
<li>see more detailed error pages</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
<div style="background-image:url('/favicon.png')"></div>
|
<div style="background-image:url('/favicon.png')"></div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text">
|
<td class="text">
|
||||||
<h1>{{ error_desc }}</h1><br/>
|
<h1>{{ error_desc | e }}</h1><br/>
|
||||||
{{ error_full_desc }}
|
{{ error_full_desc | e }}
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
{%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%}
|
{%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%}
|
||||||
|
|
||||||
{%- if cert -%}
|
{%- if cert -%}
|
||||||
<a href='{{ links.url(e.track) }}' class="hidelink certified certified_{{ cert }} smallcerticon" title="{{ e.track.title }} has reached {{ cert.capitalize() }} status">
|
<a href='{{ links.url(e.track) }}' class="hidelink certified certified_{{ cert }} smallcerticon" title="{{ e.track.title | e }} has reached {{ cert.capitalize() }} status">
|
||||||
{% include 'icons/cert_track.jinja' %}
|
{% include 'icons/cert_track.jinja' %}
|
||||||
</a>
|
</a>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
{%- if e.scrobbles >= settings.scrobbles_diamond_album -%}{% set cert = 'diamond' %}{%- endif -%}
|
{%- if e.scrobbles >= settings.scrobbles_diamond_album -%}{% set cert = 'diamond' %}{%- endif -%}
|
||||||
|
|
||||||
{%- if cert -%}
|
{%- if cert -%}
|
||||||
<a href='{{ links.url(e.album) }}' class="hidelink certified certified_{{ cert }} smallcerticon" title="{{ e.album.albumtitle }} has reached {{ cert.capitalize() }} status">
|
<a href='{{ links.url(e.album) }}' class="hidelink certified certified_{{ cert }} smallcerticon" title="{{ e.album.albumtitle | e }} has reached {{ cert.capitalize() }} status">
|
||||||
{% include 'icons/cert_album.jinja' %}
|
{% include 'icons/cert_album.jinja' %}
|
||||||
</a>
|
</a>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
@ -87,7 +87,7 @@
|
|||||||
{%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%}
|
{%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%}
|
||||||
|
|
||||||
{%- if cert -%}
|
{%- if cert -%}
|
||||||
<a href='{{ links.url(e.track) }}' class="hidelink certified certified_{{ cert }} smallcerticon" title="{{ e.track.title }} has reached {{ cert.capitalize() }} status">
|
<a href='{{ links.url(e.track) }}' class="hidelink certified certified_{{ cert }} smallcerticon" title="{{ e.track.title | e }} has reached {{ cert.capitalize() }} status">
|
||||||
{% include 'icons/cert_track.jinja' %}
|
{% include 'icons/cert_track.jinja' %}
|
||||||
</a>
|
</a>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "malojaserver"
|
name = "malojaserver"
|
||||||
version = "3.2.1"
|
version = "3.2.2"
|
||||||
description = "Self-hosted music scrobble database"
|
description = "Self-hosted music scrobble database"
|
||||||
readme = "./README.md"
|
readme = "./README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user