@@ -617,6 +617,128 @@ def settings_function_delete(form):
617617 flash_success_errors (error , action , url_for ('routes_settings.settings_function' ))
618618
619619
620+ def settings_function_update (form_del , form ):
621+ """
622+ Receive a function module file, check it for errors, replace the existing module
623+ """
624+ action = '{action} {controller}' .format (
625+ action = gettext ("Update" ),
626+ controller = TRANSLATIONS ['controller' ]['title' ])
627+ error = []
628+
629+ controller_info = None
630+ existing_controller_info = None
631+
632+ try :
633+ install_dir = os .path .abspath (INSTALL_DIRECTORY )
634+ tmp_directory = os .path .join (install_dir , 'mycodo/functions/tmp_functions' )
635+ assure_path_exists (tmp_directory )
636+ assure_path_exists (PATH_FUNCTIONS_CUSTOM )
637+ tmp_name = 'tmp_function_testing.py'
638+ full_path_tmp = os .path .join (tmp_directory , tmp_name )
639+
640+ controller_device_name = form_del .controller_id .data
641+
642+ if not form .update_controller_file .data :
643+ error .append ('No file present' )
644+ elif form .update_controller_file .data .filename == '' :
645+ error .append ('No file name' )
646+ else :
647+ form .update_controller_file .data .save (full_path_tmp )
648+
649+ if not error :
650+ # Load and validate the uploaded (new) module
651+ try :
652+ controller_info , status = load_module_from_file (full_path_tmp , 'functions' )
653+ if not controller_info or not hasattr (controller_info , 'FUNCTION_INFORMATION' ):
654+ error .append ("Could not load FUNCTION_INFORMATION dictionary from "
655+ "the uploaded controller module" )
656+ except Exception :
657+ error .append ("Could not load uploaded file as a python module:\n "
658+ "{}" .format (traceback .format_exc ()))
659+
660+ # Load the existing (old) module from disk to extract its function_name_unique
661+ existing_file_path = os .path .join (
662+ PATH_FUNCTIONS_CUSTOM , '{}.py' .format (controller_device_name .lower ()))
663+ try :
664+ existing_controller_info , status = load_module_from_file (existing_file_path , 'functions' )
665+ if not existing_controller_info or not hasattr (existing_controller_info , 'FUNCTION_INFORMATION' ):
666+ error .append ("Could not load FUNCTION_INFORMATION dictionary from "
667+ "the existing controller module" )
668+ except Exception :
669+ error .append ("Could not load existing controller module as a python module:\n "
670+ "{}" .format (traceback .format_exc ()))
671+
672+ if not error :
673+ if 'function_name_unique' not in controller_info .FUNCTION_INFORMATION :
674+ error .append (
675+ "'function_name_unique' not found in "
676+ "FUNCTION_INFORMATION dictionary" )
677+ elif controller_info .FUNCTION_INFORMATION ['function_name_unique' ] == '' :
678+ error .append ("'function_name_unique' is empty" )
679+ elif (controller_info .FUNCTION_INFORMATION ['function_name_unique' ].lower () !=
680+ existing_controller_info .FUNCTION_INFORMATION ['function_name_unique' ].lower ()):
681+ error .append (
682+ "'function_name_unique' must match the existing module name '{}', "
683+ "but '{}' was found" .format (
684+ existing_controller_info .FUNCTION_INFORMATION ['function_name_unique' ],
685+ controller_info .FUNCTION_INFORMATION ['function_name_unique' ]))
686+
687+ if 'function_name' not in controller_info .FUNCTION_INFORMATION :
688+ error .append ("'function_name' not found in FUNCTION_INFORMATION dictionary" )
689+ elif controller_info .FUNCTION_INFORMATION ['function_name' ] == '' :
690+ error .append ("'function_name' is empty" )
691+
692+ if 'dependencies_module' in controller_info .FUNCTION_INFORMATION :
693+ if not isinstance (controller_info .FUNCTION_INFORMATION ['dependencies_module' ], list ):
694+ error .append ("'dependencies_module' must be a list of tuples" )
695+ else :
696+ for each_dep in controller_info .FUNCTION_INFORMATION ['dependencies_module' ]:
697+ if not isinstance (each_dep , tuple ):
698+ error .append ("'dependencies_module' must be a list of tuples" )
699+ elif len (each_dep ) != 3 :
700+ error .append ("'dependencies_module': tuples in list must have 3 items" )
701+ elif not each_dep [0 ] or not each_dep [1 ] or not each_dep [2 ]:
702+ error .append (
703+ "'dependencies_module': tuples in list must "
704+ "not be empty" )
705+ elif each_dep [0 ] not in ['internal' , 'pip-pypi' , 'apt' ]:
706+ error .append (
707+ "'dependencies_module': first in tuple "
708+ "must be 'internal', 'pip-pypi', "
709+ "or 'apt'" )
710+
711+ if not error :
712+ # Determine filename from the uploaded module's function_name_unique
713+ unique_name = '{}.py' .format (controller_info .FUNCTION_INFORMATION ['function_name_unique' ].lower ())
714+
715+ # Move module from temp directory to function directory, overwriting the existing module
716+ full_path_final = os .path .join (PATH_FUNCTIONS_CUSTOM , unique_name )
717+ os .rename (full_path_tmp , full_path_final )
718+
719+ # Reload frontend to refresh the controllers
720+ cmd = '{path}/mycodo/scripts/mycodo_wrapper frontend_reload 2>&1' .format (
721+ path = install_dir )
722+ subprocess .Popen (cmd , shell = True )
723+ flash ('Frontend reloaded to scan for updated Controller Modules' , 'success' )
724+
725+ # Restart the backend if any Controller using this module is currently activated
726+ controller_activated = CustomController .query .filter (
727+ CustomController .device == controller_device_name ,
728+ CustomController .is_activated .is_ (True )).count ()
729+ if controller_activated :
730+ cmd = '{path}/mycodo/scripts/mycodo_wrapper daemon_restart 2>&1' .format (
731+ path = install_dir )
732+ subprocess .Popen (cmd , shell = True )
733+ flash ('Backend restarted to apply updated Controller Module' , 'success' )
734+
735+ except Exception as err :
736+ logger .exception ("Function Update" )
737+ error .append ("Exception: {}" .format (err ))
738+
739+ flash_success_errors (error , action , url_for ('routes_settings.settings_function' ))
740+
741+
620742def settings_action_import (form ):
621743 """
622744 Receive an action module file, check it for errors, add it to Mycodo controller list
0 commit comments