#!/usr/bin/python3
import sys, os, locale, requests, threading, time
from locale import gettext as _
from datetime import datetime
import gi, subprocess, time, pickle, signal, shutil
gi.require_version('Gtk', '3.0')
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import Gtk, GLib, Pango
from gi.repository import AyatanaAppIndicator3 as appindicator
if os.path.exists('updaterlib.py'):
  from updaterlib import *
else:
  from update_applet.updaterlib import *

import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)

os_data = sysinfo()

DOMAIN = 'rosa-update-system'
LOCALE_DIR = '/usr/share/locale'
locale.bindtextdomain(DOMAIN, LOCALE_DIR)
locale.textdomain(DOMAIN)

cfgdir = os.environ['HOME'] + '/.config/update_applet'
autostartdir = os.environ['HOME'] + '/.config/autostart'
desktopfile = '/usr/share/applications/update_applet.desktop'
kroko_gui = '/usr/libexec/kroko_gui'
get_key_url = 'https://mirror.rosa.ru/r/rosa-chrome-buy?version=' + os_data['platform']

updated = False
started = False
lastlog = []
sec =  60 # единица измерения 1 - sec, 60 - min
pwf_time = 30 # секунд
token_file = '/etc/dnf/vars/token'

# дефолт для сохраняемых настроек, после изменений здесь, нужно сбрасывать настройки апплета
config = {}
config['upd_times'] = (15, 60, 360, 720, 0) # в минутах
config['timeout'] = 360 # одно значение из списка выше
config['show_log'] = False
config['messages'] = []

UI = '/usr/share/update_applet/update_applet.ui'
update_wrp='update_applet.helper'
iconpath='/usr/share/update_applet'

if os.path.exists('update_applet'):
  UI = './update_applet.glade'
  update_wrp='./update_applet.helper'
  iconpath = os.path.abspath('./')
  kroko_gui = os.path.abspath('./kroko_gui')

ICON = os.path.join(iconpath, 'update_applet_ok.svg')


def set_icons():
    global green
    global yellow
    global red
    try:
        result = subprocess.run(["dnf", "repolist"], 
                              capture_output=True, 
                              text=True,
                              check=True)
        output = result.stdout.lower() + result.stderr.lower()
        is_testing = True if "testing" in output else False
    except:
        is_testing = False

    if is_testing:
        red = os.path.join(iconpath, 'update_applet_testing_error.svg')
        green = os.path.join(iconpath,  'update_applet_testing_ok.svg')
        yellow = os.path.join(iconpath, 'update_applet_testing_warning.svg')
    else:
        red = os.path.join(iconpath, 'update_applet_error.svg')
        green = os.path.join(iconpath,  'update_applet_ok.svg')
        yellow = os.path.join(iconpath, 'update_applet_warning.svg')

# True если ключ уже есть или если не нужен
def check_token():
  if not os.path.exists(token_file):
    return True
  elif os.stat(token_file).st_size != 0:
    return True
  else:
    return set_token()

def set_token(*args):
  stoptimers()
  token = get_token()
  if not token:
    return False
  ret = subprocess.Popen([ 'pkexec bash -c "echo ' + token + ' > ' + token_file + '"'], stdout=subprocess.PIPE, shell=True).wait()
  if ret != 0:
    dialog(first=_('Error'), second=_('Can not save token!'), mtype='error')
    return False
  runtimer(config['timeout'])
  return True

