Merge branch 'newsettings'

This commit is contained in:
krateng 2021-12-21 23:12:05 +01:00
commit e4b63bb570
34 changed files with 411 additions and 390 deletions

6
.gitignore vendored
View File

@ -1,10 +1,12 @@
# generic temporary / dev files
*.pyc
*.sh
!/install_*.sh
*.note
*.xcf
nohup.out
*-old
# local actions
scripts/*
# currently not using
/screenshot*.png

View File

@ -136,11 +136,11 @@ to run the server in the foreground.
### Customization
* Have a look at the [available settings](settings.md) and specifiy your choices in `/etc/maloja/settings/settings.ini`. You can also set each of these settings as an environment variable with the prefix `MALOJA_` (e.g. `MALOJA_SKIP_SETUP`).
* 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`).
* If you have activated admin mode in your web interface, you can upload custom images for artists or tracks by simply dragging them onto the existing image on the artist or track page. You can also manage custom images directly in the file system - consult `images.info` in the `/var/lib/maloja/images` folder.
* To specify custom rules, consult the `rules.info` file in `/etc/maloja/rules`. You can also apply some predefined rules on the `/setup` page of your server.
* To specify custom rules, consult the `rules.info` file in `/etc/maloja/rules`. You can also apply some predefined rules on the `/admin_setup` page of your server.
## How to scrobble

View File

@ -15,7 +15,7 @@ python_version = ">=3.6"
requires = [
"bottle>=0.12.16",
"waitress>=1.3",
"doreah>=1.6.12",
"doreah>=1.7.1",
"nimrodel>=0.7.0",
"setproctitle>=1.1.10",
"wand>=0.5.4",

View File

@ -3,7 +3,7 @@ from ._exceptions import *
from .. import database
import datetime
from doreah.settings import get_settings
from ..globalconf import malojaconfig
class Listenbrainz(APIHandler):
@ -72,7 +72,7 @@ class Listenbrainz(APIHandler):
if token not in database.allAPIkeys():
raise InvalidAuthException()
else:
return 200,{"code":200,"message":"Token valid.","valid":True,"user_name":get_settings("NAME") or 'Maloja User'}
return 200,{"code":200,"message":"Token valid.","valid":True,"user_name":malojaconfig["NAME"]}
def get_token_from_request_keys(self,keys):
if 'token' in keys:

View File

@ -1,5 +1,5 @@
from ..database import *
from doreah import settings
from ..globalconf import malojaconfig
from ..__pkginfo__ import version
from ..malojauri import uri_to_internal
from .. import utilities
@ -41,7 +41,7 @@ def server_info():
response.set_header("Content-Type","application/json")
return {
"name":settings.get_settings("NAME"),
"name":malojaconfig["NAME"],
"version":version,
"versionstring":".".join(str(n) for n in version),
"db_status":dbstatus
@ -61,7 +61,7 @@ def get_scrobbles_external(**keys):
offset = (k_amount.get('page') * k_amount.get('perpage')) if k_amount.get('perpage') is not math.inf else 0
result = result[offset:]
if k_amount.get('perpage') is not math.inf: result = result[:k_amount.get('perpage')]
return {"list":result}
@ -326,3 +326,10 @@ def add_picture(b64,artist:Multi=[],title=None):
def newrule(**keys):
tsv.add_entry(data_dir['rules']("webmade.tsv"),[k for k in keys])
#addEntry("rules/webmade.tsv",[k for k in keys])
@api.post("settings")
@authenticated_api
def set_settings(**keys):
from .. import globalconf
globalconf.malojaconfig.update(keys)

View File

@ -1,7 +1,7 @@
import re
#from . import utilities
from doreah import tsv, settings
from .globalconf import data_dir
from doreah import tsv
from .globalconf import data_dir, malojaconfig
import pkg_resources
# need to do this as a class so it can retain loaded settings from file
@ -62,13 +62,13 @@ class CleanerAgent:
#Delimiters used for extra artists, even when in the title field
#delimiters_feat = ["ft.","ft","feat.","feat","featuring","Ft.","Ft","Feat.","Feat","Featuring"]
delimiters_feat = settings.get_settings("DELIMITERS_FEAT")
delimiters_feat = malojaconfig["DELIMITERS_FEAT"]
#Delimiters in informal artist strings, spaces expected around them
#delimiters = ["vs.","vs","&"]
delimiters = settings.get_settings("DELIMITERS_INFORMAL")
delimiters = malojaconfig["DELIMITERS_FEAT"]
#Delimiters used specifically to tag multiple artists when only one tag field is available, no spaces used
#delimiters_formal = ["; ",";","/"]
delimiters_formal = settings.get_settings("DELIMITERS_FORMAL")
delimiters_formal = malojaconfig["DELIMITERS_FEAT"]
def parseArtists(self,a):
@ -76,7 +76,7 @@ class CleanerAgent:
res = [self.parseArtists(art) for art in a]
return [a for group in res for a in group]
if a.strip() in settings.get_settings("INVALID_ARTISTS"):
if a.strip() in malojaconfig["DELIMITERS_FEAT"]:
return []
if a.strip().lower() in self.rules_ignoreartist:
@ -135,7 +135,7 @@ class CleanerAgent:
t = re.sub(r" \(originally by .*?\)","",t)
t = re.sub(r" \(.*?Remaster.*?\)","",t)
for s in settings.get_settings("REMOVE_FROM_TITLE"):
for s in malojaconfig["DELIMITERS_FEAT"]:
if s in t:
t = t.replace(s,"")

View File

@ -1,100 +0,0 @@
# Do not change settings in this file
# Instead, simply write an entry with the same name in your own settings.ini file
# Category headers in [brackets] are only for organization and not necessary
[Directories]
# DATA_DIRECTORY determines the base directory. It should be specified as environment
# variable because this file itself is loaded from it.
# Configuration is always in this base directory. Other data defaults to be here
# too, but can be customized with the options below.
DIRECTORY_STATE = None # This is /var/lib/maloja per XDG
DIRECTORY_LOGS = None # this is /var/log/maloja per XDG
DIRECTORY_CACHE = None # this is /var/cache/maloja per XDG
[HTTP]
WEB_PORT = 42010
HOST = "::" # You most likely want either :: for IPv6 or 0.0.0.0 for IPv4 here
[Login]
DEFAULT_PASSWORD = none
FORCE_PASSWORD = none
# these are only meant for Docker containers
# on first start, set the environment variable MALOJA_DEFAULT_PASSWORD
# if you forgot and already generated a random password, you can overwrite it with MALOJA_FORCE_PASSWORD
[Third Party Services]
# order in which to use the metadata providers
# keep in mind that musicbrainz is rate-limited and should probably not be used first
METADATA_PROVIDERS = [lastfm,spotify,deezer,musicbrainz]
# whether to proxy scrobble to other services
SCROBBLE_LASTFM = false
CACHE_EXPIRE_NEGATIVE = 30 # after how many days negative results should be tried again
CACHE_EXPIRE_POSITIVE = 300 # after how many days positive results should be refreshed
THUMBOR_SERVER = None
THUMBOR_SECRET = ""
# Can be 'YouTube', 'YouTube Music', 'Spotify', 'Tidal', 'SoundCloud', 'Deezer', 'Amazon Music', 'Apple', 'Beatport', 'Bandcamp', 'Qobuz'
# Set to None to disable
TRACK_SEARCH_PROVIDER = None
[Database]
USE_DB_CACHE = yes
CACHE_DATABASE_SHORT = true
CACHE_DATABASE_PERM = true #more permanent cache for old timeranges
DB_CACHE_ENTRIES = 10000 #experiment with this depending on your RAM
DB_MAX_MEMORY = 75 # percentage of RAM utilization (whole container, not just maloja) that should trigger a flush
INVALID_ARTISTS = ["[Unknown Artist]","Unknown Artist","Spotify"]
REMOVE_FROM_TITLE = ["(Original Mix)","(Radio Edit)","(Album Version)","(Explicit Version)","(Bonus Track)"]
DELIMITERS_FEAT = ["ft.","ft","feat.","feat","featuring","Ft.","Ft","Feat.","Feat","Featuring"]
DELIMITERS_INFORMAL = ["vs.","vs","&"]
DELIMITERS_FORMAL = [";","/"]
USE_PARSE_PLUGINS = no
[Local Images]
USE_LOCAL_IMAGES = true
LOCAL_IMAGE_ROTATE = 3600 # when multiple images are present locally, how many seconds we wait between rotation
[Web Interface]
# what range is shown per default for the tile view on the start page
# can be week, month, year, alltime
DEFAULT_RANGE_CHARTS_ARTISTS = year
DEFAULT_RANGE_CHARTS_TRACKS = year
# same for pulse view
# can be day, week, month, year
DEFAULT_STEP_PULSE = month
# display top tiles on artist and track chart pages
CHARTS_DISPLAY_TILES = false
# this does not actually block any requests, it's just an interface feature t
# prevent visitors from mindlessly clicking on those options and hogging your cpu
DISCOURAGE_CPU_HEAVY_STATS = false
# Offset in hours to UTC
TIMEZONE = 0
TIME_FORMAT = "%d. %b %Y %I:%M %p" # use '%H:%M' instead of '%I:%M %p' for 24 hour clock
[Fluff]
# how many scrobbles a track needs to aquire this status
SCROBBLES_GOLD = 250
SCROBBLES_PLATINUM = 500
SCROBBLES_DIAMOND = 1000
# name for comparisons
NAME = None
[Misc]
SKIP_SETUP = no
LOGGING = true
DEV_MODE = false
# set this to true if your console output will be processed and should never change existing lines
CLEAN_OUTPUT = false

View File

@ -10,12 +10,11 @@ from .malojauri import uri_to_internal, internal_to_uri, compose_querystring
from .thirdparty import proxy_scrobble_all
from .__pkginfo__ import version
from .globalconf import data_dir
from .globalconf import data_dir, malojaconfig
# doreah toolkit
from doreah.logging import log
from doreah import tsv
from doreah import settings
from doreah.caching import Cache, DeepCache
from doreah.auth import authenticated_api, authenticated_api_with_alternate
from doreah.io import ProgressBar
@ -283,7 +282,7 @@ def info():
artists = {}
return {
"name":settings.get_settings("NAME"),
"name":malojaconfig["NAME"],
"artists":{
chartentry["artist"]:round(chartentry["scrobbles"] * 100 / totalscrobbles,3)
for chartentry in get_charts_artists() if chartentry["scrobbles"]/totalscrobbles >= 0
@ -476,7 +475,7 @@ def trackInfo(track):
scrobbles = c["scrobbles"]
position = c["rank"]
cert = None
threshold_gold, threshold_platinum, threshold_diamond = settings.get_settings("SCROBBLES_GOLD","SCROBBLES_PLATINUM","SCROBBLES_DIAMOND")
threshold_gold, threshold_platinum, threshold_diamond = malojaconfig["SCROBBLES_GOLD","SCROBBLES_PLATINUM","SCROBBLES_DIAMOND"]
if scrobbles >= threshold_diamond: cert = "diamond"
elif scrobbles >= threshold_platinum: cert = "platinum"
elif scrobbles >= threshold_gold: cert = "gold"
@ -742,7 +741,7 @@ def build_db():
scrobblenum = len(db)
log(f"Found {scrobblenum} scrobbles...")
usebar = not settings.get_settings("CLEAN_OUTPUT")
usebar = not malojaconfig["CLEAN_OUTPUT"]
if usebar: pbar = ProgressBar(max=scrobblenum,prefix="Loading scrobbles")
else:
n = 0
@ -860,7 +859,7 @@ def sync():
import copy
if settings.get_settings("USE_DB_CACHE"):
if malojaconfig["USE_DB_CACHE"]:
def db_query(**kwargs):
return db_query_cached(**kwargs)
def db_aggregate(**kwargs):
@ -872,8 +871,8 @@ else:
return db_aggregate_full(**kwargs)
csz = settings.get_settings("DB_CACHE_ENTRIES")
cmp = settings.get_settings("DB_MAX_MEMORY")
csz = malojaconfig["DB_CACHE_ENTRIES"]
cmp = malojaconfig["DB_MAX_MEMORY"]
try:
import psutil
use_psutil = True
@ -885,8 +884,8 @@ cache_query_perm = lru.LRU(csz)
cache_aggregate = lru.LRU(csz)
cache_aggregate_perm = lru.LRU(csz)
perm_caching = settings.get_settings("CACHE_DATABASE_PERM")
temp_caching = settings.get_settings("CACHE_DATABASE_SHORT")
perm_caching = malojaconfig["CACHE_DATABASE_PERM"]
temp_caching = malojaconfig["CACHE_DATABASE_SHORT"]
cachestats = {
"cache_query":{

View File

@ -1,8 +1,8 @@
import os
from doreah.settings import get_settings
from doreah.settings import config as settingsconfig
from doreah.configuration import Configuration
from doreah.configuration import types as tp
pthj = os.path.join
# if DATA_DIRECTORY is specified, this is the directory to use for EVERYTHING, no matter what
@ -17,102 +17,233 @@ pthj = os.path.join
# if not, use the first we have permissions for
# after we decide which to use, fix it in settings to avoid future heuristics
try:
HOME_DIR = os.environ["XDG_DATA_HOME"].split(":")[0]
assert os.path.exists(HOME_DIR)
except:
HOME_DIR = os.path.join(os.environ["HOME"],".local/share/")
usrfol = pthj(HOME_DIR,"maloja")
etccfg = '/etc/maloja'
varlib = '/var/lib/maloja'
varcac = '/var/cache/maloja'
varlog = '/var/log/maloja'
# USEFUL FUNCS
pthj = os.path.join
dir_settings = {
"config":None,
"state":None,
"logs":None,
"cache":None,
# "clients":None,
# "rules":None,
# "settings":None,
# "auth":None,
# "backups":None,
# "images":None,
# "scrobbles":None,
# "logs":None,
# "cache":None
def is_dir_usable(pth):
try:
os.makedirs(pth,exist_ok=True)
os.mknod(pthj(pth,".test"))
os.remove(pthj(pth,".test"))
return True
except:
return False
def get_env_vars(key,pathsuffix=[]):
return [pthj(pth,*pathsuffix) for pth in os.environ.get(key,'').split(':') if pth != '']
directory_info = {
"config":{
"sentinel":"rules",
"possible_folders":[
"/etc/maloja",
os.path.expanduser("~/.local/share/maloja")
],
"setting":"directory_config"
},
"cache":{
"sentinel":"dummy",
"possible_folders":[
"/var/cache/maloja",
os.path.expanduser("~/.local/share/maloja/cache")
],
"setting":"directory_cache"
},
"state":{
"sentinel":"scrobbles",
"possible_folders":[
"/var/lib/maloja",
os.path.expanduser("~/.local/share/maloja")
],
"setting":"directory_state"
},
"logs":{
"sentinel":"dbfix",
"possible_folders":[
"/var/log/maloja",
os.path.expanduser("~/.local/share/maloja/logs")
],
"setting":"directory_logs"
}
}
dir_options = {
"config":[
"/etc/maloja",
usrfol
],
"state":[
"/var/lib/maloja",
"/etc/maloja",
usrfol
],
"logs":[
"/var/log/maloja",
"/etc/maloja/logs",
pthj(usrfol,"logs")
],
"cache":[
"/var/cache/maloja",
"/etc/maloja/cache",
pthj(usrfol,"cache")
]
}
# function that
# 1) checks if folder has been specified by user
# 2) if not, checks if one has been in use before and writes it to dict/config
# 3) if not, determines which to use and writes it to dict/config
# returns determined folder
def find_good_folder(datatype,configobject):
info = directory_info[datatype]
sentinels = {
"config":"settings",
"state":"scrobbles",
"logs":None,
"cache":None,
}
# check each possible folder if its used
for p in info['possible_folders']:
if os.path.exists(pthj(p,info['sentinel'])):
#print(p,"has been determined as maloja's folder for",datatype)
configobject[info['setting']] = p
return p
# check environ variables
stng_data = get_settings("DATA_DIRECTORY",files=[],environ_prefix="MALOJA_")
if stng_data is not None:
dir_settings['config'] = stng_data
dir_settings['state'] = stng_data
dir_settings['cache'] = pthj(stng_data,'cache')
dir_settings['logs'] = pthj(stng_data,'logs')
#print("Could not find previous",datatype,"folder")
# check which one we can use
for p in info['possible_folders']:
if is_dir_usable(p):
#print(p,"has been selected as maloja's folder for",datatype)
configobject[info['setting']] = p
return p
#print("No folder can be used for",datatype)
#print("This should not happen!")
### 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")
if maloja_dir_config is None:
maloja_dir_config = find_good_folder('config',{})
found_new_config_dir = True
else:
dir_settings['config'], dir_settings['state'], dir_settings['cache'], dir_settings['logs'] = get_settings("DIRECTORY_CONFIG","DIRECTORY_STATE","DIRECTORY_LOGS","DIRECTORY_CACHE",files=[],environ_prefix="MALOJA_")
# as soon as we know the config directory, we can load from settings file
if dir_settings['config'] is not None:
settingsfiles = [pthj(dir_settings['config'],'settings','default.ini'),pthj(dir_settings['config'],'settings','settings.ini')]
dir_settings['config'], dir_settings['state'], dir_settings['cache'], dir_settings['logs'] = get_settings("DIRECTORY_CONFIG","DIRECTORY_STATE","DIRECTORY_LOGS","DIRECTORY_CACHE",files=settingsfiles,environ_prefix="MALOJA_")
found_new_config_dir = False
# remember whether we had to find our config dir or it was user-specified
oldsettingsfile = pthj(maloja_dir_config,"settings","settings.ini")
newsettingsfile = pthj(maloja_dir_config,"settings.ini")
if os.path.exists(oldsettingsfile):
os.rename(oldsettingsfile,newsettingsfile)
# now to the stuff no setting has explicitly defined
for dirtype in dir_settings:
if dir_settings[dirtype] is None:
for option in dir_options[dirtype]:
if os.path.exists(option):
# check if this is really the directory used for this category (/etc/maloja could be used for state or just config)
if sentinels[dirtype] is None or os.path.exists(pthj(option,sentinels[dirtype])):
dir_settings[dirtype] = option
break
# if no directory seems to exist, use the first writable one
for dirtype in dir_settings:
if dir_settings[dirtype] is None:
for option in dir_options[dirtype]:
try:
os.makedirs(option,exist_ok=True)
os.mknod(pthj(option,".test"))
os.remove(pthj(option,".test"))
dir_settings[dirtype] = option
break
except:
pass
### STEP 2 - create settings object
assert all((dir_settings[s] is not None) for s in dir_settings)
malojaconfig = Configuration(
settings={
"Setup":{
"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_state":(tp.String(), "State Directory", "/var/lib/maloja", "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_cache":(tp.String(), "Cache Directory", "/var/cache/maloja", "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."),
"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.")
},
"Debug":{
"logging":(tp.Boolean(), "Enable Logging", True),
"dev_mode":(tp.Boolean(), "Enable developer mode", False),
},
"Network":{
"host":(tp.String(), "Host", "::", "Host for your server - most likely :: for IPv6 or 0.0.0.0 for IPv4"),
"port":(tp.Integer(), "Port", 42010),
},
"Technical":{
"cache_expire_positive":(tp.Integer(), "Image Cache Expiration", 300, "Days until images are refetched"),
"cache_expire_negative":(tp.Integer(), "Image Cache Negative Expiration", 30, "Days until failed image fetches are reattempted"),
"use_db_cache":(tp.Boolean(), "Use DB Cache", True),
"cache_database_short":(tp.Boolean(), "Use volatile Database Cache", True),
"cache_database_perm":(tp.Boolean(), "Use permanent Database Cache", True),
"db_cache_entries":(tp.Integer(), "Maximal Cache entries", 10000),
"db_max_memory":(tp.Integer(max=100,min=20), "RAM Percentage Theshold", 75, "Maximal percentage of RAM that should be used by whole system before Maloja discards cache entries. Use a higher number if your Maloja runs on a dedicated instance (e.g. a container)")
},
"Fluff":{
"scrobbles_gold":(tp.Integer(), "Scrobbles for Gold", 250, "How many scrobbles a track needs to be considered 'Gold' status"),
"scrobbles_platinum":(tp.Integer(), "Scrobbles for Platinum", 500, "How many scrobbles a track needs to be considered 'Platinum' status"),
"scrobbles_diamond":(tp.Integer(), "Scrobbles for Diamond", 1000, "How many scrobbles a track needs to be considered 'Diamond' status"),
"name":(tp.String(), "Name", "Generic Maloja User")
},
"Third Party Services":{
"metadata_providers":(tp.List(tp.String()), "Metadata Providers", ['lastfm','spotify','deezer','musicbrainz'], "Which metadata providers should be used in what order. Musicbrainz is rate-limited and should not be used first."),
"scrobble_lastfm":(tp.Boolean(), "Proxy-Scrobble to Last.fm", False),
"lastfm_api_key":(tp.String(), "Last.fm API Key", None),
"lastfm_api_secret":(tp.String(), "Last.fm API Secret", None),
"spotify_api_id":(tp.String(), "Spotify API ID", None),
"spotify_api_secret":(tp.String(), "Spotify API Secret", None),
"lastfm_api_key":(tp.String(), "Last.fm API Key", None),
"audiodb_api_key":(tp.String(), "TheAudioDB API Key", None),
"track_search_provider":(tp.String(), "Track Search Provider", None),
"send_stats":(tp.Boolean(), "Send Statistics", None),
},
"Database":{
"invalid_artists":(tp.Set(tp.String()), "Invalid Artists", ["[Unknown Artist]","Unknown Artist","Spotify"], "Artists that should be discarded immediately"),
"remove_from_title":(tp.Set(tp.String()), "Remove from Title", ["(Original Mix)","(Radio Edit)","(Album Version)","(Explicit Version)","(Bonus Track)"], "Phrases that should be removed from song titles"),
"delimiters_feat":(tp.Set(tp.String()), "Featuring Delimiters", ["ft.","ft","feat.","feat","featuring","Ft.","Ft","Feat.","Feat","Featuring"], "Delimiters used for extra artists, even when in the title field"),
"delimiters_informal":(tp.Set(tp.String()), "Informal Delimiters", ["vs.","vs","&"], "Delimiters in informal artist strings with spaces expected around them"),
"delimiters_formal":(tp.Set(tp.String()), "Formal Delimiters", [";","/"], "Delimiters used to tag multiple artists when only one tag field is available")
},
"Web Interface":{
"default_range_charts_artists":(tp.Choice({'alltime':'All Time','year':'Year','month':"Month",'week':'Week'}), "Default Range Artist Charts", "year"),
"default_range_charts_tracks":(tp.Choice({'alltime':'All Time','year':'Year','month':"Month",'week':'Week'}), "Default Range Track Charts", "year"),
"default_step_pulse":(tp.Choice({'year':'Year','month':"Month",'week':'Week','day':'Day'}), "Default Pulse Step", "month"),
"charts_display_tiles":(tp.Boolean(), "Display Chart Tiles", False),
"discourage_cpu_heavy_stats":(tp.Boolean(), "Discourage CPU-heavy stats", False, "Prevent visitors from mindlessly clicking on CPU-heavy options. Does not actually disable them for malicious actors!"),
"use_local_images":(tp.Boolean(), "Use Local Images", True),
"local_image_rotate":(tp.Integer(), "Local Image Rotate", 3600),
"timezone":(tp.Integer(), "UTC Offset", 0),
"time_format":(tp.String(), "Time Format", "%d. %b %Y %I:%M %p")
}
},
configfile=newsettingsfile,
save_endpoint="/apis/mlj_1/settings",
env_prefix="MALOJA_"
)
if found_new_config_dir:
malojaconfig["DIRECTORY_CONFIG"] = maloja_dir_config
# this really doesn't matter because when are we gonna load info about where
# the settings file is stored from the settings file
# but oh well
malojaconfig.render_help(pthj(maloja_dir_config,"settings.md"),
top_text='''If you wish to adjust settings in the settings.ini file, do so while the server
is not running in order to avoid data being overwritten.
Technically, each setting can be set via environment variable or the settings
file - simply add the prefix `MALOJA_` for environment variables. It is recommended
to use the settings file where possible and not configure each aspect of your
server via environment variables!''')
### STEP 3 - check all possible folders for files (old installation)
for datatype in ("state","cache","logs"):
# obviously default values shouldn't trigger this
# if user has nothing specified, we need to use this
if malojaconfig.get_specified(directory_info[datatype]['setting']) is None and malojaconfig.get_specified('DATA_DIRECTORY') is None:
find_good_folder(datatype,malojaconfig)
### STEP 4 - this is where all the guessing about previous installation ends
### we have our definite settings and are now just generating the real
### folder names for everything
if malojaconfig['DATA_DIRECTORY'] is None:
dir_settings = {
"config":malojaconfig['DIRECTORY_CONFIG'],
"state":malojaconfig['DIRECTORY_STATE'],
"cache":malojaconfig['DIRECTORY_CACHE'],
"logs":malojaconfig['DIRECTORY_LOGS'],
}
else:
dir_settings = {
"config":malojaconfig['DATA_DIRECTORY'],
"state":malojaconfig['DATA_DIRECTORY'],
"cache":pthj(malojaconfig['DATA_DIRECTORY'],"cache"),
"logs":pthj(malojaconfig['DATA_DIRECTORY'],"logs"),
}
data_directories = {
@ -122,7 +253,7 @@ data_directories = {
"scrobbles":pthj(dir_settings['state'],"scrobbles"),
"rules":pthj(dir_settings['config'],"rules"),
"clients":pthj(dir_settings['config'],"clients"),
"settings":pthj(dir_settings['config'],"settings"),
"settings":pthj(dir_settings['config']),
"css":pthj(dir_settings['config'],"custom_css"),
"logs":pthj(dir_settings['logs']),
"cache":pthj(dir_settings['cache']),
@ -142,13 +273,6 @@ data_dir = {
from doreah import config
config(
settings={
"files":[
data_dir['settings']("default.ini"),
data_dir['settings']("settings.ini")
],
"environ_prefix":"MALOJA_"
},
caching={
"folder": data_dir['cache']()
},
@ -157,36 +281,19 @@ config(
"cookieprefix":"maloja",
"stylesheets":["/style.css"],
"dbfile":data_dir['auth']("auth.ddb")
}
)
# because we loaded a doreah module already before setting the config, we need to to that manually
settingsconfig._readpreconfig()
config(
},
logging={
"logfolder": data_dir['logs']() if get_settings("LOGGING") else None
"logfolder": data_dir['logs']() if malojaconfig["LOGGING"] else None
},
regular={
"autostart": False,
"offset": get_settings("TIMEZONE") or 0
"offset": malojaconfig["TIMEZONE"]
}
)
settingsconfig._readpreconfig()
# thumbor
THUMBOR_SERVER, THUMBOR_SECRET = get_settings("THUMBOR_SERVER","THUMBOR_SECRET")
try:
USE_THUMBOR = THUMBOR_SERVER is not None and THUMBOR_SECRET is not None
if USE_THUMBOR:
from libthumbor import CryptoURL
THUMBOR_GENERATOR = CryptoURL(key=THUMBOR_SECRET)
OWNURL = get_settings("PUBLIC_URL")
assert OWNURL is not None
except:
USE_THUMBOR = False
log("Thumbor could not be initialized. Is libthumbor installed?")
# what the fuck did i just write
# this spaghetti file is proudly sponsored by the rice crackers i'm eating at the
# moment as well as some cute chinese girl whose asmr i'm listening to in the
# background. and now to bed!

View File

@ -1,8 +1,8 @@
from .. import database_packed
from . import filters
from ..globalconf import malojaconfig
from .. import database, database_packed, malojatime, utilities, malojauri
from doreah import settings
from doreah.regular import repeatdaily
import urllib
@ -30,7 +30,7 @@ def update_jinja_environment():
"malojatime": malojatime,
"utilities": utilities,
"mlj_uri": malojauri,
"settings": settings.get_settings,
"settings": malojaconfig,
# external
"urllib": urllib,
"math":math,

View File

@ -2,10 +2,11 @@ from datetime import timezone, timedelta, date, time, datetime
from calendar import monthrange
from os.path import commonprefix
import math
from doreah.settings import get_settings
from .globalconf import malojaconfig
OFFSET = get_settings("TIMEZONE")
OFFSET = malojaconfig["TIMEZONE"]
TIMEZONE = timezone(timedelta(hours=OFFSET))
UTC = timezone.utc
@ -486,7 +487,7 @@ def timestamp_desc(t,short=False):
timeobj = datetime.fromtimestamp(t,tz=TIMEZONE)
if not short: return timeobj.strftime(get_settings("TIME_FORMAT"))
if not short: return timeobj.strftime(malojaconfig["TIME_FORMAT"])
difference = int(datetime.now().timestamp() - t)

View File

@ -47,7 +47,7 @@ def start():
sp = subprocess.Popen(["python3","-m","maloja.proccontrol.supervisor"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
print(col["green"]("Maloja started!"))
port = settings.get_settings("WEB_PORT")
port = malojaconfig["WEB_PORT"]
print("Visit your server address (Port " + str(port) + ") to see your web interface. Visit /admin_setup to get started.")
print("If you're installing this on your local machine, these links should get you there:")

View File

@ -1,11 +1,10 @@
import pkg_resources
from distutils import dir_util
from doreah import settings
from doreah.io import col, ask, prompt
from doreah import auth
import os
from ..globalconf import data_dir, dir_settings
from ..globalconf import data_dir, dir_settings, malojaconfig
# EXTERNAL API KEYS
@ -32,19 +31,19 @@ def randomstring(length=32):
def setup():
copy_initial_local_files()
SKIP = settings.get_settings("SKIP_SETUP")
SKIP = malojaconfig["SKIP_SETUP"]
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 apikeys:
key = settings.get_settings(k)
key = malojaconfig[k]
if key is False:
print("\t" + "Currently not using a " + apikeys[k] + " for image display.")
print("\t" + "Currently not using a " + col['red'](apikeys[k]) + " for image display.")
elif key is None or key == "ASK":
print("\t" + "Please enter your " + col['gold'](apikeys[k]) + ". If you do not want to use one at this moment, simply leave this empty and press Enter.")
key = prompt("",types=(str,),default=False,skip=SKIP)
settings.update_settings(data_dir['settings']("settings.ini"),{k:key},create_new=True)
malojaconfig[k] = key
else:
print("\t" + apikeys[k] + " found.")
print("\t" + col['lawngreen'](apikeys[k]) + " found.")
# OWN API KEY
@ -57,8 +56,7 @@ def setup():
keyfile.write(key + "\t" + "Default Generated Key")
# PASSWORD
defaultpassword = settings.get_settings("DEFAULT_PASSWORD")
forcepassword = settings.get_settings("FORCE_PASSWORD")
forcepassword = malojaconfig["FORCE_PASSWORD"]
# this is mainly meant for docker, supply password via environment variable
if forcepassword is not None:
@ -67,25 +65,17 @@ def setup():
print("Password has been set.")
elif auth.defaultuser.checkpw("admin"):
# if the actual pw is admin, it means we've never set this up properly (eg first start after update)
if defaultpassword is None:
# non-docker installation or user didn't set environment variable
defaultpassword = randomstring(32)
newpw = prompt("Please set a password for web backend access. Leave this empty to generate a random password.",skip=SKIP,secret=True)
if newpw is None:
newpw = defaultpassword
print("Generated password:",newpw)
else:
# docker installation (or settings file, but don't do that)
# we still 'ask' the user to set one, but for docker this will be skipped
newpw = prompt("Please set a password for web backend access. Leave this empty to use the default password.",skip=SKIP,default=defaultpassword,secret=True)
auth.defaultuser.setpw(newpw)
if settings.get_settings("NAME") is None:
name = prompt("Please enter your name. This will be displayed e.g. when comparing your charts to another user. Leave this empty if you would not like to specify a name right now.",default="Generic Maloja User",skip=SKIP)
settings.update_settings(data_dir['settings']("settings.ini"),{"NAME":name},create_new=True)
newpw = prompt("Please set a password for web backend access. Leave this empty to generate a random password.",skip=SKIP,secret=True)
if newpw is None:
newpw = randomstring(32)
print("Generated password:",newpw)
if settings.get_settings("SEND_STATS") is None:
auth.defaultuser.setpw(newpw)
if malojaconfig["SEND_STATS"] is None:
answer = ask("I would like to know how many people use Maloja. Would it be okay to send a daily ping to my server (this contains no data that isn't accessible via your web interface already)?",default=True,skip=SKIP)
if answer:
settings.update_settings(data_dir['settings']("settings.ini"),{"SEND_STATS":True,"PUBLIC_URL":None},create_new=True)
malojaconfig["SEND_STATS"] = True
malojaconfig["PUBLIC_URL"] = None
else:
settings.update_settings(data_dir['settings']("settings.ini"),{"SEND_STATS":False},create_new=True)
malojaconfig["SEND_STATS"] = False

View File

@ -5,8 +5,8 @@ import subprocess
import setproctitle
import signal
from doreah.logging import log
from doreah.settings import get_settings
from .globalconf import malojaconfig
from .control import getInstance
@ -33,7 +33,7 @@ def start():
while True:
log("Maloja is not running, starting...",module="supervisor")
if get_settings("UPDATE_AFTER_CRASH"):
if malojaconfig["UPDATE_AFTER_CRASH"]:
update()
process = start()

View File

@ -17,10 +17,10 @@ from . import malojauri
from .utilities import resolveImage
from .malojauri import uri_to_internal, remove_identical, compose_querystring
from . import globalconf
from .globalconf import malojaconfig
from .jinjaenv.context import jinja_environment
from jinja2.exceptions import TemplateNotFound
# doreah toolkit
from doreah import settings
from doreah.logging import log
from doreah.timing import Clock
from doreah import auth
@ -43,11 +43,8 @@ import urllib
### TECHNICAL SETTINGS
#####
#settings.config(files=["settings/default.ini","settings/settings.ini"])
#settings.update("settings/default.ini","settings/settings.ini")
MAIN_PORT = settings.get_settings("WEB_PORT")
HOST = settings.get_settings("HOST")
PORT = malojaconfig["PORT"]
HOST = malojaconfig["HOST"]
THREADS = 24
BaseRequest.MEMFILE_MAX = 15 * 1024 * 1024
@ -87,8 +84,10 @@ css = generate_css()
#####
def clean_html(inp):
if settings.get_settings("DEV_MODE"): return inp
else: return html_minify(inp)
return inp
#if malojaconfig["DEV_MODE"]: return inp
#else: return html_minify(inp)
@ -215,7 +214,7 @@ def static_image(pth):
def get_css():
response.content_type = 'text/css'
global css
if settings.get_settings("DEV_MODE"): css = generate_css()
if malojaconfig["DEV_MODE"]: css = generate_css()
return css
@ -247,6 +246,7 @@ def static_html(name):
LOCAL_CONTEXT = {
"adminmode":adminmode,
"config":malojaconfig,
"apikey":request.cookies.get("apikey") if adminmode else None,
"_urikeys":keys, #temporary!
}
@ -259,7 +259,7 @@ def static_html(name):
except (ValueError, IndexError) as e:
abort(404,"This Artist or Track does not exist")
if settings.get_settings("DEV_MODE"): jinja_environment.cache.clear()
if malojaconfig["DEV_MODE"]: jinja_environment.cache.clear()
log("Generated page {name} in {time:.5f}s".format(name=name,time=clock.stop()),module="debug_performance")
return clean_html(res)
@ -326,7 +326,7 @@ def run_server():
try:
#run(webserver, host=HOST, port=MAIN_PORT, server='waitress')
waitress.serve(webserver, host=HOST, port=MAIN_PORT, threads=THREADS)
waitress.serve(webserver, host=HOST, port=PORT, threads=THREADS)
except OSError:
log("Error. Is another Maloja process already running?")
raise

View File

@ -10,9 +10,9 @@ import xml.etree.ElementTree as ElementTree
import json
import urllib.parse, urllib.request
import base64
from doreah.settings import get_settings
from doreah.logging import log
from ..globalconf import malojaconfig
services = {
@ -69,7 +69,7 @@ class GenericInterface:
# populate from settings file once on creation
# avoid constant disk access, restart on adding services is acceptable
for key in self.settings:
self.settings[key] = get_settings(self.settings[key])
self.settings[key] = malojaconfig[self.settings[key]]
self.authorize()
# this makes sure that of every class we define, we immediately create an
@ -105,7 +105,7 @@ class ProxyScrobbleInterface(GenericInterface,abstract=True):
def active_proxyscrobble(self):
return (
all(self.settings[key] not in [None,"ASK",False] for key in self.proxyscrobble["required_settings"]) and
get_settings(self.proxyscrobble["activated_setting"])
malojaconfig[self.proxyscrobble["activated_setting"]]
)
def scrobble(self,artists,title,timestamp):
@ -130,7 +130,7 @@ class ImportInterface(GenericInterface,abstract=True):
def active_import(self):
return (
all(self.settings[key] not in [None,"ASK",False] for key in self.scrobbleimport["required_settings"]) and
get_settings(self.scrobbleimport["activated_setting"])
malojaconfig[self.scrobbleimport["activated_setting"]]
)
@ -147,7 +147,7 @@ class MetadataInterface(GenericInterface,abstract=True):
def active_metadata(self):
return (
all(self.settings[key] not in [None,"ASK",False] for key in self.metadata["required_settings"]) and
self.identifier in get_settings("METADATA_PROVIDERS")
self.identifier in malojaconfig["METADATA_PROVIDERS"]
)
def get_image_track(self,track):
@ -228,5 +228,5 @@ from . import *
services["metadata"].sort(
key=lambda provider : get_settings("METADATA_PROVIDERS").index(provider.identifier)
key=lambda provider : malojaconfig["METADATA_PROVIDERS"].index(provider.identifier)
)

View File

@ -1,8 +1,7 @@
from .. import globalconf
from ..globalconf import data_dir
from ..globalconf import data_dir, malojaconfig
from .. import thirdparty
from doreah import settings, caching
from doreah import caching
from doreah.logging import log
import itertools
@ -15,27 +14,13 @@ import re
import datetime
if globalconf.USE_THUMBOR:
def thumborize(url):
if url.startswith("/"): url = globalconf.OWNURL + url
encrypted_url = globalconf.THUMBOR_GENERATOR.generate(
width=300,
height=300,
smart=True,
image_url=url
)
return globalconf.THUMBOR_SERVER + encrypted_url
else:
def thumborize(url):
return url
### Caches
cacheage = settings.get_settings("CACHE_EXPIRE_POSITIVE") * 24 * 3600
cacheage_neg = settings.get_settings("CACHE_EXPIRE_NEGATIVE") * 24 * 3600
cacheage = malojaconfig["CACHE_EXPIRE_POSITIVE"] * 24 * 3600
cacheage_neg = malojaconfig["CACHE_EXPIRE_NEGATIVE"] * 24 * 3600
artist_cache = caching.Cache(name="imgcache_artists",maxage=cacheage,maxage_negative=cacheage_neg,persistent=True)
track_cache = caching.Cache(name="imgcache_tracks",maxage=cacheage,maxage_negative=cacheage_neg,persistent=True)
@ -133,7 +118,7 @@ def local_files(artist=None,artists=None,title=None):
# these caches are there so we don't check all files every time, but return the same one
local_cache_age = settings.get_settings("LOCAL_IMAGE_ROTATE")
local_cache_age = malojaconfig["LOCAL_IMAGE_ROTATE"]
local_artist_cache = caching.Cache(maxage=local_cache_age)
local_track_cache = caching.Cache(maxage=local_cache_age)
@ -142,9 +127,9 @@ def getTrackImage(artists,title,fast=False):
hashable_track = (frozenset(artists),title)
# Prio 1: Local image
if settings.get_settings("USE_LOCAL_IMAGES"):
if malojaconfig["USE_LOCAL_IMAGES"]:
try:
return thumborize(local_track_cache.get(hashable_track))
return local_track_cache.get(hashable_track)
except:
images = local_files(artists=artists,title=title)
if len(images) != 0:
@ -156,7 +141,7 @@ def getTrackImage(artists,title,fast=False):
# Prio 2: Cached remote link
try:
result = track_cache.get(hashable_track)
if result is not None: return thumborize(result)
if result is not None: return result
# if we have cached the nonexistence of that image, we immediately return
# the redirect to the artist and let the resolver handle it
# (even if we're not in a fast lookup right now)
@ -179,7 +164,7 @@ def getTrackImage(artists,title,fast=False):
# cache results (even negative ones)
track_cache.add(hashable_track,result)
# return either result or redirect to artist
if result is not None: return thumborize(result)
if result is not None: return result
for a in artists:
res = getArtistImage(artist=a,fast=False)
if res != "": return res
@ -189,21 +174,21 @@ def getTrackImage(artists,title,fast=False):
def getArtistImage(artist,fast=False):
# Prio 1: Local image
if settings.get_settings("USE_LOCAL_IMAGES"):
if malojaconfig["USE_LOCAL_IMAGES"]:
try:
return thumborize(local_artist_cache.get(artist))
return local_artist_cache.get(artist)
except:
images = local_files(artist=artist)
if len(images) != 0:
res = random.choice(images)
local_artist_cache.add(artist,res)
return thumborize(urllib.parse.quote(res))
return urllib.parse.quote(res)
# Prio 2: Cached remote link
try:
result = artist_cache.get(artist)
if result is not None: return thumborize(result)
if result is not None: return result
else: return ""
# none means non-existence is cached, return empty
except:
@ -219,7 +204,7 @@ def getArtistImage(artist,fast=False):
result = thirdparty.get_image_artist_all(artist)
# cache results (even negative ones)
artist_cache.add(artist,result) #cache_artist(artist,result)
if result is not None: return thumborize(result)
if result is not None: return result
else: return ""
def getTrackImages(trackobjectlist,fast=False):

View File

@ -1,8 +1,8 @@
from ..__pkginfo__ import version
from ..malojatime import ranges, thisweek, thisyear
from ..globalconf import malojaconfig
from doreah.regular import yearly, daily
from doreah import settings
from doreah.logging import log
import datetime
@ -87,7 +87,7 @@ def update_weekly():
@daily
def send_stats():
if settings.get_settings("SEND_STATS"):
if malojaconfig["SEND_STATS"]:
log("Sending daily stats report...")
@ -98,8 +98,8 @@ def send_stats():
"method":"POST",
"headers":{"Content-Type": "application/json"},
"data":json.dumps({
"name":settings.get_settings("NAME"),
"url":settings.get_settings("PUBLIC_URL"),
"name":malojaconfig["NAME"],
"url":malojaconfig["PUBLIC_URL"],
"version":".".join(str(d) for d in version),
"artists":len(ARTISTS),
"tracks":len(TRACKS),

View File

@ -35,6 +35,11 @@
<span style="opacity:0.5;">Database Maintenance</span>
{% else %}
<a href="/admin_issues">Database Maintenance</a>
{% endif %} |
{% if page=='admin_settings' %}
<span style="opacity:0.5;">Settings</span>
{% else %}
<a href="/admin_settings">Settings</a>
{% endif %}
</span>
<br/><br/>

View File

@ -48,13 +48,13 @@
<span>Get your own Maloja scrobble server on <a target="_blank" rel="noopener noreferrer" href="https://pypi.org/project/malojaserver/">PyPI</a></span>
</div>
<div>
<a href="/"><span style="font-weight:bold;">Maloja {% if settings("DEV_MODE") %}[Developer Mode]{% endif %}</span></a>
<a href="/"><span style="font-weight:bold;">Maloja {% if settings["DEV_MODE"] %}[Developer Mode]{% endif %}</span></a>
</div>
<div>
<span><input id="searchinput" placeholder="Search for an artist or track..." oninput="search(this)" onblur="clearresults()" /></span>
</div>
<span id="resultwrap" class="hide">
<div id="resultwrap" class="hide">
<div class="searchresults">
<span>Artists</span>
<table class="searchresults_artists" id="searchresults_artists">
@ -64,7 +64,7 @@
<table class="searchresults_tracks" id="searchresults_tracks">
</table>
</div>
</span>
</div>
</div>
<a href="/admin_overview"><div title="Server Administration" id="settingsicon">

View File

@ -0,0 +1,8 @@
{% set page ='admin_settings' %}
{% extends "abstracts/admin.jinja" %}
{% block title %}Maloja - Settings{% endblock %}
{% block maincontent %}
{{ config.html() }}
{% endblock %}

View File

@ -35,7 +35,7 @@
</tr>
</table>
{% if settings('CHARTS_DISPLAY_TILES') %}
{% if settings['CHARTS_DISPLAY_TILES'] %}
{% include 'partials/charts_artists_tiles.jinja' %}
<br/><br/>
{% endif %}

View File

@ -37,7 +37,7 @@
</tr>
</table>
{% if settings('CHARTS_DISPLAY_TILES') %}
{% if settings['CHARTS_DISPLAY_TILES'] %}
{% include 'partials/charts_tracks_tiles.jinja' %}
<br/><br/>
{% endif %}

View File

@ -10,7 +10,7 @@
<td class='icon'><div style="background-image:url('{{ img }}')"></div></td>
{% if "artists" in entity %}
{% if settings('TRACK_SEARCH_PROVIDER') is not none %}
{% if settings['TRACK_SEARCH_PROVIDER'] %}
<td class='searchProvider'>{{ links.link_search(entity) }}</td>
{% endif %}
<td class='track'>

View File

@ -62,7 +62,7 @@
{% macro link_search(entity) -%}
{% set searchstr = (entity.artists + [entity.title]) | join(" ") | urlencode %}
{% set searchprovider = settings('TRACK_SEARCH_PROVIDER') | lower | replace(' ','') %}
{% set searchprovider = settings['TRACK_SEARCH_PROVIDER'] | lower | replace(' ','') %}
{% set searchproviders =
{
"youtube":'https://www.youtube.com/results?search_query=',

View File

@ -31,7 +31,7 @@
{% for r in xcurrent %}
{% if r.range == limitkeys.timerange %}
<span class='stat_selector' style='opacity:0.5;'>{{ r.localisation }}</span>
{% elif r.heavy and settings('DISCOURAGE_CPU_HEAVY_STATS') %}
{% elif r.heavy and settings['DISCOURAGE_CPU_HEAVY_STATS'] %}
<span class='stat_selector blocked' title="CPU heavy statistics are discouraged at the moment.">{{ r.localisation }}</span>
{% else %}
<a href='{{ mlj_uri.create_uri("",allkeys,{"timerange":r.range}) }}'><span class='stat_selector'>{{ r.localisation }}</span></a>
@ -49,7 +49,7 @@
{# {% if all(r.keys[k] == allkeys[k] for k in r.keys) %} #}
{% if r.replacekeys | map('compare_key_in_dicts',r.replacekeys,allkeys) | alltrue %}
<span class='stat_selector' style='opacity:0.5;'>{{ r.localisation }}</span>
{% elif r.heavy and settings('DISCOURAGE_CPU_HEAVY_STATS') %}
{% elif r.heavy and settings['DISCOURAGE_CPU_HEAVY_STATS'] %}
<span class='stat_selector blocked' title="CPU heavy statistics are discouraged at the moment.">{{ r.localisation }}</span>
{% else %}
<a href='{{ mlj_uri.create_uri("",allkeys,r.replacekeys) }}'><span class='stat_selector'>{{ r.localisation }}</span></a>

View File

@ -4,9 +4,9 @@
{% block scripts %}
<script>document.addEventListener('DOMContentLoaded',function() {
showRange('topartists','{{ settings("DEFAULT_RANGE_CHARTS_ARTISTS") }}');
showRange('toptracks','{{ settings("DEFAULT_RANGE_CHARTS_TRACKS") }}');
showRange('pulse','{{ settings("DEFAULT_STEP_PULSE") }}');
showRange('topartists','{{ settings["DEFAULT_RANGE_CHARTS_ARTISTS"] }}');
showRange('toptracks','{{ settings["DEFAULT_RANGE_CHARTS_TRACKS"] }}');
showRange('pulse','{{ settings["DEFAULT_STEP_PULSE"] }}');
})</script>
<script src="/rangeselect.js"></script>
<script src="/cookies.js"></script>

View File

@ -138,6 +138,20 @@ a.hidelink:hover {
/** INPUTS **/
input[type="text"], input[type="number"] {
background-color: transparent;
border: 0px;
border-bottom: 1px solid var(--text-color);
color: white;
}
input:focus {
outline: 0;
background-color: var(--base-color-accent-light);
}

