import FS from 'fs' ; import Path from 'path' ; import DeepMerge from 'deepmerge' ; import PathIsInside from 'path-is-inside' ; import StripJsonComments from 'strip-json-comments' ; import { Validator } from './utils/validator' ; import type { Configuration } from '@vimtur/common' ; function mapEnv ( env , obj , dest ) { if ( process . env [ env ] ) { console . log ( `Using ${ process . env [ env ] ! } from ${ env }` ) ; let node = obj ; const path = dest . split ( '.' ) ; for ( let i = 0 ; i < path . length - 1 ; i ++ ) { if ( ! node [ path [ i ] ] ) { node [ path [ i ] ] = { } ; } node = node [ path [ i ] ] ; } let value = process . env [ env ] ! ; if ( ! isNaN ( Number ( value ) ) ) { value = Number ( value ) ; } node [ path [ path . length - 1 ] ] = value ; } } function readJsonSync ( file ) { return JSON . parse ( StripJsonComments ( FS . readFileSync ( file ) . toString ( ) ) ) ; } function getVersion ( ) { try { const version = FS . readFileSync ( './version' ) . toString ( ) . trim ( ) ; console . log ( `Version file found: ${ version }` ) ; return version ; } catch ( _err ) { console . warn ( 'Failed to load version file. Using default.' ) ; return 'DEVELOPMENT' ; } } export const VERSION_NAME = getVersion ( ) ; const DEFAULTS = { port : 3523 , enablePhash : false , enableTensorFlow : true , maxCloneAge : 4 * 7 * 24 * 60 * 60 , transcoder : { maxCopyEnabled : true , minQuality : 480 , cacheQualities : [ 240 ] , streamQualities : [ 240 , 480 , 720 , 1080 ] , bitrateMultiplier : 1.5 , enableCachingKeyframes : true , enablePrecachingKeyframes : false , enableVideoCaching : false , enableVideoPreviews : true , videoPreviewHeight : 120 , videoPreviewFps : 10 , videoPreviewMaxHeight : 6000 , } , user : { autoplayEnabled : true , tagColumnCount : 1 , stateEnabled : false , lowQualityOnLoadEnabled : false , lowQualityOnLoadEnabledForMobile : true , initialLoadLimit : 750 , galleryImageCount : 15 , quickTagShowRating : true , quickTagShowArtist : true , quickTagShowAlbum : true , quickTagShowTitle : true , quickTagShowPeople : true , quickTagShowPath : true , quickTagShowPlaylists : true , quickTagShowAutoTags : true , useNativeSelectOnMobile : true , scaleToScreenWidthOnMobile : true , showTaskNotifications : false , autoClearCompletedTasks : true , } , } ; class Config { private validator : Validator ; private merged ? : Configuration . Main ; private baseLayers ? : Configuration . Partial [ ] ; public static init ( ) { const validator = Validator . load ( 'Configuration.Main' ) ; const userConfig = Config . getUserLayer ( ) ; const environment = Config . getEnvironmentLayer ( ) ; const config = new Config ( validator ) ; config . setLayers ( [ DEFAULTS , userConfig , environment ] ) ; return config ; } private static getEnvironmentLayer ( ) { const config = { } ; mapEnv ( 'PORT' , config , 'port' ) ; mapEnv ( 'DATA_PATH' , config , 'libraryPath' ) ; mapEnv ( 'CACHE_PATH' , config , 'cachePath' ) ; mapEnv ( 'USERNAME' , config , 'username' ) ; mapEnv ( 'PASSWORD' , config , 'password' ) ; mapEnv ( 'DATABASE' , config , 'database.provider' ) ; mapEnv ( 'DATABASE_URI' , config , 'database.uri' ) ; mapEnv ( 'DATABASE_DB' , config , 'database.db' ) ; return config ; } private static getUserLayer ( ) { const path = process . env [ 'CONFIG_PATH' ] ; if ( ! path ) { return { } ; } return readJsonSync ( path ) ; } private constructor ( validator ) { this . validator = validator ; } public get ( ) { if ( ! this . merged ) { throw new Error ( 'Configuration layers incorrectly configured' ) ; } return this . merged ; } public setUserOverlay ( config ) { this . setLayers ( [ config ] ) ; } private setLayers ( layers ) { const layerArray = [ ] ; if ( this . baseLayers ) { layerArray . push ( ... this . baseLayers ) ; } layerArray . push ( ... layers ) ; const merged = DeepMerge . all ( layerArray , { arrayMerge : ( _ , sourceArray ) => sourceArray , } ) as any ; merged . transcoder . cacheQualities = [ ... new Set ( merged . transcoder . cacheQualities ) ] . sort ( ( a , b ) => a - b ) ; merged . transcoder . streamQualities = [ ... new Set ( merged . transcoder . streamQualities ) ] . sort ( ( a , b ) => a - b ) ; const validationResult = this . validator . validate ( merged ) ; if ( ! validationResult . success ) { throw new Error ( validationResult . errorText ?? 'Merged configuration schema failed to validate.' ) ; } if ( ! this . baseLayers ) { if ( PathIsInside ( Path . resolve ( merged . cachePath ) , Path . resolve ( merged . libraryPath ) ) ) { throw new Error ( 'Cache folder cannot be inside the library' ) ; } this . baseLayers = layers ; } this . merged = merged ; } } export default Config . init ( ) ;