# вернет либо введенный в диалогово окошке ключ либо False
def get_token():
  token = dialog(first=_('Warning'), second=_('For access for Rosa repos please enter the key'), mtype='info',
                 entry=True, btn=[_("Confirm"), 1, _("I have no key"), 0 ], above=False)
  if not token:
    subprocess.Popen( ['nohup', 'xdg-open', get_key_url ],
                      start_new_session=True,
                      stdout=subprocess.DEVNULL,
                      stdin=subprocess.DEVNULL,
                      stderr=subprocess.DEVNULL )
    token = dialog(first=_('Warning'), second=_('For access for Rosa repos please enter the key'), mtype='info', entry=True,  btn=[_("Confirm"), 1, _("Remind later"), 0 ])
    if not token:
      return False
  token = str(token).strip()
  while not ( len(token) == 15 and token == token.lower() and token.isalnum() ):
    token = dialog(first=_('Wrong token format'), second=_('Please enter the key again'), mtype='info', entry=True, btn=[_("Confirm"), 1, _("Remind later"), 0 ])
    if not token:
      #os.execlp('xdg-open', 'xdg-open', get_key_url)
      return False
    token = str(token).strip()
  return token

def first_check():
  print('first check')
  first_counter = int(time.perf_counter())
  checknet = False
  waittime = 0
  while checknet == False and  waittime <= 60:
    print(f'Network check time: {waittime} sec')
    for addr in ('https://ya.ru', 'https://abf.io', 'https://google.com'):
      try:
        requests.get(addr).status_code
        checknet = True
        break
      except:
        time.sleep(1)
    waittime = int(time.perf_counter()) - first_counter
  GLib.idle_add(lambda text=_("Check repository permissions..."): app.ind_app.set_title(text))
  refresh()
  if not check_request(os_data, token_file):
    GLib.idle_add(lambda : (notify_send(_('Attention!'), _('Wrong or obsolete key, or error in the server side'), 'warning', None, None ), False)[1])
    GLib.idle_add(lambda: (set_token(), False)[1])
  GLib.idle_add(lambda text=_("Check updates..."): app.ind_app.set_title(text))
  fn_indicator_back()
  runtimer(config['timeout'])

def pickle_write():
    with open(cfgdir + '/dump', 'wb') as f:
       pickle.dump(config, f)

def runtimer(timeout):
  global timer_id
  if timeout != 0 and (not 'timer_id' in globals() or not timer_id):
    print('timer:' + str(config['timeout'] * sec) + 'sec')
    timer_id = GLib.timeout_add_seconds(timeout * sec, lambda: threading_me(fn_indicator_back))

def checkupd():
    global started
    global update_counter
    global lastlog
    global upd_lines
    upd_lines = 0
    started = True
    checked = False
    set_icons()
    for a in  (1, 2, 3):
        if runit(app, 'timeout 1m ' + update_wrp + ' refresh', _('Update repositories'), 94, get_repos(), 1):
          checked = True
          break
        echo(app, _('Update repositories') + ' - ERROR',  _('Update repositories') + ': ' + _('Update repositories') + ' ERROR, try:' + str(a) )
        print('refresh false, try: ' + str(a))
        time.sleep(a)
    refresh()
    if checked:
        lastlog = []
        updates_found = False
        echo(app, '==> ' + _('Check updates'), _('Check updates'))
        with subprocess.Popen([ 'timeout 1m ' + update_wrp + ' check' ], stdout=subprocess.PIPE, shell=True) as proc:
            for line in proc.stdout:
                if decode_it(line):
                    updates_found = True
                    header = _('Packages that need to be updated:')
                    echo(app, '==> ' + header,  _('Check updates') + ': ' + header)
                    lastlog.append(header)
                    break
        refresh()
        if updates_found:
            print('updates  found')
            with subprocess.Popen([ 'timeout 2m ' + update_wrp + ' listupd' ], stdout=subprocess.PIPE, shell=True) as proc:
                for line in proc.stdout:
                    decoded_line = decode_it(line)
                    if decoded_line:
                        echo(app, decoded_line,  _('Check updates') + ': ' + decoded_line)
                        lastlog.append(decoded_line)
                        upd_lines += 1
            echo(app, '\n', _('Check updates') + ': ' + _('Updates found'))
            app.update_button.show()
            app.pwf_button.show()
            app.ind_app.set_icon_full(yellow, 'yellow')
            update_counter = int(time.perf_counter())
        else:
            echo(app, '\n    ' + _('Updates not found'), _('Check updates') + ': ' + _('Updates not found'))
            app.ind_app.set_title(_('Already up to date') + '\n' + _('Last check: ') + timenow() )
            app.ind_app.set_icon_full(green, 'green')
    else:
        started = False
        print('check updates error')
        echo(app, 'ERROR!!!', 'ERROR: ' + _('Update repositories'))
        app.ind_app.set_icon_full(red, 'red')
        app.ind_app.set_title(_('Cannot refresh metadata') + '\n' + _('Last check: ') + timenow() )
        GLib.idle_add(lambda : (notify_send(_('ERROR:'), _('Cannot refresh metadata'), 'error', None, None ), False)[1])

    started = False
    app.progress.hide()
    refresh()
    GLib.idle_add(lambda: (check_os_messages(), False)[1])
    return False

