예제 #1
0
파일: exec.py 프로젝트: chiisaa/ConsolePi
    def confirm_and_spin(self, action_dict, *args, **kwargs):
        """
        called by menu_exec.
        Collects user Confirmation if operation warrants it (Powering off or cycle outlets)
        and Generates appropriate spinner text

        returns tuple
            0: Bool False indicates user abort otherwise True should be returned
            1: str spinner_text used in menu_exec while function runs
            3: str name (for rename operation)
        """
        pwr = self.pwr
        menu = self.menu
        _func = action_dict["function"].__name__
        _off_str = "{{red}}OFF{{norm}}"
        _on_str = "{{green}}ON{{norm}}"
        _cycle_str = "{{red}}C{{green}}Y{{red}}C{{green}}L{{red}}E{{norm}}"
        _type = _addr = None
        to_state = kwargs.get("desired_state")
        if _func in ["pwr_toggle", "pwr_cycle", "pwr_rename"]:
            _type = args[0].lower()
            _addr = args[1]
            _grp = action_dict["key"]
            if _type == "dli":
                port = kwargs["port"]
                if not port == "all":
                    port_name = pwr.data["dli_power"][_addr][port]["name"]
                    to_state = not pwr.data["dli_power"][_addr][port]["state"]
            elif _type == "esphome":
                port = port_name = kwargs["port"]
                if not port == "all":
                    to_state = not pwr.data['defined'][_grp]['is_on'][port]['state']
            else:
                port = f"{_type}:{_addr}"
                port_name = _grp
                to_state = not pwr.data["defined"][_grp]["is_on"]
        if _type == "dli" or _type == "tasmota" or _type == "esphome":
            host_short = utils.get_host_short(_addr)
        else:
            host_short = None

        prompt = spin_text = name = confirmed = None  # init
        if _func == "pwr_all":
            if kwargs["action"] == "cycle":
                prompt = "{} Power Cycle All Powered {} Outlets".format(
                    "" if _type is None else _type + ":" + host_short, _on_str
                )
                spin_text = "Cycling All{} Ports".format(
                    "" if _type is None else " " + _type + ":" + host_short
                )
            elif kwargs["action"] == "toggle":
                if not kwargs["desired_state"]:
                    prompt = "Power All{} Outlets {}".format(
                        "" if _type is None else " " + _type + ":" + host_short,
                        _off_str,
                    )
                spin_text = "Powering {} ALL{} Outlets".format(
                    menu.format_line(kwargs["desired_state"]).text,
                    "" if _type is None else _type + " :" + host_short,
                )
        elif _func == "pwr_toggle":
            if _type == "dli" and port == "all":
                prompt = "Power {} ALL {} Outlets".format(
                    _off_str if not to_state else _on_str, host_short
                )
            elif not to_state:
                if _type == "dli":
                    prompt = f"Power {_off_str} {host_short} Outlet {port}({port_name})"
                elif _type == "esphome":
                    prompt = f"Power {_off_str} {host_short} Outlet {port}"
                else:  # GPIO or TASMOTA
                    prompt = f"Power {_off_str} Outlet {_grp}({_type}:{_addr})"

            spin_text = "Powering {} {}Outlet{}".format(
                menu.format_line(to_state).text,
                "ALL " if port == "all" else "",
                "s" if port == "all" else "",
            )
        elif _func == "pwr_rename":
            try:
                name = input(
                    "New name for{} Outlet {}: ".format(
                        " " + host_short if host_short else "",
                        port_name
                        if not _type == "dli"
                        else str(port) + "(" + port_name + ")",
                    )
                )
            except KeyboardInterrupt:
                name = None
                confirmed = False
                print("")  # So header doesn't print on same line as aborted prompt when DEBUG is on
            if name:
                old_name = port_name
                _rnm_str = "{red}{old_name}{norm} --> {green}{name}{norm}".format(
                    red="{{red}}",
                    green="{{green}}",
                    norm="{{norm}}",
                    old_name=old_name,
                    name=name,
                )
                if _type == "dli":
                    prompt = "Rename {} Outlet {}: {} ".format(
                        host_short, port, _rnm_str
                    )
                else:
                    old_name = _grp
                    prompt = "Rename Outlet {}:{} {} ".format(
                        _type, host_short, _rnm_str
                    )

                spin_text = "Renaming Port"

        elif _func == "pwr_cycle":
            if _type == "dli" and port == "all":
                prompt = "Power {} ALL {} Outlets".format(_cycle_str, host_short)
            elif _type == "dli":
                prompt = "Cycle Power on {} Outlet {}({})".format(
                    host_short, port, port_name
                )
            elif _type == "esphome":
                _msg = f"{_grp}({host_short})" if _grp != host_short else f"{_grp}"
                prompt = f"Cycle Power on {_msg} Outlet {port}"
            else:  # GPIO or TASMOTA
                prompt = "Cycle Power on Outlet {}({})".format(port_name, port)
            spin_text = "Cycling {}Outlet{}".format(
                "ALL " if port == "all" else "", "s" if port == "all" else ""
            )

        if prompt:
            prompt = menu.format_line(prompt).text
            confirmed = (
                confirmed if confirmed is not None else utils.user_input_bool(prompt)
            )
        else:
            if _func != "pwr_rename":
                confirmed = True

        return confirmed, spin_text, name