View File

@ -1,9 +1,9 @@
bottle>=0.12.16
waitress>=1.3
doreah>=1.6.12
doreah>=1.7.1
nimrodel>=0.7.0
setproctitle>=1.1.10
wand>=0.5.4
jinja2>2.11
jinja2>=2.11
lru-dict>=1.1.6
css_html_js_minify>=2.5.5

View File

@ -3,53 +3,56 @@ Technically, each setting can be set via environment variable or the settings fi
Settings File | Environment Variable | Type | Description
------ | --------- | --------- | ---------
**Setup**
&nbsp; | `MALOJA_DATA_DIRECTORY` | String | Use this directory to store all application files. Useful for docker. Overwrites all individually specified directories below.
&nbsp; | `MALOJA_DIRECTORY_CONFIG` | String | Use this directory to store configuration files.
`DIRECTORY_STATE` | `MALOJA_DIRECTORY_STATE` | String | Use this directory to store state files.
`DIRECTORY_LOGS` | `MALOJA_DIRECTORY_LOGS` | String | Use this directory to store log files.
`DIRECTORY_CACHE` | `MALOJA_DIRECTORY_CACHE` | String | Use this directory to store cache files.
`SKIP_SETUP` | `MALOJA_SKIP_SETUP` | Boolean | Whether to make server startup non-interactive. Vital for docker.
&nbsp; | `MALOJA_FORCE_PASSWORD` | String | Sets password for admin login in web interface. This should normally be done via the interactive prompt.
`CLEAN_OUTPUT` | `MALOJA_CLEAN_OUTPUT` | Boolean | Avoid mutable console output. Use if console output will be redirected e.g. to a web interface.
`data_directory` | `MALOJA_DATA_DIRECTORY` | String | Folder for all user data. Overwrites all choices for specific directories.
`directory_config` | `MALOJA_DIRECTORY_CONFIG` | String | Folder for config data. Only applied when global data directory is not set.
`directory_state` | `MALOJA_DIRECTORY_STATE` | String | Folder for state data. Only applied when global data directory is not set.
`directory_logs` | `MALOJA_DIRECTORY_LOGS` | String | Folder for log data. Only applied when global data directory is not set.
`directory_cache` | `MALOJA_DIRECTORY_CACHE` | String | Folder for cache data. Only applied when global data directory is not set.
`skip_setup` | `MALOJA_SKIP_SETUP` | Boolean | Make server setup process non-interactive. Vital for Docker.
`force_password` | `MALOJA_FORCE_PASSWORD` | String | On startup, overwrite admin password with this one. This should usually only be done via environment variable in Docker.
`clean_output` | `MALOJA_CLEAN_OUTPUT` | Boolean | Use if console output will be redirected e.g. to a web interface.
**Debug**
`LOGGING` | `MALOJA_LOGGING` | Boolean | Enable logging
`DEV_MODE` | `MALOJA_DEV_MODE` | Boolean | Enable developer mode
`logging` | `MALOJA_LOGGING` | Boolean | Enable Logging
`dev_mode` | `MALOJA_DEV_MODE` | Boolean | Enable developer mode
**Network**
`host` | `MALOJA_HOST` | String | Host for your server - most likely :: for IPv6 or 0.0.0.0 for IPv4
`port` | `MALOJA_PORT` | Integer | Port
**Technical**
`WEB_PORT` | &nbsp; | Integer | HTTP port to use for your web interface and API
`HOST` | &nbsp; | String | Host for your server - most likely `::` for IPv6 or `0.0.0.0` for IPv4
`CACHE_EXPIRE_POSITIVE` | &nbsp; | Integer | Days until images are refetched
`CACHE_EXPIRE_NEGATIVE` | &nbsp; | Integer | Days until failed image fetches are reattempted
`USE_DB_CACHE` | &nbsp; | Boolean | Whether to use the Database Cache.
`CACHE_DATABASE_SHORT` | &nbsp; | Boolean | Whether to use the Volatile DB Cache.
`CACHE_DATABASE_PERM` | &nbsp; | Boolean | Whether to use the Permanent DB Cache.
`DB_CACHE_ENTRIES` | &nbsp; | Integer | Maximal entries of cache.
`DB_MAX_MEMORY` | &nbsp; | Integer | Maximal percentage of total RAM that should be used (by whole system) before Maloja discards cache entries. Use a higher number if your Maloja runs on a dedicated instance (e.g. a container)
`cache_expire_positive` | `MALOJA_CACHE_EXPIRE_POSITIVE` | Integer | Days until images are refetched
`cache_expire_negative` | `MALOJA_CACHE_EXPIRE_NEGATIVE` | Integer | Days until failed image fetches are reattempted
`use_db_cache` | `MALOJA_USE_DB_CACHE` | Boolean | Use DB Cache
`cache_database_short` | `MALOJA_CACHE_DATABASE_SHORT` | Boolean | Use volatile Database Cache
`cache_database_perm` | `MALOJA_CACHE_DATABASE_PERM` | Boolean | Use permanent Database Cache
`db_cache_entries` | `MALOJA_DB_CACHE_ENTRIES` | Integer | Maximal Cache entries
`db_max_memory` | `MALOJA_DB_MAX_MEMORY` | Integer | Maximal percentage of RAM that should be used by whole system before Maloja discards cache entries. Use a higher number if your Maloja runs on a dedicated instance (e.g. a container)
**Fluff**
`SCROBBLES_GOLD` | &nbsp; | Integer | How many scrobbles should be considered 'Gold' status for a track
`SCROBBLES_PLATINUM` | &nbsp; | Integer | How many scrobbles should be considered 'Platinum' status for a track
`SCROBBLES_DIAMOND` | &nbsp; | Integer | How many scrobbles should be considered 'Diamond' status for a track
`NAME` | &nbsp; | String | Your Name for display
`scrobbles_gold` | `MALOJA_SCROBBLES_GOLD` | Integer | How many scrobbles a track needs to be considered 'Gold' status
`scrobbles_platinum` | `MALOJA_SCROBBLES_PLATINUM` | Integer | How many scrobbles a track needs to be considered 'Platinum' status
`scrobbles_diamond` | `MALOJA_SCROBBLES_DIAMOND` | Integer | How many scrobbles a track needs to be considered 'Diamond' status
`name` | `MALOJA_NAME` | String | Name
**Third Party Services**
`METADATA_PROVIDERS` | &nbsp; | List (String) | Which metadata providers should be used in what order. Musicbrainz is rate-limited and should not be used first.
`SCROBBLE_LASTFM` | &nbsp; | Boolean | Proxy-scrobble to Last.fm
`LASTFM_API_KEY` | &nbsp; | String | API key for Last.fm. Necessary if proxy-scrobbling to Last.fm or using it as a metadata provider
`LASTFM_API_SECRET` | &nbsp; | String | API secret for Last.fm. Necessary if proxy-scrobbling to Last.fm or using it as a metadata provider
`SPOTIFY_API_ID` | &nbsp; | String | API ID for Spotify. Necessary if using it as a metadata provider.
`SPOTIFY_API_SECRET` | &nbsp; | String | API Secret for Spotify. Necessary if using it as a metadata provider.
`TRACK_SEARCH_PROVIDER` | &nbsp; | String | Provider for track search next to scrobbles. None to disable.
`THUMBOR_SERVER` | &nbsp; | String | URL of Thumbor server to serve custom artwork.
`THUMBOR_SECRET` | &nbsp; | String | Secret of Thumbor server
`metadata_providers` | `MALOJA_METADATA_PROVIDERS` | List | Which metadata providers should be used in what order. Musicbrainz is rate-limited and should not be used first.
`scrobble_lastfm` | `MALOJA_SCROBBLE_LASTFM` | Boolean | Proxy-Scrobble to Last.fm
`lastfm_api_key` | `MALOJA_LASTFM_API_KEY` | String | Last.fm API Key
`lastfm_api_secret` | `MALOJA_LASTFM_API_SECRET` | String | Last.fm API Secret
`spotify_api_id` | `MALOJA_SPOTIFY_API_ID` | String | Spotify API ID
`spotify_api_secret` | `MALOJA_SPOTIFY_API_SECRET` | String | Spotify API Secret
`audiodb_api_key` | `MALOJA_AUDIODB_API_KEY` | String | TheAudioDB API Key
`track_search_provider` | `MALOJA_TRACK_SEARCH_PROVIDER` | String | Track Search Provider
`send_stats` | `MALOJA_SEND_STATS` | Boolean | Send Statistics
**Database**
`INVALID_ARTISTS` | &nbsp; | List (String) | Artists that should be discarded immediately
`REMOVE_FROM_TITLE` | &nbsp; | List (String) | Phrases that should be removed from song titles
`DELIMITERS_FEAT` | &nbsp; | List (String) | Delimiters used for extra artists, even when in the title field
`DELIMITERS_INFORMAL` | &nbsp; | List (String) | Delimiters in informal artist strings with spaces expected around them
`DELIMITERS_FORMAL` | &nbsp; | List (String) | Delimiters used to tag multiple artists when only one tag field is available
`invalid_artists` | `MALOJA_INVALID_ARTISTS` | Set | Artists that should be discarded immediately
`remove_from_title` | `MALOJA_REMOVE_FROM_TITLE` | Set | Phrases that should be removed from song titles
`delimiters_feat` | `MALOJA_DELIMITERS_FEAT` | Set | Delimiters used for extra artists, even when in the title field
`delimiters_informal` | `MALOJA_DELIMITERS_INFORMAL` | Set | Delimiters in informal artist strings with spaces expected around them
`delimiters_formal` | `MALOJA_DELIMITERS_FORMAL` | Set | Delimiters used to tag multiple artists when only one tag field is available
**Web Interface**
`DEFAULT_RANGE_CHARTS_ARTISTS` | &nbsp; | String | What range is shown per default for the tile view on the start page
`DEFAULT_RANGE_CHARTS_TRACKS` | &nbsp; | String | What range is shown per default for the tile view on the start page
`DEFAULT_STEP_PULSE` | &nbsp; | String | What steps are shown per default for the pulse view on the start page
`CHARTS_DISPLAY_TILES` | &nbsp; | Boolean | Whether to show tiles on chart pages
`DISCOURAGE_CPU_HEAVY_STATS` | &nbsp; | Boolean | Prevent visitors from mindlessly clicking on CPU-heavy options. Does not actually disable them for malicious actors!
`USE_LOCAL_IMAGES` | &nbsp; | Boolean | Use local images if present
`LOCAL_IMAGE_ROTATE` | &nbsp; | Integer | How many seconds to wait between rotating local images
`default_range_charts_artists` | `MALOJA_DEFAULT_RANGE_CHARTS_ARTISTS` | Choice | Default Range Artist Charts
`default_range_charts_tracks` | `MALOJA_DEFAULT_RANGE_CHARTS_TRACKS` | Choice | Default Range Track Charts
`default_step_pulse` | `MALOJA_DEFAULT_STEP_PULSE` | Choice | Default Pulse Step
`charts_display_tiles` | `MALOJA_CHARTS_DISPLAY_TILES` | Boolean | Display Chart Tiles
`discourage_cpu_heavy_stats` | `MALOJA_DISCOURAGE_CPU_HEAVY_STATS` | Boolean | Prevent visitors from mindlessly clicking on CPU-heavy options. Does not actually disable them for malicious actors!
`use_local_images` | `MALOJA_USE_LOCAL_IMAGES` | Boolean | Use Local Images
`local_image_rotate` | `MALOJA_LOCAL_IMAGE_ROTATE` | Integer | Local Image Rotate
`timezone` | `MALOJA_TIMEZONE` | Integer | UTC Offset
`time_format` | `MALOJA_TIME_FORMAT` | String | Time Format