def stoptimers():
    global timer_id
    global once_timer_id
    if 'once_timer_id' in globals() and once_timer_id > 0 and GLib.MainContext.default().find_source_by_id(once_timer_id):
      try:
        GLib.Source.remove(once_timer_id)
      except (NameError, TypeError, ValueError, Warning, GLib.GError, GLib.Error):
        pass
    if 'timer_id' in globals() and timer_id > 0 and GLib.MainContext.default().find_source_by_id(timer_id):
      GLib.Source.remove(timer_id)
    timer_id = False
    first_timer_id = False
    print('stop timers')

def timelabel(time):
  if time == 0:
      return _('On startup')
  t = _('min')
  if time >= 60:
      time = time / 60
      t = _('h')
  return f'{str(time)} {t}.'

def timenow():
  return datetime.now().strftime('%d.%m.%Y %H:%M')

def check_autostart():
    newfile =True
    if not os.path.exists(os.path.join(autostartdir, 'update_applet.desktop')):
        return True
    with open(os.path.join(autostartdir, 'update_applet.desktop'), 'r') as desktop:
        lines = desktop.readlines()
        for line in lines:
            line = line.strip()
            if line.startswith('Hidden'):
                newfile = False
                if 'false' in line:
                    return True
        if newfile:
            return True
    return False

def show_icon_update(times):
  for itime  in times:
    app.submenu_icon_item[itime] = Gtk.MenuItem( label=timelabel(itime) )
    app.submenu_icon_item[itime].connect("activate", fn_icon_update[itime], '')
    app.submenu_icon_item[itime].show()
    app.submenu_icon_update.append(app.submenu_icon_item[itime])

def runit(app, cmd, action, percent, lines=None, skip=None):
  result = None
  thread_complete = threading.Event()
  echo(app, '==> ' + action, action)
  if lines:
    # расчет шага движения прогрессбара
    cur_progress = app.progress.get_fraction()
    delta = percent / 100 - cur_progress
    step = delta / lines

  def ftarget():
    nonlocal skip, action, cur_progress, step
    with subprocess.Popen([cmd], stdout=subprocess.PIPE, shell=True) as proc:
          for line in proc.stdout:
              decoded_line = decode_it(line)
              if decoded_line:
                  if decoded_line.startswith('<=v=>'):
                      action = _(decoded_line.replace('<=v=>', ''))
                  elif decoded_line.startswith('[SKIPPED]'):
                      continue
                  else:
                      GLib.idle_add(lambda line=decoded_line: echo(app, line, action + ': ' + line))
                      if skip:
                          skip -= 1
                      elif lines:
                          if  (cur_progress + step ) * 100 <= percent:
                              # двигаемся по расчитанным примерно шагам
                              cur_progress += step
                          else:
                              # если расчет не верен и уперлись в лимит двигаемся дальше уменьшая шаги
                              step = (1 - cur_progress) / 2.1
                              cur_progress += step
                          if cur_progress > 0.999:
                              cur_progress = 0.999
                          GLib.idle_add(lambda prgrs=cur_progress: app.progress.set_fraction(prgrs))
                          GLib.idle_add(lambda txt=str(format(cur_progress * 100, '.2f') ) + '%': app.progress.set_text(txt))
          ret = proc.wait()
    if  cur_progress * 100 < percent:
      # если прогресс не дотянул до запланированного, дотягиваем
      GLib.idle_add(lambda prgrs=percent / 100: app.progress.set_fraction(prgrs))
      GLib.idle_add(lambda txt=str(percent) + '%': app.progress.set_text(txt))
    return ret

  def run_target_function():
    nonlocal result, thread_complete
    result = ftarget()
    thread_complete.set()

  thread = threading.Thread(target=run_target_function, daemon=True)
  thread.start()

  while not thread_complete.is_set():
    time.sleep(1)
    refresh()
  return not result

