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 # generic temporary / dev files
*.pyc *.pyc
*.sh
!/install_*.sh
*.note *.note
*.xcf *.xcf
nohup.out nohup.out
*-old
# local actions
scripts/*
# currently not using # currently not using
/screenshot*.png /screenshot*.png

View File

@ -136,11 +136,11 @@ to run the server in the foreground.
### Customization ### 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. * 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 ## How to scrobble

View File

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

View File

@ -3,7 +3,7 @@ from ._exceptions import *
from .. import database from .. import database
import datetime import datetime
from doreah.settings import get_settings from ..globalconf import malojaconfig
class Listenbrainz(APIHandler): class Listenbrainz(APIHandler):
@ -72,7 +72,7 @@ class Listenbrainz(APIHandler):
if token not in database.allAPIkeys(): if token not in database.allAPIkeys():
raise InvalidAuthException() raise InvalidAuthException()
else: 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): def get_token_from_request_keys(self,keys):
if 'token' in keys: if 'token' in keys:

View File

@ -1,5 +1,5 @@
from ..database import * from ..database import *
from doreah import settings from ..globalconf import malojaconfig
from ..__pkginfo__ import version from ..__pkginfo__ import version
from ..malojauri import uri_to_internal from ..malojauri import uri_to_internal
from .. import utilities from .. import utilities
@ -41,7 +41,7 @@ def server_info():
response.set_header("Content-Type","application/json") response.set_header("Content-Type","application/json")
return { return {
"name":settings.get_settings("NAME"), "name":malojaconfig["NAME"],
"version":version, "version":version,
"versionstring":".".join(str(n) for n in version), "versionstring":".".join(str(n) for n in version),
"db_status":dbstatus "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 offset = (k_amount.get('page') * k_amount.get('perpage')) if k_amount.get('perpage') is not math.inf else 0
result = result[offset:] result = result[offset:]
if k_amount.get('perpage') is not math.inf: result = result[:k_amount.get('perpage')] if k_amount.get('perpage') is not math.inf: result = result[:k_amount.get('perpage')]
return {"list":result} return {"list":result}
@ -326,3 +326,10 @@ def add_picture(b64,artist:Multi=[],title=None):
def newrule(**keys): def newrule(**keys):
tsv.add_entry(data_dir['rules']("webmade.tsv"),[k for k in keys]) tsv.add_entry(data_dir['rules']("webmade.tsv"),[k for k in keys])
#addEntry("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 import re
#from . import utilities #from . import utilities
from doreah import tsv, settings from doreah import tsv
from .globalconf import data_dir from .globalconf import data_dir, malojaconfig
import pkg_resources import pkg_resources
# need to do this as a class so it can retain loaded settings from file # 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 used for extra artists, even when in the title field
#delimiters_feat = ["ft.","ft","feat.","feat","featuring","Ft.","Ft","Feat.","Feat","Featuring"] #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 in informal artist strings, spaces expected around them
#delimiters = ["vs.","vs","&"] #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 used specifically to tag multiple artists when only one tag field is available, no spaces used
#delimiters_formal = ["; ",";","/"] #delimiters_formal = ["; ",";","/"]
delimiters_formal = settings.get_settings("DELIMITERS_FORMAL") delimiters_formal = malojaconfig["DELIMITERS_FEAT"]
def parseArtists(self,a): def parseArtists(self,a):
@ -76,7 +76,7 @@ class CleanerAgent:
res = [self.parseArtists(art) for art in a] res = [self.parseArtists(art) for art in a]
return [a for group in res for a in group] 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 [] return []
if a.strip().lower() in self.rules_ignoreartist: 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" \(originally by .*?\)","",t)
t = re.sub(r" \(.*?Remaster.*?\)","",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: if s in t:
t = t.replace(s,"") 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 .thirdparty import proxy_scrobble_all
from .__pkginfo__ import version from .__pkginfo__ import version
from .globalconf import data_dir from .globalconf import data_dir, malojaconfig
# doreah toolkit # doreah toolkit
from doreah.logging import log from doreah.logging import log
from doreah import tsv from doreah import tsv
from doreah import settings
from doreah.caching import Cache, DeepCache from doreah.caching import Cache, DeepCache
from doreah.auth import authenticated_api, authenticated_api_with_alternate from doreah.auth import authenticated_api, authenticated_api_with_alternate
from doreah.io import ProgressBar from doreah.io import ProgressBar
@ -283,7 +282,7 @@ def info():
artists = {} artists = {}
return { return {
"name":settings.get_settings("NAME"), "name":malojaconfig["NAME"],
"artists":{ "artists":{
chartentry["artist"]:round(chartentry["scrobbles"] * 100 / totalscrobbles,3) chartentry["artist"]:round(chartentry["scrobbles"] * 100 / totalscrobbles,3)
for chartentry in get_charts_artists() if chartentry["scrobbles"]/totalscrobbles >= 0 for chartentry in get_charts_artists() if chartentry["scrobbles"]/totalscrobbles >= 0
@ -476,7 +475,7 @@ def trackInfo(track):
scrobbles = c["scrobbles"] scrobbles = c["scrobbles"]
position = c["rank"] position = c["rank"]
cert = None 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" if scrobbles >= threshold_diamond: cert = "diamond"
elif scrobbles >= threshold_platinum: cert = "platinum" elif scrobbles >= threshold_platinum: cert = "platinum"
elif scrobbles >= threshold_gold: cert = "gold" elif scrobbles >= threshold_gold: cert = "gold"
@ -742,7 +741,7 @@ def build_db():
scrobblenum = len(db) scrobblenum = len(db)
log(f"Found {scrobblenum} scrobbles...") 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") if usebar: pbar = ProgressBar(max=scrobblenum,prefix="Loading scrobbles")
else: else:
n = 0 n = 0
@ -860,7 +859,7 @@ def sync():
import copy import copy
if settings.get_settings("USE_DB_CACHE"): if malojaconfig["USE_DB_CACHE"]:
def db_query(**kwargs): def db_query(**kwargs):
return db_query_cached(**kwargs) return db_query_cached(**kwargs)
def db_aggregate(**kwargs): def db_aggregate(**kwargs):
@ -872,8 +871,8 @@ else:
return db_aggregate_full(**kwargs) return db_aggregate_full(**kwargs)
csz = settings.get_settings("DB_CACHE_ENTRIES") csz = malojaconfig["DB_CACHE_ENTRIES"]
cmp = settings.get_settings("DB_MAX_MEMORY") cmp = malojaconfig["DB_MAX_MEMORY"]
try: try:
import psutil import psutil
use_psutil = True use_psutil = True
@ -885,8 +884,8 @@ cache_query_perm = lru.LRU(csz)
cache_aggregate = lru.LRU(csz) cache_aggregate = lru.LRU(csz)
cache_aggregate_perm = lru.LRU(csz) cache_aggregate_perm = lru.LRU(csz)
perm_caching = settings.get_settings("CACHE_DATABASE_PERM") perm_caching = malojaconfig["CACHE_DATABASE_PERM"]
temp_caching = settings.get_settings("CACHE_DATABASE_SHORT") temp_caching = malojaconfig["CACHE_DATABASE_SHORT"]
cachestats = { cachestats = {
"cache_query":{ "cache_query":{

View File

@ -1,8 +1,8 @@
import os import os
from doreah.settings import get_settings
from doreah.settings import config as settingsconfig 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 # 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 # if not, use the first we have permissions for
# after we decide which to use, fix it in settings to avoid future heuristics # after we decide which to use, fix it in settings to avoid future heuristics
try: # USEFUL FUNCS
HOME_DIR = os.environ["XDG_DATA_HOME"].split(":")[0] pthj = os.path.join
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'
dir_settings = { def is_dir_usable(pth):
"config":None, try:
"state":None, os.makedirs(pth,exist_ok=True)
"logs":None, os.mknod(pthj(pth,".test"))
"cache":None, os.remove(pthj(pth,".test"))
# "clients":None, return True
# "rules":None, except:
# "settings":None, return False
# "auth":None,
# "backups":None, def get_env_vars(key,pathsuffix=[]):
# "images":None, return [pthj(pth,*pathsuffix) for pth in os.environ.get(key,'').split(':') if pth != '']
# "scrobbles":None,
# "logs":None,
# "cache":None
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 = { # function that
"config":[ # 1) checks if folder has been specified by user
"/etc/maloja", # 2) if not, checks if one has been in use before and writes it to dict/config
usrfol # 3) if not, determines which to use and writes it to dict/config
], # returns determined folder
"state":[ def find_good_folder(datatype,configobject):
"/var/lib/maloja", info = directory_info[datatype]
"/etc/maloja",
usrfol
],
"logs":[
"/var/log/maloja",
"/etc/maloja/logs",
pthj(usrfol,"logs")
],
"cache":[
"/var/cache/maloja",
"/etc/maloja/cache",
pthj(usrfol,"cache")
]
}
sentinels = { # check each possible folder if its used
"config":"settings", for p in info['possible_folders']:
"state":"scrobbles", if os.path.exists(pthj(p,info['sentinel'])):
"logs":None, #print(p,"has been determined as maloja's folder for",datatype)
"cache":None, configobject[info['setting']] = p
} return p
# check environ variables #print("Could not find previous",datatype,"folder")
stng_data = get_settings("DATA_DIRECTORY",files=[],environ_prefix="MALOJA_") # check which one we can use
if stng_data is not None: for p in info['possible_folders']:
dir_settings['config'] = stng_data if is_dir_usable(p):
dir_settings['state'] = stng_data #print(p,"has been selected as maloja's folder for",datatype)
dir_settings['cache'] = pthj(stng_data,'cache') configobject[info['setting']] = p
dir_settings['logs'] = pthj(stng_data,'logs') 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: 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_") found_new_config_dir = False
# as soon as we know the config directory, we can load from settings file # remember whether we had to find our config dir or it was user-specified
if dir_settings['config'] is not None:
settingsfiles = [pthj(dir_settings['config'],'settings','default.ini'),pthj(dir_settings['config'],'settings','settings.ini')] oldsettingsfile = pthj(maloja_dir_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_") 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 ### STEP 2 - create settings object
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
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 = { data_directories = {
@ -122,7 +253,7 @@ data_directories = {
"scrobbles":pthj(dir_settings['state'],"scrobbles"), "scrobbles":pthj(dir_settings['state'],"scrobbles"),
"rules":pthj(dir_settings['config'],"rules"), "rules":pthj(dir_settings['config'],"rules"),
"clients":pthj(dir_settings['config'],"clients"), "clients":pthj(dir_settings['config'],"clients"),
"settings":pthj(dir_settings['config'],"settings"), "settings":pthj(dir_settings['config']),
"css":pthj(dir_settings['config'],"custom_css"), "css":pthj(dir_settings['config'],"custom_css"),
"logs":pthj(dir_settings['logs']), "logs":pthj(dir_settings['logs']),
"cache":pthj(dir_settings['cache']), "cache":pthj(dir_settings['cache']),
@ -142,13 +273,6 @@ data_dir = {
from doreah import config from doreah import config
config( config(
settings={
"files":[
data_dir['settings']("default.ini"),
data_dir['settings']("settings.ini")
],
"environ_prefix":"MALOJA_"
},
caching={ caching={
"folder": data_dir['cache']() "folder": data_dir['cache']()
}, },
@ -157,36 +281,19 @@ config(
"cookieprefix":"maloja", "cookieprefix":"maloja",
"stylesheets":["/style.css"], "stylesheets":["/style.css"],
"dbfile":data_dir['auth']("auth.ddb") "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={ logging={
"logfolder": data_dir['logs']() if get_settings("LOGGING") else None "logfolder": data_dir['logs']() if malojaconfig["LOGGING"] else None
}, },
regular={ regular={
"autostart": False, "autostart": False,
"offset": get_settings("TIMEZONE") or 0 "offset": malojaconfig["TIMEZONE"]
} }
) )
settingsconfig._readpreconfig() settingsconfig._readpreconfig()
# what the fuck did i just write
# this spaghetti file is proudly sponsored by the rice crackers i'm eating at the
# thumbor # moment as well as some cute chinese girl whose asmr i'm listening to in the
# background. and now to bed!
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?")

View File

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

View File

@ -2,10 +2,11 @@ from datetime import timezone, timedelta, date, time, datetime
from calendar import monthrange from calendar import monthrange
from os.path import commonprefix from os.path import commonprefix
import math import math
from doreah.settings import get_settings
from .globalconf import malojaconfig
OFFSET = get_settings("TIMEZONE") OFFSET = malojaconfig["TIMEZONE"]
TIMEZONE = timezone(timedelta(hours=OFFSET)) TIMEZONE = timezone(timedelta(hours=OFFSET))
UTC = timezone.utc UTC = timezone.utc
@ -486,7 +487,7 @@ def timestamp_desc(t,short=False):
timeobj = datetime.fromtimestamp(t,tz=TIMEZONE) 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) 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) sp = subprocess.Popen(["python3","-m","maloja.proccontrol.supervisor"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
print(col["green"]("Maloja started!")) 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("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:") 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 import pkg_resources
from distutils import dir_util from distutils import dir_util
from doreah import settings
from doreah.io import col, ask, prompt from doreah.io import col, ask, prompt
from doreah import auth from doreah import auth
import os import os
from ..globalconf import data_dir, dir_settings from ..globalconf import data_dir, dir_settings, malojaconfig
# EXTERNAL API KEYS # EXTERNAL API KEYS
@ -32,19 +31,19 @@ def randomstring(length=32):
def setup(): def setup():
copy_initial_local_files() 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.") 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: for k in apikeys:
key = settings.get_settings(k) key = malojaconfig[k]
if key is False: 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": 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.") 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) 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: else:
print("\t" + apikeys[k] + " found.") print("\t" + col['lawngreen'](apikeys[k]) + " found.")
# OWN API KEY # OWN API KEY
@ -57,8 +56,7 @@ def setup():
keyfile.write(key + "\t" + "Default Generated Key") keyfile.write(key + "\t" + "Default Generated Key")
# PASSWORD # PASSWORD
defaultpassword = settings.get_settings("DEFAULT_PASSWORD") forcepassword = malojaconfig["FORCE_PASSWORD"]
forcepassword = settings.get_settings("FORCE_PASSWORD")
# this is mainly meant for docker, supply password via environment variable # this is mainly meant for docker, supply password via environment variable
if forcepassword is not None: if forcepassword is not None:
@ -67,25 +65,17 @@ def setup():
print("Password has been set.") print("Password has been set.")
elif auth.defaultuser.checkpw("admin"): 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 the actual pw is admin, it means we've never set this up properly (eg first start after update)
if defaultpassword is None: newpw = prompt("Please set a password for web backend access. Leave this empty to generate a random password.",skip=SKIP,secret=True)
# non-docker installation or user didn't set environment variable if newpw is None:
defaultpassword = randomstring(32) newpw = randomstring(32)
newpw = prompt("Please set a password for web backend access. Leave this empty to generate a random password.",skip=SKIP,secret=True) print("Generated password:",newpw)
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)
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) 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: 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: 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 setproctitle
import signal import signal
from doreah.logging import log from doreah.logging import log
from doreah.settings import get_settings
from .globalconf import malojaconfig
from .control import getInstance from .control import getInstance
@ -33,7 +33,7 @@ def start():
while True: while True:
log("Maloja is not running, starting...",module="supervisor") log("Maloja is not running, starting...",module="supervisor")
if get_settings("UPDATE_AFTER_CRASH"): if malojaconfig["UPDATE_AFTER_CRASH"]:
update() update()
process = start() process = start()

View File

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

View File

@ -10,9 +10,9 @@ import xml.etree.ElementTree as ElementTree
import json import json
import urllib.parse, urllib.request import urllib.parse, urllib.request
import base64 import base64
from doreah.settings import get_settings
from doreah.logging import log from doreah.logging import log
from ..globalconf import malojaconfig
services = { services = {
@ -69,7 +69,7 @@ class GenericInterface:
# populate from settings file once on creation # populate from settings file once on creation
# avoid constant disk access, restart on adding services is acceptable # avoid constant disk access, restart on adding services is acceptable
for key in self.settings: for key in self.settings:
self.settings[key] = get_settings(self.settings[key]) self.settings[key] = malojaconfig[self.settings[key]]
self.authorize() self.authorize()
# this makes sure that of every class we define, we immediately create an # 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): def active_proxyscrobble(self):
return ( return (
all(self.settings[key] not in [None,"ASK",False] for key in self.proxyscrobble["required_settings"]) and 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): def scrobble(self,artists,title,timestamp):
@ -130,7 +130,7 @@ class ImportInterface(GenericInterface,abstract=True):
def active_import(self): def active_import(self):
return ( return (
all(self.settings[key] not in [None,"ASK",False] for key in self.scrobbleimport["required_settings"]) and 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): def active_metadata(self):
return ( return (
all(self.settings[key] not in [None,"ASK",False] for key in self.metadata["required_settings"]) and 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): def get_image_track(self,track):
@ -228,5 +228,5 @@ from . import *
services["metadata"].sort( 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, malojaconfig
from ..globalconf import data_dir
from .. import thirdparty from .. import thirdparty
from doreah import settings, caching from doreah import caching
from doreah.logging import log from doreah.logging import log
import itertools import itertools
@ -15,27 +14,13 @@ import re
import datetime 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 ### Caches
cacheage = settings.get_settings("CACHE_EXPIRE_POSITIVE") * 24 * 3600 cacheage = malojaconfig["CACHE_EXPIRE_POSITIVE"] * 24 * 3600
cacheage_neg = settings.get_settings("CACHE_EXPIRE_NEGATIVE") * 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) 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) 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 # 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_artist_cache = caching.Cache(maxage=local_cache_age)
local_track_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) hashable_track = (frozenset(artists),title)
# Prio 1: Local image # Prio 1: Local image
if settings.get_settings("USE_LOCAL_IMAGES"): if malojaconfig["USE_LOCAL_IMAGES"]:
try: try:
return thumborize(local_track_cache.get(hashable_track)) return local_track_cache.get(hashable_track)
except: except:
images = local_files(artists=artists,title=title) images = local_files(artists=artists,title=title)
if len(images) != 0: if len(images) != 0:
@ -156,7 +141,7 @@ def getTrackImage(artists,title,fast=False):
# Prio 2: Cached remote link # Prio 2: Cached remote link
try: try:
result = track_cache.get(hashable_track) 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 # if we have cached the nonexistence of that image, we immediately return
# the redirect to the artist and let the resolver handle it # the redirect to the artist and let the resolver handle it
# (even if we're not in a fast lookup right now) # (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) # cache results (even negative ones)
track_cache.add(hashable_track,result) track_cache.add(hashable_track,result)
# return either result or redirect to artist # 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: for a in artists:
res = getArtistImage(artist=a,fast=False) res = getArtistImage(artist=a,fast=False)
if res != "": return res if res != "": return res
@ -189,21 +174,21 @@ def getTrackImage(artists,title,fast=False):
def getArtistImage(artist,fast=False): def getArtistImage(artist,fast=False):
# Prio 1: Local image # Prio 1: Local image
if settings.get_settings("USE_LOCAL_IMAGES"): if malojaconfig["USE_LOCAL_IMAGES"]:
try: try:
return thumborize(local_artist_cache.get(artist)) return local_artist_cache.get(artist)
except: except:
images = local_files(artist=artist) images = local_files(artist=artist)
if len(images) != 0: if len(images) != 0:
res = random.choice(images) res = random.choice(images)
local_artist_cache.add(artist,res) local_artist_cache.add(artist,res)
return thumborize(urllib.parse.quote(res)) return urllib.parse.quote(res)
# Prio 2: Cached remote link # Prio 2: Cached remote link
try: try:
result = artist_cache.get(artist) result = artist_cache.get(artist)
if result is not None: return thumborize(result) if result is not None: return result
else: return "" else: return ""
# none means non-existence is cached, return empty # none means non-existence is cached, return empty
except: except:
@ -219,7 +204,7 @@ def getArtistImage(artist,fast=False):
result = thirdparty.get_image_artist_all(artist) result = thirdparty.get_image_artist_all(artist)
# cache results (even negative ones) # cache results (even negative ones)
artist_cache.add(artist,result) #cache_artist(artist,result) 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 "" else: return ""
def getTrackImages(trackobjectlist,fast=False): def getTrackImages(trackobjectlist,fast=False):

View File

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

View File

@ -35,6 +35,11 @@
<span style="opacity:0.5;">Database Maintenance</span> <span style="opacity:0.5;">Database Maintenance</span>
{% else %} {% else %}
<a href="/admin_issues">Database Maintenance</a> <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 %} {% endif %}
</span> </span>
<br/><br/> <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> <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>
<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>
<div> <div>
<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 artist or track..." oninput="search(this)" onblur="clearresults()" /></span>
</div> </div>
<span id="resultwrap" class="hide"> <div id="resultwrap" class="hide">
<div class="searchresults"> <div class="searchresults">
<span>Artists</span> <span>Artists</span>
<table class="searchresults_artists" id="searchresults_artists"> <table class="searchresults_artists" id="searchresults_artists">
@ -64,7 +64,7 @@
<table class="searchresults_tracks" id="searchresults_tracks"> <table class="searchresults_tracks" id="searchresults_tracks">
</table> </table>
</div> </div>
</span> </div>
</div> </div>
<a href="/admin_overview"><div title="Server Administration" id="settingsicon"> <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> </tr>
</table> </table>
{% if settings('CHARTS_DISPLAY_TILES') %} {% if settings['CHARTS_DISPLAY_TILES'] %}
{% include 'partials/charts_artists_tiles.jinja' %} {% include 'partials/charts_artists_tiles.jinja' %}
<br/><br/> <br/><br/>
{% endif %} {% endif %}

View File

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

View File

@ -10,7 +10,7 @@
<td class='icon'><div style="background-image:url('{{ img }}')"></div></td> <td class='icon'><div style="background-image:url('{{ img }}')"></div></td>
{% if "artists" in entity %} {% 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> <td class='searchProvider'>{{ links.link_search(entity) }}</td>
{% endif %} {% endif %}
<td class='track'> <td class='track'>

View File

@ -62,7 +62,7 @@
{% macro link_search(entity) -%} {% macro link_search(entity) -%}
{% set searchstr = (entity.artists + [entity.title]) | join(" ") | urlencode %} {% 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 = {% set searchproviders =
{ {
"youtube":'https://www.youtube.com/results?search_query=', "youtube":'https://www.youtube.com/results?search_query=',

View File

@ -31,7 +31,7 @@
{% for r in xcurrent %} {% for r in xcurrent %}
{% if r.range == limitkeys.timerange %} {% if r.range == limitkeys.timerange %}
<span class='stat_selector' style='opacity:0.5;'>{{ r.localisation }}</span> <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> <span class='stat_selector blocked' title="CPU heavy statistics are discouraged at the moment.">{{ r.localisation }}</span>
{% else %} {% else %}
<a href='{{ mlj_uri.create_uri("",allkeys,{"timerange":r.range}) }}'><span class='stat_selector'>{{ r.localisation }}</span></a> <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 all(r.keys[k] == allkeys[k] for k in r.keys) %} #}
{% if r.replacekeys | map('compare_key_in_dicts',r.replacekeys,allkeys) | alltrue %} {% if r.replacekeys | map('compare_key_in_dicts',r.replacekeys,allkeys) | alltrue %}
<span class='stat_selector' style='opacity:0.5;'>{{ r.localisation }}</span> <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> <span class='stat_selector blocked' title="CPU heavy statistics are discouraged at the moment.">{{ r.localisation }}</span>
{% else %} {% else %}
<a href='{{ mlj_uri.create_uri("",allkeys,r.replacekeys) }}'><span class='stat_selector'>{{ r.localisation }}</span></a> <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 %} {% block scripts %}
<script>document.addEventListener('DOMContentLoaded',function() { <script>document.addEventListener('DOMContentLoaded',function() {
showRange('topartists','{{ settings("DEFAULT_RANGE_CHARTS_ARTISTS") }}'); showRange('topartists','{{ settings["DEFAULT_RANGE_CHARTS_ARTISTS"] }}');
showRange('toptracks','{{ settings("DEFAULT_RANGE_CHARTS_TRACKS") }}'); showRange('toptracks','{{ settings["DEFAULT_RANGE_CHARTS_TRACKS"] }}');
showRange('pulse','{{ settings("DEFAULT_STEP_PULSE") }}'); showRange('pulse','{{ settings["DEFAULT_STEP_PULSE"] }}');
})</script> })</script>
<script src="/rangeselect.js"></script> <script src="/rangeselect.js"></script>
<script src="/cookies.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 bottle>=0.12.16
waitress>=1.3 waitress>=1.3
doreah>=1.6.12 doreah>=1.7.1
nimrodel>=0.7.0 nimrodel>=0.7.0
setproctitle>=1.1.10 setproctitle>=1.1.10
wand>=0.5.4 wand>=0.5.4
jinja2>2.11 jinja2>=2.11
lru-dict>=1.1.6 lru-dict>=1.1.6
css_html_js_minify>=2.5.5 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 Settings File | Environment Variable | Type | Description
------ | --------- | --------- | --------- ------ | --------- | --------- | ---------
**Setup** **Setup**
&nbsp; | `MALOJA_DATA_DIRECTORY` | String | Use this directory to store all application files. Useful for docker. Overwrites all individually specified directories below. `data_directory` | `MALOJA_DATA_DIRECTORY` | String | Folder for all user data. Overwrites all choices for specific directories.
&nbsp; | `MALOJA_DIRECTORY_CONFIG` | String | Use this directory to store configuration files. `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 | Use this directory to store state files. `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 | Use this directory to store log files. `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 | Use this directory to store cache files. `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 | Whether to make server startup non-interactive. Vital for docker. `skip_setup` | `MALOJA_SKIP_SETUP` | Boolean | Make server setup process 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. `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 | Avoid mutable console output. Use if console output will be redirected e.g. to a web interface. `clean_output` | `MALOJA_CLEAN_OUTPUT` | Boolean | Use if console output will be redirected e.g. to a web interface.
**Debug** **Debug**
`LOGGING` | `MALOJA_LOGGING` | Boolean | Enable logging `logging` | `MALOJA_LOGGING` | Boolean | Enable Logging
`DEV_MODE` | `MALOJA_DEV_MODE` | Boolean | Enable developer mode `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** **Technical**
`WEB_PORT` | &nbsp; | Integer | HTTP port to use for your web interface and API `cache_expire_positive` | `MALOJA_CACHE_EXPIRE_POSITIVE` | Integer | Days until images are refetched
`HOST` | &nbsp; | String | Host for your server - most likely `::` for IPv6 or `0.0.0.0` for IPv4 `cache_expire_negative` | `MALOJA_CACHE_EXPIRE_NEGATIVE` | Integer | Days until failed image fetches are reattempted
`CACHE_EXPIRE_POSITIVE` | &nbsp; | Integer | Days until images are refetched `use_db_cache` | `MALOJA_USE_DB_CACHE` | Boolean | Use DB Cache
`CACHE_EXPIRE_NEGATIVE` | &nbsp; | Integer | Days until failed image fetches are reattempted `cache_database_short` | `MALOJA_CACHE_DATABASE_SHORT` | Boolean | Use volatile Database Cache
`USE_DB_CACHE` | &nbsp; | Boolean | Whether to use the Database Cache. `cache_database_perm` | `MALOJA_CACHE_DATABASE_PERM` | Boolean | Use permanent Database Cache
`CACHE_DATABASE_SHORT` | &nbsp; | Boolean | Whether to use the Volatile DB Cache. `db_cache_entries` | `MALOJA_DB_CACHE_ENTRIES` | Integer | Maximal Cache entries
`CACHE_DATABASE_PERM` | &nbsp; | Boolean | Whether to use the Permanent DB Cache. `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)
`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)
**Fluff** **Fluff**
`SCROBBLES_GOLD` | &nbsp; | Integer | How many scrobbles should be considered 'Gold' status for a track `scrobbles_gold` | `MALOJA_SCROBBLES_GOLD` | Integer | How many scrobbles a track needs to be considered 'Gold' status
`SCROBBLES_PLATINUM` | &nbsp; | Integer | How many scrobbles should be considered 'Platinum' status for a track `scrobbles_platinum` | `MALOJA_SCROBBLES_PLATINUM` | Integer | How many scrobbles a track needs to be considered 'Platinum' status
`SCROBBLES_DIAMOND` | &nbsp; | Integer | How many scrobbles should be considered 'Diamond' status for a track `scrobbles_diamond` | `MALOJA_SCROBBLES_DIAMOND` | Integer | How many scrobbles a track needs to be considered 'Diamond' status
`NAME` | &nbsp; | String | Your Name for display `name` | `MALOJA_NAME` | String | Name
**Third Party Services** **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. `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` | &nbsp; | Boolean | Proxy-scrobble to Last.fm `scrobble_lastfm` | `MALOJA_SCROBBLE_LASTFM` | 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_key` | `MALOJA_LASTFM_API_KEY` | String | Last.fm API Key
`LASTFM_API_SECRET` | &nbsp; | String | API secret for Last.fm. Necessary if proxy-scrobbling to Last.fm or using it as a metadata provider `lastfm_api_secret` | `MALOJA_LASTFM_API_SECRET` | String | Last.fm API Secret
`SPOTIFY_API_ID` | &nbsp; | String | API ID for Spotify. Necessary if using it as a metadata provider. `spotify_api_id` | `MALOJA_SPOTIFY_API_ID` | String | Spotify API ID
`SPOTIFY_API_SECRET` | &nbsp; | String | API Secret for Spotify. Necessary if using it as a metadata provider. `spotify_api_secret` | `MALOJA_SPOTIFY_API_SECRET` | String | Spotify API Secret
`TRACK_SEARCH_PROVIDER` | &nbsp; | String | Provider for track search next to scrobbles. None to disable. `audiodb_api_key` | `MALOJA_AUDIODB_API_KEY` | String | TheAudioDB API Key
`THUMBOR_SERVER` | &nbsp; | String | URL of Thumbor server to serve custom artwork. `track_search_provider` | `MALOJA_TRACK_SEARCH_PROVIDER` | String | Track Search Provider
`THUMBOR_SECRET` | &nbsp; | String | Secret of Thumbor server `send_stats` | `MALOJA_SEND_STATS` | Boolean | Send Statistics
**Database** **Database**
`INVALID_ARTISTS` | &nbsp; | List (String) | Artists that should be discarded immediately `invalid_artists` | `MALOJA_INVALID_ARTISTS` | Set | Artists that should be discarded immediately
`REMOVE_FROM_TITLE` | &nbsp; | List (String) | Phrases that should be removed from song titles `remove_from_title` | `MALOJA_REMOVE_FROM_TITLE` | Set | 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_feat` | `MALOJA_DELIMITERS_FEAT` | Set | 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_informal` | `MALOJA_DELIMITERS_INFORMAL` | Set | 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 `delimiters_formal` | `MALOJA_DELIMITERS_FORMAL` | Set | Delimiters used to tag multiple artists when only one tag field is available
**Web Interface** **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_artists` | `MALOJA_DEFAULT_RANGE_CHARTS_ARTISTS` | Choice | Default Range Artist Charts
`DEFAULT_RANGE_CHARTS_TRACKS` | &nbsp; | String | What range is shown per default for the tile view on the start page `default_range_charts_tracks` | `MALOJA_DEFAULT_RANGE_CHARTS_TRACKS` | Choice | Default Range Track Charts
`DEFAULT_STEP_PULSE` | &nbsp; | String | What steps are shown per default for the pulse view on the start page `default_step_pulse` | `MALOJA_DEFAULT_STEP_PULSE` | Choice | Default Pulse Step
`CHARTS_DISPLAY_TILES` | &nbsp; | Boolean | Whether to show tiles on chart pages `charts_display_tiles` | `MALOJA_CHARTS_DISPLAY_TILES` | Boolean | Display Chart Tiles
`DISCOURAGE_CPU_HEAVY_STATS` | &nbsp; | Boolean | Prevent visitors from mindlessly clicking on CPU-heavy options. Does not actually disable them for malicious actors! `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` | &nbsp; | Boolean | Use local images if present `use_local_images` | `MALOJA_USE_LOCAL_IMAGES` | Boolean | Use Local Images
`LOCAL_IMAGE_ROTATE` | &nbsp; | Integer | How many seconds to wait between rotating 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