예제 #2
0
파일: exec.py 프로젝트: chiisaa/ConsolePi
    def menu_exec(self, choice, menu_actions, calling_menu="main_menu"):
        '''Execute Menu Selection.  This method needs to be overhauled.

        The ConsolePiAction object defined but not used in __init__ is part of the plan for overhaul
        menu will build insance of the object for each selection. That will be used to determine what
        action to perform and what to do after etc.
        '''
        pwr = self.pwr

        if not config.debug and calling_menu not in ["dli_menu", "power_menu"]:
            os.system("clear")

        if (
            not choice.lower
            or choice.lower in menu_actions
            and menu_actions[choice.lower] is None
        ):
            (
                self.menu.rows,
                self.menu.cols,
            ) = (
                utils.get_tty_size()
            )  # re-calc tty size in case they've adjusted the window
            return

        else:
            ch = choice.lower
            try:  # Invalid Selection
                if isinstance(menu_actions[ch], dict):
                    if menu_actions[ch].get("cmd"):
                        # TimeStamp for picocom session log file if defined
                        menu_actions[ch]["cmd"] = menu_actions[ch]["cmd"].replace(
                            "{{timestamp}}", time.strftime("%F_%H.%M")
                        )

                        # -- // AUTO POWER ON LINKED OUTLETS \\ --
                        if (
                            config.power and "pwr_key" in menu_actions[ch]
                        ):
                            self.exec_auto_pwron(menu_actions[ch]["pwr_key"])

                        # -- // Print pre-connect messsge if provided \\ --
                        if menu_actions[ch].get("pre_msg"):
                            print(menu_actions[ch]["pre_msg"])

                        # --// execute the command \\--
                        try:
                            _error = None
                            if "exec_kwargs" in menu_actions[ch]:
                                c = menu_actions[ch]["cmd"]
                                _error = utils.do_shell_cmd(
                                    c, **menu_actions[ch]["exec_kwargs"]
                                )
                                if _error and self.autopwr_wait:
                                    # TODO simplify this after moving to action object
                                    _method = "ssh -t" if "ssh" in c else "telnet"
                                    if "ssh" in _method:
                                        _h = (
                                            c.split(f"{_method} ")[1]
                                            .split(" -p")[0]
                                            .split("@")[1]
                                        )
                                        _p = int(c.split("-p ")[1])
                                    elif _method == "telnet":
                                        c_list = c.split()
                                        _h = c_list[-2]
                                        _p = int(c_list[-1])

                                    def wait_for_boot():
                                        while True:
                                            try:
                                                if utils.is_reachable(_h, _p, silent=True):
                                                    break
                                                else:
                                                    time.sleep(3)
                                            except KeyboardInterrupt:
                                                self.autopwr_wait = False
                                                log.show("Connection Aborted")
                                                break

                                    print(
                                        "\nInitial Attempt Failed, but host is linked to an outlet that was"
                                    )
                                    print("off. Host may still be booting\n")
                                    utils.spinner(
                                        f"Waiting for {_h} to boot, CTRL-C to Abort",
                                        wait_for_boot,
                                    )
                                    if self.autopwr_wait:
                                        _error = utils.do_shell_cmd(c, **menu_actions[ch]["exec_kwargs"])
                                        self.autopwr_wait = False
                            else:
                                c = shlex.split(menu_actions[ch]["cmd"])
                                result = subprocess.run(c, stderr=subprocess.PIPE)
                                _stderr = result.stderr.decode("UTF-8")
                                if _stderr or result.returncode == 1:
                                    _error = utils.error_handler(c, _stderr)

                            if _error:
                                log.show(_error)

                            # -- // resize the terminal to handle serial connections that jack the terminal size \\ --
                            c = " ".join([str(i) for i in c])
                            if "picocom" in c:
                                os.system(
                                    "/etc/ConsolePi/src/consolepi-commands/resize >/dev/null"
                                )

                        except KeyboardInterrupt:
                            log.show("Aborted last command based on user input")

                    elif "function" in menu_actions[ch]:
                        args = (
                            menu_actions[ch]["args"]
                            if "args" in menu_actions[ch]
                            else []
                        )
                        kwargs = (
                            menu_actions[ch]["kwargs"]
                            if "kwargs" in menu_actions[ch]
                            else {}
                        )
                        confirmed, spin_text, name = self.confirm_and_spin(
                            menu_actions[ch], *args, **kwargs
                        )
                        if confirmed:
                            # update kwargs with name from confirm_and_spin method
                            if menu_actions[ch]["function"].__name__ == "pwr_rename":
                                kwargs["name"] = name

                            # // -- CALL THE FUNCTION \\--
                            if (
                                spin_text
                            ):  # start spinner if spin_text set by confirm_and_spin
                                with Halo(text=spin_text, spinner="dots2"):
                                    response = menu_actions[ch]["function"](
                                        *args, **kwargs
                                    )
                            else:  # no spinner
                                response = menu_actions[ch]["function"](*args, **kwargs)

                            # --// Power Menus \\--
                            if calling_menu in ["power_menu", "dli_menu"]:
                                if menu_actions[ch]["function"].__name__ == "pwr_all":
                                    with Halo(
                                        text="Refreshing Outlet States", spinner="dots"
                                    ):
                                        self.outlet_update(
                                            refresh=True, upd_linked=True
                                        )  # TODO can I move this to Outlets Class
                                else:
                                    _grp = menu_actions[ch]["key"]
                                    _type = menu_actions[ch]["args"][0]
                                    _addr = menu_actions[ch]["args"][1]
                                    # --// EVAL responses for dli outlets \\--
                                    if _type == "dli":
                                        host_short = utils.get_host_short(_addr)
                                        _port = menu_actions[ch]["kwargs"]["port"]
                                        # --// Operations performed on ALL outlets \\--
                                        if (
                                            isinstance(response, bool)
                                            and _port is not None
                                        ):
                                            if (
                                                menu_actions[ch]["function"].__name__
                                                == "pwr_toggle"
                                            ):
                                                self.spin.start(
                                                    "Request Sent, Refreshing Outlet States"
                                                )
                                                # threading.Thread(target=self.get_dli_outlets,
                                                # kwargs={'upd_linked': True, 'refresh': True}, name='pwr_toggle_refresh').start()
                                                upd_linked = (
                                                    True
                                                    if calling_menu == "power_menu"
                                                    else False
                                                )  # else dli_menu
                                                threading.Thread(
                                                    target=self.outlet_update,
                                                    kwargs={
                                                        "upd_linked": upd_linked,
                                                        "refresh": True,
                                                    },
                                                    name="pwr_toggle_refresh",
                                                ).start()
                                                if _grp in pwr.data["defined"]:
                                                    pwr.data["defined"][_grp]["is_on"][
                                                        _port
                                                    ]["state"] = response
                                                elif _port != "all":
                                                    pwr.data["dli_power"][_addr][_port][
                                                        "state"
                                                    ] = response
                                                else:  # dli toggle all
                                                    for t in threading.enumerate():
                                                        if (
                                                            t.name
                                                            == "pwr_toggle_refresh"
                                                        ):
                                                            t.join()  # if refresh thread is running join ~
                                                            # wait for it to complete.
                                                            # TODO Don't think this works or below
                                                            # wouldn't have been necessary.

                                                            # toggle all returns True (ON) or False (OFF) if command
                                                            # successfully sent.  In reality the ports
                                                            # may not be in the  state yet, but dli is working it.
                                                            # Update menu items to reflect end state
                                                            for p in pwr.data[
                                                                "dli_power"
                                                            ][_addr]:
                                                                pwr.data["dli_power"][
                                                                    _addr
                                                                ][p]["state"] = response
                                                            break
                                                self.spin.stop()
                                            # Cycle operation returns False if outlet is off, only valid on powered outlets
                                            elif (
                                                menu_actions[ch]["function"].__name__
                                                == "pwr_cycle"
                                                and not response
                                            ):
                                                log.show(
                                                    f"{host_short} Port {_port} is Off.  Cycle is not valid"
                                                )
                                            elif (
                                                menu_actions[ch]["function"].__name__
                                                == "pwr_rename"
                                            ):
                                                if response:
                                                    _name = pwr._dli[_addr].name(_port)
                                                    if _grp in pwr.data.get(
                                                        "defined", {}
                                                    ):
                                                        pwr.data["defined"][_grp][
                                                            "is_on"
                                                        ][_port]["name"] = _name
                                                    else:
                                                        threading.Thread(
                                                            target=self.outlet_update,
                                                            kwargs={
                                                                "upd_linked": True,
                                                                "refresh": True,
                                                            },
                                                            name="pwr_rename_refresh",
                                                        ).start()
                                                    pwr.data["dli_power"][_addr][_port][
                                                        "name"
                                                    ] = _name
                                        # --// str responses are errors append to error_msgs \\--
                                        # TODO refactor response to use new cpi.response(...)
                                        elif (
                                            isinstance(response, str)
                                            and _port is not None
                                        ):
                                            log.show(response)
                                        # --// Can Remove After Refactoring all responses to bool or str \\--
                                        elif isinstance(response, int):
                                            if (
                                                menu_actions[ch]["function"].__name__
                                                == "pwr_cycle"
                                                and _port == "all"
                                            ):
                                                if response != 200:
                                                    log.show(
                                                        "Error Response Returned {}".format(
                                                            response
                                                        )
                                                    )
                                            # This is a catch as for the most part I've tried to refactor so the pwr library
                                            # returns port state on success (True/False)
                                            else:
                                                if response in [200, 204]:
                                                    log.show(
                                                        "DEV NOTE: check pwr library ret=200 or 204"
                                                    )
                                                else:
                                                    _action = menu_actions[ch][
                                                        "function"
                                                    ].__name__
                                                    log.show(
                                                        f"Error returned from dli {host_short} when "
                                                        f"attempting to {_action} port {_port}"
                                                    )
                                    # --// EVAL responses for espHome outlets \\--
                                    elif _type == "esphome":
                                        host_short = utils.get_host_short(_addr)
                                        _port = menu_actions[ch]["kwargs"]["port"]
                                        # --// Operations performed on ALL outlets \\--
                                        if (isinstance(response, bool) and _port is not None):
                                            pwr.data['defined'][_grp]['is_on'][_port]['state'] = response
                                            if (
                                                menu_actions[ch]["function"].__name__
                                                == "pwr_cycle"
                                                and not response
                                            ):
                                                _msg = f"{_grp}({host_short})" if _grp != host_short else f"{_grp}"
                                                if _msg != _port:
                                                    _msg = f"{_msg} Port {_port} is Off. Cycle is not valid"
                                                else:
                                                    _msg = f"{_msg} is Off. Cycle is not valid"
                                                log.show(_msg)
                                        elif (isinstance(response, str) and _port is not None):
                                            log.show(response)

                                    # --// EVAL responses for GPIO and tasmota outlets \\--
                                    else:
                                        if (
                                            menu_actions[ch]["function"].__name__
                                            == "pwr_toggle"
                                        ):
                                            if _grp in pwr.data.get("defined", {}):
                                                if isinstance(response, bool):
                                                    pwr.data["defined"][_grp][
                                                        "is_on"
                                                    ] = response
                                                else:
                                                    pwr.data["defined"][_grp][
                                                        "errors"
                                                    ] = response
                                        elif (
                                            menu_actions[ch]["function"].__name__
                                            == "pwr_cycle"
                                            and not response
                                        ):
                                            log.show(
                                                "Cycle is not valid for Outlets in the off state"
                                            )
                                        elif (
                                            menu_actions[ch]["function"].__name__
                                            == "pwr_rename"
                                        ):
                                            log.show(
                                                "rename not yet implemented for {} outlets".format(
                                                    _type
                                                )
                                            )
                            elif calling_menu in ["key_menu", "rename_menu"]:
                                if response:
                                    log.show(response)
                        else:  # not confirmed
                            log.show("Operation Aborted by User")
                elif menu_actions[ch].__name__ in ["power_menu", "dli_menu"]:
                    menu_actions[ch](calling_menu=calling_menu)
                else:
                    menu_actions[ch]()
            except KeyError as e:
                if len(choice.orig) <= 2 or not self.exec_shell_cmd(choice.orig):
                    log.show(f"Invalid selection {e}, please try again.")
        return True