def read_code_file():
    code_file = "/run/update_trigger.code"
    try:
        with open(code_file, 'r') as fcode:
            content = fcode.read().strip()
            return int(content) if content else 0
    except (ValueError, FileNotFoundError):
        return 0
    except Exception as e:
        print(f"Read code file failed: {e}")
        return 0

def update():
    global updated
    global started
    global upd_lines
    started = True
    updated = False
    app.update_button.hide()
    app.pwf_button.show()
    app.progress.set_fraction(0.0)
    app.progress.set_text('0' + '%')
    app.progress.show()
    # команда, текст заголовка, максимальный процент прогресс бара, кол-во строк расчетное, кол-во лишних строк перед
    if not runit(app, 'timeout 15m ' + update_wrp + ' update', _('Install packages'), 98, upd_lines * 5 + 40 , 1):
      echo(app, 'ERROR!!!', 'ERROR: ' + update_wrp)
      app.ind_app.set_icon_full(red, 'red')
      app.update_button.show()
      app.ind_app.set_title(_('Automatic update failed. Update check timer stopped.') + '\n' +
                        _('Try upgrading later, or use system package manager'))
      notify_send(_('Automatic update failed. Update check timer stopped.'), \
                  _('Try upgrading later, or use system package manager'), 'error', None, None)
      started = False
      halt_timer(pwf_time)
      return False
    updated = True
    started = False
    echo(app, '', _('Complete'))
    trigger_code = read_code_file()
    if trigger_code == 1:
        notify_send(_('Warning!'), _('A system reboot is required to complete the update process.'),
                    'error', _('Reboot'), lambda *args, p=pwf_time: reboot_timer(p))
    else:
        notify_send(_('Complete!'), _('The system has been successfully updated'), 'ok', None, None)

    app.progress.hide()
    app.ind_app.set_icon_full(green, 'green')
    app.ind_app.set_title(_('Already up to date') + '\n' + _('Last check: ') + timenow() )
    runtimer(config['timeout'])
    halt_timer(pwf_time)
    GLib.idle_add(lambda: (check_os_messages(), False)[1])
    return False

def check_os_messages():
    global config
    try:
        messages = os.listdir('/usr/share/os_messages')
        for a in messages:
            if '.' in a:
                continue
            if a not in config['messages']:
                show_os_message(a)
    except Exception as e:
        print('show os messages error:', e)

def show_os_message(msg):
    lang_suffix = os.environ.get('LANG', '')
    lmsg = f'{msg}.{lang_suffix}'
    if os.path.exists(os.path.join('/usr/share/os_messages', lmsg)):
        file=(os.path.join('/usr/share/os_messages', lmsg))
    else:
        file=(os.path.join('/usr/share/os_messages', msg))
    try:
        with open(file, 'r') as f:
            lines = [line.rstrip('\n') for line in f if line.strip()]
            header = lines[0] if lines else ''
            message = '\n'.join(lines[1:]) if len(lines) > 1 else ''
            notify_send(header,
                        message,
                        'error',
                        _('do not show again'),
                        lambda *args, msg=msg: add_msg_exclude(msg)
                        )

    except Exception as e:
        print('show os message error:', e)

