#!/usr/sbin/python3
# cpupower-gui-helper.py

"""
Copyright (C) 2017-2020 [RnD]²

This file is part of cpupower-gui.

cpupower-gui is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

cpupower-gui is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with cpupower-gui.  If not, see <http://www.gnu.org/licenses/>.

Author: Evangelos Rigas <erigas@rnd2.org>
"""

import gettext
import locale
import sys
from pathlib import Path

import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib

sys.path.insert(1, "/usr/share/cpupower-gui")

import cpupower_gui.utils as util

localedir = "/usr/share/locale"

locale.bindtextdomain("cpupower-gui", localedir)
locale.textdomain("cpupower-gui")
gettext.bindtextdomain("cpupower-gui", localedir)
gettext.textdomain("cpupower-gui")

SYS_PATH = "/sys/devices/system/cpu/cpu{}/cpufreq"
FREQ_MIN = "scaling_min_freq"
FREQ_MAX = "scaling_max_freq"
GOVERNOR = "scaling_governor"
ONLINE_PATH = "/sys/devices/system/cpu/cpu{}/online"
PERF_PREF = "energy_performance_preference"


class CpupowerGui_DBus(dbus.service.Object):
    def __init__(self, loop):
        self.loop = loop
        self.bus = dbus.SystemBus()
        bus_name = dbus.service.BusName("org.rnd2.cpupower_gui.helper", bus=self.bus)
        dbus.service.Object.__init__(self, bus_name, "/org/rnd2/cpupower_gui/helper")
        self.init_polkit()
        self.authorized = {}

    def init_polkit(self):
        """Set polkit flags"""
        self.name = self.bus.get_unique_name()
        self.subject = ("system-bus-name", {"name": self.name})
        self.details = {}
        # details = {'polkit.message':'This is to authenticate for systemd units'}
        self.flags = 1  # AllowUserInteraction flag
        self.cancellation_id = ""  # No cancellation id

    def _is_authorized(self, sender, action_id="org.rnd2.cpupower_gui.apply_runtime"):
        """Checks if sender is authorized to perform the action with action_id

        Args:
            sender: D-Bus client name
            action_id: PolicyKit1 action to be performed

        Returns:
            auth: 0 or 1 depending if user is authorized or not

        """
        # If user was previously authorized
        if sender in self.authorized:
            if self.authorized[sender]:
                return 1

        # Check if user is authorized using PolicyKit1
        proxy = self.bus.get_object(
            "org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority"
        )
        authority = dbus.Interface(
            proxy, dbus_interface="org.freedesktop.PolicyKit1.Authority"
        )
        subject = ("system-bus-name", {"name": sender})
        result = authority.CheckAuthorization(
            subject, action_id, self.details, self.flags, self.cancellation_id
        )
        auth = bool(result[0])
        self.authorized[sender] = auth
        return auth

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", in_signature="i", out_signature="(ii)"
    )
    def get_cpu_frequencies(self, cpu):
        if self.is_present(cpu) and self.is_online(cpu):
            return util.read_freqs(cpu)
        return 0, 0

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", in_signature="i", out_signature="(ii)"
    )
    def get_cpu_limits(self, cpu):
        if self.is_present(cpu) and self.is_online(cpu):
            return util.read_freq_lims(cpu)
        return 0, 0

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", in_signature="i", out_signature="as"
    )
    def get_cpu_governors(self, cpu):
        if self.is_present(cpu) and self.is_online(cpu):
            return util.read_govs(cpu)
        return [""]

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", in_signature="i", out_signature="as"
    )
    def get_cpu_energy_preferences(self, cpu):
        if self.is_present(cpu) and self.is_online(cpu):
            return util.read_available_energy_prefs(cpu)
        return [""]

    @dbus.service.method("org.rnd2.cpupower_gui.helper", out_signature="ai")
    def get_cpus_online(self):
        return util.cpus_online()

    @dbus.service.method("org.rnd2.cpupower_gui.helper", out_signature="ai")
    def get_cpus_offline(self):
        return util.cpus_offline()

    @dbus.service.method("org.rnd2.cpupower_gui.helper", out_signature="ai")
    def get_cpus_available(self):
        return util.cpus_available()

    @dbus.service.method("org.rnd2.cpupower_gui.helper", out_signature="ai")
    def get_cpus_present(self):
        return util.cpus_present()

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", in_signature="i", out_signature="i"
    )
    def cpu_allowed_offline(self, cpu):
        path = Path(ONLINE_PATH.format(cpu))
        return int(path.exists())

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", in_signature="i", out_signature="s"
    )
    def get_cpu_governor(self, cpu):
        if self.is_present(cpu) and self.is_online(cpu):
            return util.read_governor(cpu)
        return ""

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", in_signature="i", out_signature="s"
    )
    def get_cpu_energy_preference(self, cpu):
        if self.is_present(cpu) and self.is_online(cpu):
            return util.read_energy_pref(cpu)
        return ""

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper",
        in_signature="iii",
        out_signature="i",
        sender_keyword="sender",
    )
    def update_cpu_settings(self, cpu, freq_min_hw, freq_max_hw, sender=None):
        if self._is_authorized(sender):
            ret = self._update_cpu(int(cpu), int(freq_min_hw), int(freq_max_hw))
            return ret
        else:
            return -1

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper",
        in_signature="i",
        out_signature="i",
        sender_keyword="sender",
    )
    def set_cpu_online(self, cpu, sender=None):
        if self._is_authorized(sender):
            sys_file = Path(ONLINE_PATH.format(cpu))
            sys_file.write_text("1")
            return 0
        else:
            return -1

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper",
        in_signature="i",
        out_signature="i",
        sender_keyword="sender",
    )
    def set_cpu_offline(self, cpu, sender=None):
        if self._is_authorized(sender):
            sys_file = Path(ONLINE_PATH.format(cpu))
            sys_file.write_text("0")
            return 0
        else:
            return -1

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper",
        in_signature="is",
        out_signature="i",
        sender_keyword="sender",
    )
    def update_cpu_governor(self, cpu, governor, sender=None):
        if self._is_authorized(sender):
            ret = self._update_cpu_governor(int(cpu), str(governor))
            return ret
        else:
            return -1

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper",
        in_signature="is",
        out_signature="i",
        sender_keyword="sender",
    )
    def update_cpu_energy_prefs(self, cpu, pref, sender=None):
        if pref not in util.read_available_energy_prefs(cpu):
            return 0

        if self._is_authorized(sender):
            ret = self._update_cpu_energy_prefs(int(cpu), str(pref))
            return ret
        else:
            return -1

    @dbus.service.method(
        "org.rnd2.cpupower_gui.helper", sender_keyword="sender", out_signature="i"
    )
    def isauthorized(self, sender=None):
        if sender:
            auth = self._is_authorized(sender)
            return auth
        else:
            return -1

    @staticmethod
    def is_online(cpu):
        return cpu in util.cpus_online()

    @staticmethod
    def is_present(cpu):
        return cpu in util.cpus_present()

    def _update_cpu(self, cpu, fmin, fmax):
        if self.is_present(cpu) and self.is_online(cpu):
            try:
                sys_path = Path(SYS_PATH.format(cpu))

                sys_file = sys_path / FREQ_MIN
                sys_file.write_text(str(fmin))

                sys_file = sys_path / FREQ_MAX
                sys_file.write_text(str(fmax))
                return 0
            except IOError as e:
                return -13
        else:
            return -1

    def _update_cpu_governor(self, cpu, governor):
        if self.is_present(cpu) and self.is_online(cpu):
            try:
                sys_path = Path(SYS_PATH.format(cpu))
                sys_file = sys_path / GOVERNOR
                sys_file.write_text(governor)
                return 0
            except IOError as e:
                return -13
        else:
            return -1

    def _update_cpu_energy_prefs(self, cpu, pref):
        if self.is_present(cpu) and self.is_online(cpu):
            try:
                sys_path = Path(SYS_PATH.format(cpu))
                sys_file = sys_path / PERF_PREF
                if sys_file.exists():
                    sys_file.write_text(pref)
                return 0
            except IOError as e:
                return -13
        else:
            return -1

    @dbus.service.method("org.rnd2.cpupower_gui.helper", sender_keyword="sender")
    def quit(self, sender=None):
        print("Request to close by {}".format(sender))
        print("\nThe cpupower_gui_helper will now close...")
        self.loop.quit()


if __name__ == "__main__":
    loop = GLib.MainLoop()
    DBusGMainLoop(set_as_default=True)
    dbus_service = CpupowerGui_DBus(loop)
    try:
        loop.run()
    except KeyboardInterrupt:
        print("\nThe cpupower_gui_helper will now close...")
        loop.quit()

# vim:set filetype=python shiftwidth=4 softtabstop=4 expandtab:
