2019-12-14 15:46:02 +03:00
import os
2021-12-12 20:43:24 +03:00
from doreah . configuration import Configuration
from doreah . configuration import types as tp
2022-03-06 03:57:46 +03:00
2019-12-12 23:24:13 +03:00
2022-04-09 22:39:04 +03:00
from . . __pkginfo__ import VERSION
2021-12-24 23:23:02 +03:00
2021-12-21 08:09:46 +03:00
2021-12-21 08:55:35 +03:00
2022-01-03 04:08:02 +03:00
2021-12-21 08:55:35 +03:00
# if DATA_DIRECTORY is specified, this is the directory to use for EVERYTHING, no matter what
# but with asynnetrical structure, cache and logs in subfolders
# otherwise, each directory is treated seperately
# in that case, individual settings for each are respected
# DIRECRORY_CONFIG, DIRECRORY_STATE, DIRECTORY_LOGS and DIRECTORY_CACHE
# config can only be determined by environment variable, the others can be loaded
# from the config files
2022-04-04 17:18:06 +03:00
# explicit settings will always be respected, fallback to default
# if default isn't usable, and config writable, find alternative and fix it in settings
2021-12-21 08:55:35 +03:00
2021-12-21 08:09:46 +03:00
# USEFUL FUNCS
2020-12-25 06:41:33 +03:00
pthj = os . path . join
2021-12-21 08:09:46 +03:00
def is_dir_usable ( pth ) :
try :
os . makedirs ( pth , exist_ok = True )
os . mknod ( pthj ( pth , " .test " ) )
os . remove ( pthj ( pth , " .test " ) )
return True
2022-04-24 20:41:55 +03:00
except Exception :
2021-12-21 08:09:46 +03:00
return False
def get_env_vars ( key , pathsuffix = [ ] ) :
return [ pthj ( pth , * pathsuffix ) for pth in os . environ . get ( key , ' ' ) . split ( ' : ' ) if pth != ' ' ]
2021-12-21 08:55:35 +03:00
directory_info = {
" config " : {
2021-12-21 09:30:38 +03:00
" sentinel " : " rules " ,
2021-12-21 08:55:35 +03:00
" 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 "
}
}
# function that
2021-12-23 09:24:24 +03:00
# checks if one has been in use before and writes it to dict/config
# if not, determines which to use and writes it to dict/config
2021-12-21 08:55:35 +03:00
# returns determined folder
def find_good_folder ( datatype , configobject ) :
info = directory_info [ datatype ]
# check each possible folder if its used
for p in info [ ' possible_folders ' ] :
if os . path . exists ( pthj ( p , info [ ' sentinel ' ] ) ) :
2021-12-21 09:30:38 +03:00
#print(p,"has been determined as maloja's folder for",datatype)
2021-12-21 08:55:35 +03:00
configobject [ info [ ' setting ' ] ] = p
return p
2021-12-21 09:30:38 +03:00
#print("Could not find previous",datatype,"folder")
2021-12-21 08:55:35 +03:00
# check which one we can use
for p in info [ ' possible_folders ' ] :
if is_dir_usable ( p ) :
2021-12-21 09:30:38 +03:00
#print(p,"has been selected as maloja's folder for",datatype)
2021-12-21 08:55:35 +03:00
configobject [ info [ ' setting ' ] ] = p
return p
2021-12-21 09:30:38 +03:00
#print("No folder can be used for",datatype)
#print("This should not happen!")
2021-12-21 08:09:46 +03:00
2020-12-25 06:41:33 +03:00
2021-12-21 08:09:46 +03:00
### 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 " )
2021-12-21 08:55:35 +03:00
2021-12-21 08:09:46 +03:00
if maloja_dir_config is None :
2021-12-21 08:55:35 +03:00
maloja_dir_config = find_good_folder ( ' config ' , { } )
found_new_config_dir = True
else :
found_new_config_dir = False
# remember whether we had to find our config dir or it was user-specified
2021-12-21 08:09:46 +03:00
2021-12-23 09:24:24 +03:00
os . makedirs ( maloja_dir_config , exist_ok = True )
2021-12-21 08:09:46 +03:00
oldsettingsfile = pthj ( maloja_dir_config , " settings " , " settings.ini " )
newsettingsfile = pthj ( maloja_dir_config , " settings.ini " )
2021-12-23 09:24:24 +03:00
2021-12-21 08:09:46 +03:00
if os . path . exists ( oldsettingsfile ) :
os . rename ( oldsettingsfile , newsettingsfile )
### STEP 2 - create settings object
2020-12-25 06:41:33 +03:00
2021-12-21 08:09:46 +03:00
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 " : {
2022-04-14 21:49:40 +03:00
" host " : ( tp . String ( ) , " Host " , " * " , " Host for your server, e.g. ' * ' for dual stack, ' :: ' for IPv6 or ' 0.0.0.0 ' for IPv4 " ) ,
2021-12-21 08:09:46 +03:00
" port " : ( tp . Integer ( ) , " Port " , 42010 ) ,
} ,
" Technical " : {
2022-02-27 02:34:06 +03:00
" cache_expire_positive " : ( tp . Integer ( ) , " Image Cache Expiration " , 60 , " Days until images are refetched " ) ,
" cache_expire_negative " : ( tp . Integer ( ) , " Image Cache Negative Expiration " , 5 , " Days until failed image fetches are reattempted " ) ,
2022-04-25 05:28:53 +03:00
" db_max_memory " : ( tp . Integer ( min = 0 , max = 100 ) , " RAM Percentage soft limit " , 50 , " RAM Usage in percent at which Maloja should no longer increase its database cache. " ) ,
2022-04-24 21:55:07 +03:00
" use_request_cache " : ( tp . Boolean ( ) , " Use request-local DB Cache " , False ) ,
2022-04-25 04:24:16 +03:00
" use_global_cache " : ( tp . Boolean ( ) , " Use global DB Cache " , True )
2021-12-21 08:09:46 +03:00
} ,
" 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 ) ,
2021-12-27 20:32:33 +03:00
" lastfm_api_sk " : ( tp . String ( ) , " Last.fm API Session Key " , None ) ,
2021-12-27 22:18:56 +03:00
" lastfm_username " : ( tp . String ( ) , " Last.fm Username " , None ) ,
" lastfm_password " : ( tp . String ( ) , " Last.fm Password " , None ) ,
2021-12-21 08:09:46 +03:00
" spotify_api_id " : ( tp . String ( ) , " Spotify API ID " , None ) ,
" spotify_api_secret " : ( tp . String ( ) , " Spotify API Secret " , None ) ,
" audiodb_api_key " : ( tp . String ( ) , " TheAudioDB API Key " , None ) ,
2022-01-01 01:13:22 +03:00
" other_maloja_url " : ( tp . String ( ) , " Other Maloja Instance URL " , None ) ,
" other_maloja_api_key " : ( tp . String ( ) , " Other Maloja Instance API Key " , None ) ,
2021-12-21 08:09:46 +03:00
" track_search_provider " : ( tp . String ( ) , " Track Search Provider " , None ) ,
" send_stats " : ( tp . Boolean ( ) , " Send Statistics " , None ) ,
2022-03-26 07:49:30 +03:00
" proxy_images " : ( tp . Boolean ( ) , " Image Proxy " , True , " Whether third party images should be downloaded and served directly by Maloja (instead of just linking their URL) " )
2021-12-21 08:09:46 +03:00
} ,
" 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 " ) ,
2022-04-28 07:08:51 +03:00
" delimiters_feat " : ( tp . Set ( tp . String ( ) ) , " Featuring Delimiters " , [ " ft. " , " ft " , " feat. " , " feat " , " featuring " ] , " Delimiters used for extra artists, even when in the title field " ) ,
2021-12-21 08:09:46 +03:00
" delimiters_informal " : ( tp . Set ( tp . String ( ) ) , " Informal Delimiters " , [ " vs. " , " vs " , " & " ] , " Delimiters in informal artist strings with spaces expected around them " ) ,
2022-04-28 07:08:51 +03:00
" delimiters_formal " : ( tp . Set ( tp . String ( ) ) , " Formal Delimiters " , [ " ; " , " / " , " | " , " ␝ " , " ␞ " , " ␟ " ] , " Delimiters used to tag multiple artists when only one tag field is available " ) ,
" filters_remix " : ( tp . Set ( tp . String ( ) ) , " Remix Filters " , [ " Remix " , " Remix Edit " , " Short Mix " , " Extended Mix " , " Soundtrack Version " ] , " Filters used to recognize the remix artists in the title " ) ,
2022-04-27 23:24:45 +03:00
" parse_remix_artists " : ( tp . Boolean ( ) , " Parse Remix Artists " , False )
2021-12-21 08:09:46 +03:00
} ,
" 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 ) ,
2023-03-16 22:21:02 +03:00
" display_art_icons " : ( tp . Boolean ( ) , " Display Album/Artist Icons " , True ) ,
2021-12-21 08:09:46 +03:00
" 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 ) ,
2022-02-18 07:31:08 +03:00
#"local_image_rotate":(tp.Integer(), "Local Image Rotate", 3600),
2021-12-21 08:09:46 +03:00
" timezone " : ( tp . Integer ( ) , " UTC Offset " , 0 ) ,
2022-03-18 03:59:47 +03:00
" time_format " : ( tp . String ( ) , " Time Format " , " %d . % b % Y % I: % M % p " ) ,
" theme " : ( tp . String ( ) , " Theme " , " maloja " )
2021-12-21 08:09:46 +03:00
}
} ,
configfile = newsettingsfile ,
save_endpoint = " /apis/mlj_1/settings " ,
2022-03-06 04:00:48 +03:00
env_prefix = " MALOJA_ " ,
extra_files = [ " /run/secrets/maloja.yml " , " /run/secrets/maloja.ini " ]
2021-12-21 08:09:46 +03:00
)
2021-12-21 08:55:35 +03:00
if found_new_config_dir :
2022-04-04 17:18:06 +03:00
try :
malojaconfig [ " DIRECTORY_CONFIG " ] = maloja_dir_config
except PermissionError as e :
pass
2021-12-21 08:55:35 +03:00
# 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
2022-04-04 17:18:06 +03:00
try :
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 .
2021-12-21 08:55:35 +03:00
2022-04-04 17:18:06 +03:00
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 !
2022-03-06 04:00:48 +03:00
2022-04-04 17:18:06 +03:00
You also can specify additional settings in the files ` / run / secrets / maloja . yml ` or
` / run / secrets / maloja . ini ` , as well as their values directly in files of the respective
name in ` / run / secrets / ` ( e . g . ` / run / secrets / lastfm_api_key ` ) . ''' )
except PermissionError as e :
pass
2021-12-21 08:09:46 +03:00
### STEP 3 - check all possible folders for files (old installation)
2020-12-25 06:41:33 +03:00
2022-04-04 17:18:06 +03:00
if not malojaconfig . readonly :
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 )
2021-12-21 08:55:35 +03:00
2021-12-24 09:29:59 +03:00
2021-12-23 09:24:24 +03:00
2021-12-21 08:55:35 +03:00
### 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
2021-12-21 08:09:46 +03:00
if malojaconfig [ ' DATA_DIRECTORY ' ] is None :
2021-12-21 08:55:35 +03:00
dir_settings = {
2021-12-21 08:09:46 +03:00
" config " : malojaconfig [ ' DIRECTORY_CONFIG ' ] ,
" state " : malojaconfig [ ' DIRECTORY_STATE ' ] ,
" cache " : malojaconfig [ ' DIRECTORY_CACHE ' ] ,
" logs " : malojaconfig [ ' DIRECTORY_LOGS ' ] ,
}
else :
2021-12-21 08:55:35 +03:00
dir_settings = {
2021-12-21 08:09:46 +03:00
" config " : malojaconfig [ ' DATA_DIRECTORY ' ] ,
" state " : malojaconfig [ ' DATA_DIRECTORY ' ] ,
" cache " : pthj ( malojaconfig [ ' DATA_DIRECTORY ' ] , " cache " ) ,
" logs " : pthj ( malojaconfig [ ' DATA_DIRECTORY ' ] , " logs " ) ,
}
2020-12-25 06:41:33 +03:00
data_directories = {
2021-12-21 08:55:35 +03:00
" auth " : pthj ( dir_settings [ ' state ' ] , " auth " ) ,
" backups " : pthj ( dir_settings [ ' state ' ] , " backups " ) ,
" images " : pthj ( dir_settings [ ' state ' ] , " images " ) ,
2022-01-03 05:04:36 +03:00
" scrobbles " : pthj ( dir_settings [ ' state ' ] ) ,
2021-12-21 08:55:35 +03:00
" rules " : pthj ( dir_settings [ ' config ' ] , " rules " ) ,
2021-12-25 04:22:57 +03:00
" clients " : pthj ( dir_settings [ ' config ' ] ) ,
2021-12-21 08:55:35 +03:00
" settings " : pthj ( dir_settings [ ' config ' ] ) ,
" css " : pthj ( dir_settings [ ' config ' ] , " custom_css " ) ,
2022-03-27 21:08:41 +03:00
" config " : dir_settings [ ' config ' ] ,
" state " : dir_settings [ ' state ' ] ,
" logs " : dir_settings [ ' logs ' ] ,
" cache " : dir_settings [ ' cache ' ] ,
2020-12-25 06:41:33 +03:00
}
2022-04-08 20:03:12 +03:00
for identifier , path in data_directories . items ( ) :
os . makedirs ( path , exist_ok = True )
2020-12-25 06:41:33 +03:00
data_dir = {
2020-12-25 07:24:59 +03:00
k : lambda * x , k = k : pthj ( data_directories [ k ] , * x ) for k in data_directories
2020-12-25 06:41:33 +03:00
}
2019-12-15 17:18:33 +03:00
2021-12-24 23:23:02 +03:00
### write down the last ran version
with open ( pthj ( dir_settings [ ' state ' ] , " .lastmalojaversion " ) , " w " ) as filed :
2021-12-26 23:36:36 +03:00
filed . write ( VERSION )
2021-12-24 23:23:02 +03:00
filed . write ( " \n " )
2020-06-06 17:46:25 +03:00
2019-12-15 17:18:33 +03:00
### DOREAH CONFIGURATION
from doreah import config
config (
2020-07-29 16:52:01 +03:00
auth = {
" multiuser " : False ,
" cookieprefix " : " maloja " ,
2022-04-24 16:12:49 +03:00
" stylesheets " : [ " /maloja.css " ] ,
2020-12-25 06:52:05 +03:00
" dbfile " : data_dir [ ' auth ' ] ( " auth.ddb " )
2021-12-21 08:09:46 +03:00
} ,
2020-06-13 18:34:30 +03:00
logging = {
2021-12-21 08:09:46 +03:00
" logfolder " : data_dir [ ' logs ' ] ( ) if malojaconfig [ " LOGGING " ] else None
2020-12-12 19:23:29 +03:00
} ,
regular = {
2021-12-21 08:09:46 +03:00
" offset " : malojaconfig [ " TIMEZONE " ]
2020-06-13 18:34:30 +03:00
}
)
2021-12-21 08:55:35 +03:00
2021-12-25 04:22:57 +03:00
2022-04-26 20:43:35 +03:00
custom_css_files = [ f for f in os . listdir ( data_dir [ ' css ' ] ( ) ) if f . lower ( ) . endswith ( ' .css ' ) ]
2021-12-25 04:22:57 +03:00
2021-12-21 08:55:35 +03:00
# 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!