def add_msg_exclude(msg):
    config['messages'].append(msg)
    pickle_write()

def get_repos():
    N = -1
    with subprocess.Popen(['dnf repolist'], stdout=subprocess.PIPE, shell=True) as proc:
        for line in proc.stdout:
            decoded_line = decode_it(line)
            if decoded_line:
                N += 1
    return N

def clear():
    app.textbuffer.set_text('\n')
    app.statusbar.push(app.context_id, '')
    app.progress.set_fraction(0)
    app.progress.set_text('0%')
    if config.get('show_log', False):
        app.log_chkbx.set_active(True)
        app.text_window.show()
    else:
        app.log_chkbx.set_active(False)
        app.text_window.hide()
    if hasattr(app, 'menu_kroko'):
        if is_nvidia():
          app.menu_kroko.set_label(label=_('Remove NVIDIA driver'))
        else:
          app.menu_kroko.set_label(label=_('Install NVIDIA driver'))
        app.menu_kroko.show()

def halt_timer(time):
    if app.builder.get_object('CHECK_HALT').get_active():
        if not poweroff('poweroff'):
            pwf_id = GLib.timeout_add_seconds(time, os.execlp, 'poweroff', 'poweroff')
            if dialog(first=_("Time to shutdown: ")+ str(pwf_time), second=_("Cancel?"), btn=[Gtk.STOCK_YES, 1, Gtk.STOCK_NO, 0 ] ):
                GLib.Source.remove(pwf_id)

def reboot_timer(time):
    if not poweroff('reboot'):
        pwf_id = GLib.timeout_add_seconds(time, os.execlp, 'reboot', 'reboot')
        if dialog(first=_("Time to reboot: ")+ str(pwf_time), second=_("Cancel?"), btn=[Gtk.STOCK_YES, 1, Gtk.STOCK_NO, 0 ] ):
            GLib.Source.remove(pwf_id)

# функции для апплета
def fn_indicator_once():
    fn_indicator()
    return False

def fn_indicator_back():
    if not app.window.props.visible:
      fn_indicator()
    return True

def fn_indicator():
    global update_couter
    checkupd()
    updates = ''
    n = 40
    for decoded_line in lastlog:
        if not decoded_line.startswith(' '):
            continue
        try:
            decoded_line.split()[4]
            updates += decoded_line.split()[0] + ' - ' + decoded_line.split()[2] + '\n'
        except:
            pass
        n = n - 1
        if n == 0:
            updates += '...'
            break
    if updates:
        app.ind_app.set_icon_full(yellow, 'yellow')
        app.ind_app.set_title(updates + '\n' + _('Last check: ') + timenow() )
        if app.window.props.visible:
            notify_send(_('Attention!'), _('Found packages to update'), 'warning', None, None )
        else:
            notify_send(_('Attention!'), _('Found packages to update'), 'warning', _('Get updates'), app.handler.get_updates )
        update_couner = int(time.perf_counter())
        print('auto check')
    if config['timeout'] == 0:
        print('kill timer')
        return False
    return True

#генератор функций для каждого значения в кортеже upd_times
fn_icon_update = {}
def f2(time_):
  global config
  global timer_id
  app.submenu_icon_item[config['timeout']].set_label( timelabel(config['timeout']) )
  config['timeout'] = time_
  app.submenu_icon_item[config['timeout']].set_label(timelabel(config['timeout']) + " *" )
  stoptimers()
  runtimer(config['timeout'])
  pickle_write()
for i in config['upd_times']:
  fn_icon_update[i] = lambda x, y, i=i: f2(i)

