2022-09-02 17:00:50 +02:00
from ast import If , Try
2022-02-19 12:33:11 +01:00
import os
2022-04-01 19:28:17 +02:00
from os import listdir
from os . path import isfile , join
2022-02-19 12:33:11 +01:00
import pathlib
import platform
import shutil
import zipfile
2022-09-12 21:54:51 +02:00
from pathlib import Path
2022-04-01 17:02:12 +02:00
import glob
2023-01-05 10:01:57 +01:00
import json
2022-02-19 12:33:11 +01:00
class ConfigFolder :
2022-09-12 21:54:51 +02:00
def __init__ ( self , configFolderPath , logsFolderPath ) :
self . configFolderPath = configFolderPath
self . logsFolderPath = logsFolderPath
2022-09-14 19:04:58 +02:00
print ( " config folder path : " + configFolderPath )
print ( " logs folder path : " + logsFolderPath )
2022-02-19 12:33:11 +01:00
def config_file_exists ( self , path ) :
return os . path . exists ( self . get_file_path ( path ) )
def get_file_path ( self , file ) :
2022-09-12 21:54:51 +02:00
return os . path . join ( self . configFolderPath , file )
2022-09-24 15:19:34 +02:00
def get_dashboard_path ( self , file ) :
return os . path . join ( self . configFolderPath , " dashboard " , file )
2022-02-19 12:33:11 +01:00
def get_upload_file ( self , file ) :
2022-09-12 21:54:51 +02:00
return os . path . join ( self . configFolderPath , ' upload ' , file )
2022-02-19 12:33:11 +01:00
def get_recipe_file_by_id ( self , recipe_id ) :
2022-09-12 21:54:51 +02:00
return os . path . join ( self . configFolderPath , ' recipes ' , " {} .yaml " . format ( recipe_id ) )
2022-02-19 12:33:11 +01:00
2022-04-01 17:02:12 +02:00
def get_fermenter_recipe_by_id ( self , recipe_id ) :
2022-09-12 21:54:51 +02:00
return os . path . join ( self . configFolderPath , ' fermenterrecipes ' , " {} .yaml " . format ( recipe_id ) )
2022-04-01 17:02:12 +02:00
def get_all_fermenter_recipes ( self ) :
2022-09-12 21:54:51 +02:00
fermenter_recipes_folder = os . path . join ( self . configFolderPath , ' fermenterrecipes ' )
2022-04-01 19:28:17 +02:00
fermenter_recipe_ids = [ os . path . splitext ( f ) [ 0 ] for f in listdir ( fermenter_recipes_folder ) if isfile ( join ( fermenter_recipes_folder , f ) ) and f . endswith ( " .yaml " ) ]
return fermenter_recipe_ids
2022-04-01 17:02:12 +02:00
2022-02-19 12:33:11 +01:00
def check_for_setup ( self ) :
2022-09-02 17:00:50 +02:00
# is there a restored_config.zip file? if yes restore it first then delte the zip.
2022-09-12 21:54:51 +02:00
backupfile = os . path . join ( self . configFolderPath , " restored_config.zip " )
2022-02-19 12:33:11 +01:00
if os . path . exists ( os . path . join ( backupfile ) ) is True :
print ( " *************************************************** " )
print ( " Found backup of config. Starting restore " )
2024-05-31 15:36:19 +02:00
required_content = [ ' dashboard/ ' ,
' recipes/ ' ,
' upload/ ' ,
' sensor.json ' ,
' actor.json ' ,
' kettle.json ' ,
' config.json ' ,
2024-06-09 21:37:12 +02:00
#'craftbeerpi.template',
2024-05-31 15:36:19 +02:00
' chromium.desktop ' ,
' config.yaml ' ]
2022-02-19 12:33:11 +01:00
zip = zipfile . ZipFile ( backupfile )
zip_content_list = zip . namelist ( )
zip_content = True
2024-05-31 15:36:19 +02:00
missing_content = [ ]
2022-02-19 12:33:11 +01:00
print ( " Checking content of zip file " )
2024-05-31 15:36:19 +02:00
2022-02-19 12:33:11 +01:00
for content in required_content :
try :
check = zip_content_list . index ( content )
except :
zip_content = False
2024-05-31 15:36:19 +02:00
missing_content . append ( content )
2022-02-19 12:33:11 +01:00
if zip_content == True :
print ( " Found correct content. Starting Restore process " )
2022-09-12 21:54:51 +02:00
output_path = pathlib . Path ( self . configFolderPath )
2022-02-19 12:33:11 +01:00
system = platform . system ( )
print ( system )
if system != " Windows " :
owner = output_path . owner ( )
group = output_path . group ( )
print ( " Removing old config folder " )
2022-09-02 17:00:50 +02:00
shutil . rmtree ( output_path , ignore_errors = True )
2022-02-19 12:33:11 +01:00
print ( " Extracting zip file to config folder " )
zip . extractall ( output_path )
zip . close ( )
if system != " Windows " :
print ( f " Changing owner and group of config folder recursively to { owner } : { group } " )
self . recursive_chown ( output_path , owner , group )
print ( " Removing backup file " )
2024-10-10 19:29:29 +02:00
try :
os . remove ( backupfile )
except :
pass
2024-05-31 16:30:35 +02:00
Line1 = " Contents of restored_config.zip file have been restored. "
Line2 = " In case of a partial backup you will still be prompted to run ' cbpi setup ' . "
print ( Line1 )
print ( Line2 )
2022-02-19 12:33:11 +01:00
else :
2024-05-31 15:36:19 +02:00
zip . close ( )
2024-05-31 16:30:35 +02:00
Line1 = " Wrong Content in zip file. No restore possible "
Line2 = f ' These files are missing { missing_content } '
print ( Line1 )
print ( Line2 )
restorelogfile = os . path . join ( self . configFolderPath , " restore_error.log " )
f = open ( restorelogfile , " w " )
f . write ( Line1 + " \n " )
f . write ( Line2 + " \n " )
f . close ( )
2022-09-02 17:00:50 +02:00
print ( " renaming zip file so it will be ignored on the next start " )
try :
2022-09-12 21:54:51 +02:00
os . rename ( backupfile , os . path . join ( self . configFolderPath , " UNRESTORABLE_restored_config.zip " ) )
2022-09-02 17:00:50 +02:00
except :
print ( " renamed file does exist - deleting instead " )
os . remove ( backupfile )
2022-02-19 12:33:11 +01:00
print ( " *************************************************** " )
2022-09-02 17:00:50 +02:00
# possible restored_config.zip has been handeled now lets check if files and folders exist
required_config_content = [
[ ' config.yaml ' , ' file ' ] ,
[ ' actor.json ' , ' file ' ] ,
[ ' sensor.json ' , ' file ' ] ,
[ ' kettle.json ' , ' file ' ] ,
2023-01-26 17:43:19 +01:00
#['fermenter_data.json', 'file'], created by fermentation_controller @ start if not available
#['step_data.json', 'file'], created by step_controller @ start if not available
2022-09-02 17:00:50 +02:00
[ ' config.json ' , ' file ' ] ,
2023-10-15 09:59:05 +02:00
[ ' craftbeerpi.template ' , ' file ' ] ,
2022-09-02 17:00:50 +02:00
[ ' chromium.desktop ' , ' file ' ] ,
2022-09-09 18:38:08 +02:00
[ ' dashboard ' , ' folder ' ] ,
2022-10-02 18:32:40 +02:00
[ ' dashboard/widgets ' , ' folder ' ] ,
2022-09-02 17:00:50 +02:00
[ ' fermenterrecipes ' , ' folder ' ] ,
2022-09-16 10:25:31 +02:00
[ self . logsFolderPath , ' folder ' ] ,
2022-09-02 17:00:50 +02:00
[ ' recipes ' , ' folder ' ] ,
[ ' upload ' , ' folder ' ]
2022-10-02 18:32:40 +02:00
#['dashboard/cbpi_dashboard_1.json', 'file'] no need to check - can be created with online editor
2022-09-02 17:00:50 +02:00
]
for checking in required_config_content :
2022-09-12 21:54:51 +02:00
if self . inform_missing_content ( self . check_for_file_or_folder ( os . path . join ( self . configFolderPath , checking [ 0 ] ) , checking [ 1 ] ) ) :
2023-10-18 19:43:26 +02:00
# since there is no complete config we now check if the config folder may be completely empty to show hints:
2024-01-20 11:17:27 +01:00
try :
if len ( os . listdir ( os . path . join ( self . configFolderPath ) ) ) == 0 :
print ( " *************************************************** " )
print ( f " the config folder ' { self . configFolderPath } ' seems to be completely empty " )
print ( " you might want to run ' cbpi setup ' .print " )
print ( " but you could also place your zipped config backup named " )
print ( " ' restored_config.zip ' inside the mentioned config folder for " )
print ( " cbpi4 to automatically unpack it " )
print ( " of course you can also place your config files manually " )
print ( " *************************************************** " )
return False
except :
print ( " *************************************************** " )
print ( " Cannot find config folder! " )
print ( " Please navigate to path where you did run ' cbpi setup ' . " )
print ( " Or run ' cbpi setup ' before starting the server. " )
print ( " *************************************************** " )
return False
2022-10-02 18:32:40 +02:00
2023-10-18 19:43:26 +02:00
# if cbpi_dashboard_1.json does'nt exist at the new location (configFolderPath/dashboard)
2022-10-02 18:32:40 +02:00
# we move every cbpi_dashboard_n.json file from the old location (configFolderPath) there.
# this could be a config zip file restore from version 4.0.7.a4 or prior.
2023-01-05 10:01:57 +01:00
dashboard_1_path = os . path . join ( self . configFolderPath , ' dashboard ' , ' cbpi_dashboard_1.json ' )
if ( not ( os . path . isfile ( dashboard_1_path ) ) ) or self . check_for_empty_dashboard_1 ( dashboard_1_path ) :
2022-10-02 18:32:40 +02:00
for file in glob . glob ( os . path . join ( self . configFolderPath , ' cbpi_dashboard_*.json ' ) ) :
2023-01-05 10:01:57 +01:00
dashboardFile = os . path . basename ( file )
print ( f " Copy dashboard json file { dashboardFile } from config to config/dashboard folder " )
shutil . move ( file , os . path . join ( self . configFolderPath , ' dashboard ' , dashboardFile ) )
def check_for_empty_dashboard_1 ( self , dashboard_1_path ) :
try :
with open ( dashboard_1_path , ' r ' ) as f :
data = json . load ( f )
2023-10-18 19:43:26 +02:00
if ( len ( data [ ' elements ' ] ) == 0 ) : # there may exist some paths but paths without elements in dashboard is not very likely
2023-01-05 10:01:57 +01:00
return True
else :
return False
except : # file missing or bad json format
return True
2022-09-02 17:00:50 +02:00
def inform_missing_content ( self , whatsmissing : str ) :
if whatsmissing == " " :
return False
2023-10-18 19:43:26 +02:00
# Starting with cbpi 4.2.0, the craftbeerpi.service file will be created dynamically from the template file based on the user id.
# Therefore, the service file is replaced with a template file in the config folder
if whatsmissing . find ( " craftbeerpi.template " ) :
2024-01-20 11:17:27 +01:00
try :
self . copyDefaultFileIfNotExists ( " craftbeerpi.template " )
return False
except :
pass
2022-09-02 17:00:50 +02:00
print ( " *************************************************** " )
print ( f " CraftBeerPi config content not found: { whatsmissing } " )
print ( " Please run ' cbpi setup ' before starting the server " )
print ( " *************************************************** " )
return True
def check_for_file_or_folder ( self , path : str , file_or_folder : str = " " ) : # file_or_folder should be "file" or "folder" or "" if both is ok
if ( file_or_folder == " " ) : # file and folder is ok
if os . path . exists ( path ) :
return " "
else :
return " file or folder missing: " + path
if ( file_or_folder == " file " ) : # only file is ok
if ( os . path . isfile ( path ) ) :
return " "
else :
return " file missing: " + path
if ( file_or_folder == " folder " ) : # oly folder is ok
if ( os . path . isdir ( path ) ) :
return " "
else :
return " folder missing: " + path
return " usage of check_file_or_folder() function wrong. second Argument must either be ' file ' or ' folder ' or an empty string "
2022-02-19 12:33:11 +01:00
def copyDefaultFileIfNotExists ( self , file ) :
if self . config_file_exists ( file ) is False :
srcfile = os . path . join ( os . path . dirname ( __file__ ) , " config " , file )
2022-09-12 21:54:51 +02:00
destfile = os . path . join ( self . configFolderPath , file )
2022-02-19 12:33:11 +01:00
shutil . copy ( srcfile , destfile )
def create_config_file ( self ) :
self . copyDefaultFileIfNotExists ( " config.yaml " )
self . copyDefaultFileIfNotExists ( " actor.json " )
self . copyDefaultFileIfNotExists ( " sensor.json " )
self . copyDefaultFileIfNotExists ( " kettle.json " )
self . copyDefaultFileIfNotExists ( " fermenter_data.json " )
self . copyDefaultFileIfNotExists ( " step_data.json " )
self . copyDefaultFileIfNotExists ( " config.json " )
2023-10-15 09:59:05 +02:00
self . copyDefaultFileIfNotExists ( " craftbeerpi.template " )
2022-02-19 12:33:11 +01:00
self . copyDefaultFileIfNotExists ( " chromium.desktop " )
print ( " Config Folder created " )
2022-02-20 11:50:44 +01:00
def create_home_folder_structure ( configFolder ) :
configFolder . create_folders ( )
print ( " Folder created " )
2022-02-19 12:33:11 +01:00
def create_folders ( self ) :
2022-09-12 21:54:51 +02:00
pathlib . Path ( self . configFolderPath ) . mkdir ( parents = True , exist_ok = True )
2022-09-16 10:25:31 +02:00
pathlib . Path ( self . logsFolderPath ) . mkdir ( parents = True , exist_ok = True )
2022-09-12 21:54:51 +02:00
pathlib . Path ( os . path . join ( self . configFolderPath , ' dashboard ' , ' widgets ' ) ) . mkdir ( parents = True , exist_ok = True )
pathlib . Path ( os . path . join ( self . configFolderPath , ' recipes ' ) ) . mkdir ( parents = True , exist_ok = True )
pathlib . Path ( os . path . join ( self . configFolderPath , ' fermenterrecipes ' ) ) . mkdir ( parents = True , exist_ok = True )
pathlib . Path ( os . path . join ( self . configFolderPath , ' upload ' ) ) . mkdir ( parents = True , exist_ok = True )
2022-02-19 12:33:11 +01:00
2022-05-12 21:27:12 +02:00
def recursive_chown ( self , path , owner , group ) :
2022-09-02 17:00:50 +02:00
try :
for dirpath , dirnames , filenames in os . walk ( path ) :
shutil . chown ( dirpath , owner , group )
for filename in filenames :
shutil . chown ( os . path . join ( dirpath , filename ) , owner , group )
except :
print ( " problems assigning file or folder permissions " )
2023-10-15 09:59:05 +02:00
print ( " if this happened on windows its fine " )
print ( " if this happened in the dev container running inside windows its also fine but you might have to rebuild the container if you run into further problems " )