class WinHandler:
    def on_CHECK_HALT_activate(self, button):
        pass

    def on_show_log_toggled(self, button):
        if app.log_chkbx.get_active():
            app.text_window.show()
            config['show_log'] = True
        else:
            w, h = app.window.get_size()
            app.text_window.hide()
            app.window.resize(w, app.window.get_preferred_height()[1])
            config['show_log'] = False
        pickle_write()

    def onSUBMIT(self, button):
        bat = powercheck()
        if bat < 40:
            if not  dialog(first=_("Attention!\nThe battery charge status is low: ") + f'{bat}%',
                           second=_("Continue?"), btn=[Gtk.STOCK_YES, 1, Gtk.STOCK_NO, 0 ] ):
                return
        clear()
        GLib.idle_add(update)

    def onEXIT(self, button):
        global timer_id
        global updated
        global started
        app.ind_app.set_status (appindicator.IndicatorStatus.ACTIVE)
        app.window.hide()
        if started and not updated:
            app.ind_app.set_icon_full(yellow, 'yellow')
            app.ind_app.set_title(_('Update process started...'))
        else:
            clear()
            runtimer(config['timeout'])
        if updated:
            updated = False

class IndHandler:
  def autostart(*args):
      newfile = True
      enabled = True
      if os.path.exists(os.path.join(autostartdir, 'update_applet.desktop')):
          with open(os.path.join(autostartdir, 'update_applet.desktop'), 'r') as desktop:
              lines = desktop.readlines()
          with open(os.path.join(autostartdir, 'update_applet.desktop'), 'w') as desktop:
              for line in lines:
                  line = line.strip()
                  if line.startswith('Hidden'):
                      newfile = False
                      if 'true' in line:
                          print('Hidden=false', file=desktop)
                      else:
                          print('Hidden=true', file=desktop)
                          enabled = False
                  else:
                      print(line, file=desktop)
              if newfile:
                  print('Hidden=true', file=desktop)
                  enabled = False
      else:
          if not os.path.exists(autostartdir):
              os.makedirs(autostartdir)
          shutil.copy(desktopfile, os.path.join(autostartdir, 'update_applet.desktop'))
          with open(os.path.join(autostartdir, 'update_applet.desktop'), 'a') as desktop:
              print('Hidden=true', file=desktop)
              enabled = False

      if enabled:
          app.menu_autostart.set_label(_('Disable autostart'))
      else:
          app.menu_autostart.set_label(_('Enable autostart'))
          stoptimers()
          dialog(first=_("Applet autostart disabled"),
                     second=_("To re-enable autostart: Menu -> System -> Update indicator -> Enable autostart"),
                     mtype='info',
                     btn=[_("I Understand"), 0] )
          runtimer(config['timeout'])

  def get_updates(*args):
    global timer_id
    global started
    global update_counter
    global updated
    stoptimers()
    app.window.show_all()
    app.ind_app.set_status (appindicator.IndicatorStatus.PASSIVE)
    if updated:
      updated = False
      update_counter = int(time.perf_counter()) - 100
    else:
      if 'update_counter' in globals():
          timedelta = int(time.perf_counter()) - update_counter
      else:
          timedelta = 100
      print('last check: ' + str(timedelta) + 'sec')
      if  timedelta > 30:
        app.update_button.hide()
        if not started:
          app.pwf_button.hide()
          clear()
          GLib.idle_add(fn_indicator_once)
      elif started:
        app.update_button.hide()
      else:
        clear()
        app.progress.hide()
        for decoded_line in lastlog:
          echo(app, decoded_line,  _('Check updates') + ': ' + decoded_line)
        echo(app, '\n',  '{text} ({n}{sec})'.format(text = _('Updates found'), n = str(timedelta), sec = _('sec ago')))
    refresh()

  def exit(*args):
    try:
      os.remove(lockfile)
    except:
      pass
    Gtk.main_quit()

  def set_token(*args):
    if not set_token():
      return
    if not check_request(os_data, token_file):
      notify_send(_('Attention!'), _('Wrong or obsolete key, or error in the server side'), 'warning', False )
      fn_set_token()

  def krokodil(*args):
    def get_nv_driver():
      result = subprocess.run(['kroko-cli', 'best-driver'], stdout=subprocess.PIPE, text=True)
      return result.stdout.strip()

    if is_nvidia():
      if not  dialog(first=_("Remove proprietary NVIDIA driver"),
                     second=_("After removing the proprietary NVIDIA driver, the system will use the open-source Nouveau driver."),
                     btn=[_("Remove"), 1, _("Cancel"), 0 ] ):
        return
    else:
      driver = progress_run(get_nv_driver, title=_('Wait..'),
                            label=_('Find the best NVIDIA driver for your graphics card'))
      if not  dialog(first=_("Installing NVIDIA Drivers"),
                     second=_("Your graphics card is currently using open-source drivers, which may provide insufficient performance in many cases.\n\
Would you like to install the proprietary NVIDIA driver: ") + driver + '?',
                     btn=[_("Install"), 1, _("Cancel"), 0 ] ):
        return
    env = os.environ.copy()
    cmdline = ['pkexec', 'env', 'SAVE_PWD=' + env['PWD'],
               'DISPLAY=' + env.get('DISPLAY', ':0'),
               'XAUTHORITY=' + env.get('XAUTHORITY', ''),
               'DBUS_SESSION_BUS_ADDRESS=' + env.get('DBUS_SESSION_BUS_ADDRESS', ''),
               kroko_gui, '--force' ]
    subprocess.Popen(cmdline, env=env, start_new_session=True)
    app.menu_kroko.hide()

class Applet:
  def __init__(self):
    self.submenu_icon_item = {}
    self.handler = IndHandler()

    # update window
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    builder = Gtk.Builder()
    self.builder = builder
    builder.set_translation_domain(DOMAIN)
    builder.add_from_file(UI)

    textaria = builder.get_object('TEXTAREA')
    textaria.override_font(Pango.FontDescription('Monospace 10'))
    self.textbuffer = textaria.get_buffer()

    self.progress = builder.get_object('PROGRESS')
    self.statusbar = builder.get_object('STATUS')
    self.text_window = builder.get_object('SCROLL')
    self.context_id = self.statusbar.get_context_id('updater')

    self.log_chkbx = builder.get_object('show_log')
    self.log_chkbx.set_label(_('Updates log'))

    self.update_button = builder.get_object('SUBMIT')
    self.pwf_button = builder.get_object('CHECK_HALT')
    builder.connect_signals(WinHandler())
    self.scroll = builder.get_object('SCROLL')

    self.window = builder.get_object('window_main')
    self.window.set_icon_from_file(ICON)
    self.window.connect("destroy", Gtk.main_quit)

    # applet
    self.ind_app = appindicator.Indicator.new (
       "update-status-indicator",
       green,
       appindicator.IndicatorCategory.SYSTEM_SERVICES)
    self.ind_app.set_status (appindicator.IndicatorStatus.ACTIVE)
    self.ind_app.set_title(_("Update applet") + ' ' + get_os_name())

    # create a menu
    menu = Gtk.Menu()

    # updates check frequency
    submenuItem2 = Gtk.MenuItem(label=_('Updates check frequency'))
    self.submenu_icon_update = Gtk.Menu()
    submenuItem2.set_submenu(self.submenu_icon_update)
    menu.append(submenuItem2)
    submenuItem2.show()

    # update OS
    menu_updates = Gtk.MenuItem(label=_('Get updates'))
    menu_updates.connect("activate", self.handler.get_updates, '')
    menu_updates.show()
    menu.append(menu_updates)

    # repository key
    if os.path.exists(token_file):
      menu_token = Gtk.MenuItem(label=_('Set token'))
      menu_token.connect("activate", self.handler.set_token, '')
      menu_token.show()
      menu.append(menu_token)

    # auto kokodil
    if kroko_status() or is_nvidia():
      self.menu_kroko = Gtk.MenuItem(label=_('Install NVIDIA driver'))
      self.menu_kroko.connect("activate", self.handler.krokodil, '')
      self.menu_kroko.show()
      menu.append(self.menu_kroko)

    # separator
    separ = Gtk.SeparatorMenuItem()
    menu.append(separ)
    separ.show()

    # toggle autostart
    if check_autostart():
        autostart_label = (_('Disable autostart'))
    else:
        autostart_label = (_('Enable autostart'))
    self.menu_autostart = Gtk.MenuItem(label=autostart_label)
    self.menu_autostart.connect("activate", self.handler.autostart, '')
    self.menu_autostart.show()
    menu.append(self.menu_autostart)

    # exit
    menu_exit = Gtk.MenuItem(label=_('Exit'))
    menu_exit.connect("activate", self.handler.exit, '')
    menu_exit.show()
    menu.append(menu_exit)

    menu.show()
    self.ind_app.set_menu(menu)

  def run(self):
    Gtk.main()

if __name__ == '__main__':
  # самоубиться если лайв ОС
  if os.path.exists('/dev/mapper/live-base'):
    print('Live OS detected, quit!')
    quit()

  # самоубиться если UIRD
  if os.path.exists('/etc/initvars'):
    print('The OS was booted using UIRD, exiting!')
    quit()

  # самоубиться если другой экземпляр апплета запущен
  os.makedirs(os.path.join('/run/user', str(os.getuid())), exist_ok=True)
  lockfile = os.path.join('/run/user', str(os.getuid()), 'update_applet')
  proc = is_allready_running(lockfile)
  if proc and 'update_applet' in proc:
      print('ERROR')
      dialog(first=_('ERROR'), second=_('Another update_applet was found!'), mtype='error', btn=[_("I Understand"), 0] )
      quit()

  # устанавливаем новый lock файл.
  with open(lockfile, 'w') as f:
      print(str(os.getpid()), file=f)

  # сбросить настройки если запустить с любым аргументом
  if len(sys.argv) > 1:
    print( sys.argv[0], '- tray applet to update sysytem\n')
    if os.path.isfile(f'{cfgdir}/dump'):
        try:
            os.remove(f'{cfgdir}/dump')
        except:
            print('Cannot remove dump')
    if os.path.isfile(f'{autostartdir}/update_applet.desktop'):
        try:
            os.remove(f'{autostartdir}/update_applet.desktop')
        except:
            print('Cannot remove desktop file from autostart dir')
    os.remove(lockfile)
    quit()

  if os.path.exists(cfgdir + '/dump'):
    with open(cfgdir + '/dump', 'rb') as f:
      config = pickle.load(f)
      if 'messages' not in config.keys():
          config['messages'] = []
  else:
    os.makedirs(cfgdir, exist_ok=True)
    pickle_write()

  first_timeout = 10
  DE = detect_DE()
  if DE == 'plasma':
      # без задержки появления индикатора в трее апплет запускается,
      # но работает не корректно в plasma, причина пока не ясна
      time.sleep(5)
      # увеличена задержка перед первой проверкой обновлений,
      # при одновременном запуске с baloo dnf refresh сильно нагружает
      # систему мешая нормальной загрузке plasma
      first_timeout = 40

  if not check_token():
    quit()
  
  set_icons()

  # запуск приложения
  app = Applet()
  clear()
  show_icon_update(config['upd_times'])
  app.submenu_icon_item[config['timeout']].set_label(timelabel(config['timeout']) + " *" )
  once_timer_id = GLib.timeout_add_seconds(first_timeout , lambda: threading_me(first_check) )
  notify_init(_('Update applet'))
  def monitor_glib():
    print("[Monitor] GLib main loop is running")
    return True  # Возвращаем True, чтобы функция вызывалась снова

  # Добавляем мониторинг каждую секунду
  #GLib.timeout_add_seconds(3, monitor_glib)
  app.run()


