#!/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 *

SHOW_LOG = False
PULSE = False

update_cmd='pkexec barium update -uv'
check_cmd='barium update -cv'

APP = 'barium_applet'
LOCALE_DIR = '/usr/share/locale'
locale.bindtextdomain(APP, LOCALE_DIR)
locale.textdomain(APP)

UI = '/usr/share/update_applet/update_applet.ui'
iconpath='/usr/share/update_applet'
ICON = os.path.join(iconpath, 'update_applet_ok.svg')
green = os.path.join(iconpath,  'update_applet_ok.svg')
yellow = os.path.join(iconpath, 'update_applet_warning.svg')
red = os.path.join(iconpath, 'update_applet_error.svg')

h = {}
h['ROOTFS'] = _('ROOTFS')
h['DISK_USAGE'] = _('DISK USAGE')
h['MEM'] = _('MEM')
h['LAST_SAVE'] = _('LAST SAVE')
h['HOME_DIR_SIZE'] = _('HOME DIR SIZE')
h['HOME_MODULE_SIZE'] = _('HOME MODULE SIZE')
h['SAVE_TIMEOUT'] = _('SAVE TIMEOUT')
used_ = _('used')
available_ = _('available')
min_ = _('min')
Mb = _('Mb')
disabled_ = _('disabled')
maxlen = len(max( h.values() , key=len)) + 2
for key, val in h.items():
  quant = (maxlen - len(val) + 2) // 5
  tabs = str('_' * quant).replace('_', '\t')
  h[key] = val + ': ' + tabs

#default values
config = {}
config['times'] = (1, 5, 10, 30, disabled_ ) # min
config['synctimeout'] = 30 # min (one of times)
config['upd_times'] = (1, 3, 5, 10, 30) # sec
config['timeout'] = 5 # sec (one of upd_times)

new_interval = False

def update():
    app.update_button.hide()
    app.pwf_button.hide()
    app.progress.set_fraction(0.0)
    app.progress.show()
    if not runit(app, 'timeout 60m ' + update_cmd, _('Update ') + sysinfo()['os_name']):
      echo(app, 'ERROR!!!', 'ERROR: ' + update_cmd)
      app.update_button.show()
      notify_send(_('Update process - failed'), \
                  _('Please, try later'), 'error', None, None)
      start_pulse(app, False)
      return False
    echo(app, '', _('Complete'))
    start_pulse(app, False)
    app.progress.set_fraction(0)
    dialog(first=_('OS has been successfully updated'), second=_('Please reboot!'))
    clear()
    app.ind_app.set_status (appindicator.IndicatorStatus.ACTIVE)
    app.window.hide()

def get_updates():
    stoptimers()
    app.window.show_all()
    app.ind_app.set_status (appindicator.IndicatorStatus.PASSIVE)
    app.update_button.hide()
    app.pwf_button.hide()
    app.progress.set_fraction(0.0)
    app.progress.show()
    if not runit(app, 'timeout 5m ' + check_cmd, _('Check updates ') + sysinfo()['os_name']):
      echo(app, _('Updates not found!\nCheck cmdline: ')  + check_cmd , _('Updates not found!'))
      start_pulse(app, False)
      return False
    echo(app, '', _('Check complete'))
    start_pulse(app, False)
    app.progress.set_fraction(0)
    app.update_button.show()
    app.pwf_button.show()


def clear():
    global INSTALL
    app.textbuffer.set_text('\n')
    app.statusbar.push(app.context_id, '')
    app.progress.set_pulse_step(0.2)
    app.text_window.hide()
    app.exit_button.set_label(_('Exit'))
    app.pwf_button.hide()
    app.update_button.show()
    start_pulse(app, False)
    app.log_chkbx.set_label(_('Show Log'))

    if SHOW_LOG:
        app.log_chkbx.set_active(True)
        app.text_window.show()
    else:
        app.log_chkbx.set_active(False)
        app.text_window.hide()

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

def stoptimers():
    global timer_id
    if 'timer_id' in globals():
      GLib.Source.remove(timer_id)
    timer_id = False
    print('stop timers')


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

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

  def onSUBMIT(self, button):
      global INSTALL
      clear()
      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
      start_pulse(app, True)
      GLib.idle_add(update)

  def onEXIT(self, button):
      global timer_id
      app.ind_app.set_status (appindicator.IndicatorStatus.ACTIVE)
      app.window.hide()
      clear()

class IndHandler():
  def exit(*args):
    Gtk.main_quit()

  def check_updates(*args):
    get_updates()

  def luks(*args):
    subprocess.call( [ 'pkexec', 'barium', 'luks-manager'] )

  def tobackup(*args):
    script = '''#!/bin/bash
  DISTRDEV=$(grep -m1 " %s/layer-base/0 " /proc/self/mountinfo | awk '{ print $10 }')
  DISTRMPOINT=$(findmnt -lnf $DISTRDEV -o TARGET)
  DISTRPATH=$DISTRMPOINT$(grep -m1 " %s/layer-base/0 " /proc/self/mountinfo | awk '{ print $4 }')
  [ "${DISTRPATH##*.}" != bak ] && exit 1
  mount -o remount,rw $DISTRMPOINT
  mv ${DISTRPATH/.bak} ${DISTRPATH/.bak}.upd
  mv ${DISTRPATH} ${DISTRPATH/.bak} || exit 2
  exit 0
  ''' % ( initvars['SYSMNT'], initvars['SYSMNT'])
    with open('/tmp/switch_to_backup.sh', 'w') as f:
      print( script, file=f )
    os.chmod('/tmp/switch_to_backup.sh', 0o755)
    ret = subprocess.call( [ 'pkexec', '/tmp/switch_to_backup.sh' ] )
    app.menu_tobackup.destroy()
    if ret == 0:
      notify_send(_('The system has been successfully swithsed to backup state'), _('Please reboot'), 'ok', None, None)
      return True
    elif ret == 1:
      notify_send(_('Error:'), _('Backup not found'), 'error', None, None)
    elif ret == 2:
      notify_send(_('Error:'), _('Can not switch to backup'), 'error', None, None)
    else:
      notify_send(_('Error:'), _('Unknown'), 'error', None, None)
    return False

  def toggle_saves(*args):
    if os.path.exists('/run/initramfs/shutdown'):
      ret = subprocess.call( [ 'pkexec', 'mv', '/run/initramfs/shutdown' , '/run/initramfs/shutdown.bak' ])
      if not ret:
        app.menu_toggle_saves.set_label(_('Enable saves'))
    else:
      ret = subprocess.call( [ 'pkexec', 'mv', '/run/initramfs/shutdown.bak' , '/run/initramfs/shutdown' ])
      if not ret:
        app.menu_toggle_saves.set_label(_('Disable saves'))

  def rmbak(w, data):
    subprocess.call( [ 'pkexec', 'rm', '-f', initvars['SYSMNT'] + '/layer-base/1/saves/*.xzm.bak' ])

  def synchome(*args):
    ret = progress_run( lambda x=['pkexec', 'synchome']: subprocess.call(x),
                        _('sunchome'), _('saving /home directory') )
    if ret:
      notify_send(_('Error:'), _('Command synchome exited with error: ') + str(ret), 'error', None, None)
    with open( '/tmp/synchome', 'w' ) as f:
      f.write(str(int(time.time())))

#генератор функций для каждого значение в кортеже times
fn_save = {}
def f1(time_):
  global config
  if type(time_) == int:
    app.handler.synchome()
  if type(config['synctimeout']) == int:
    app.submenu_save_item[config['synctimeout']].set_label( str(config['synctimeout']) + ' ' + _('min') )
  else:
    app.submenu_save_item[config['synctimeout']].set_label( str(config['synctimeout']) )
  config['synctimeout'] = time_
  pickle_write()
for i in config['times']:
  fn_save[i] = lambda x, y, i=i: f1(i)

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

def fn_getvalues(ind_app):
  global new_interval
  available = {}
  used = {}
  for name, path in ([ 'rootfs',  '/' ], [ 'disk', initvars['SYSMNT'] + '/layer-base/1' ]):
    with subprocess.Popen([ 'df', path, '-m', '/'], stdout=subprocess.PIPE) as fs:
      lines = fs.stdout.readlines()
      valueline = lines[1].decode('utf-8').split()
      available[name] = valueline[3]
      used[name] = valueline[4][0:-1]
  with subprocess.Popen(['free', '-m'], stdout=subprocess.PIPE) as free:
    lines = free.stdout.readlines()
    valueline = lines[1].decode('utf-8').split()
    available['free'] = valueline[6]
    used['free'] = float('{:.2f}'.format(int(valueline[2]) * 100 / int(valueline[1])))
  if os.path.exists('/tmp/synchome'):
    with open( '/tmp/synchome', 'r' ) as f:
      lastsave = str(f.readlines()[0])
      timestamp = int(time.time())
      delta = int((timestamp - int(lastsave)) / 60)
      if type(config['synctimeout']) == int  and delta >= config['synctimeout']:
        app.handler.synchome()
  else:
      delta = _('never')
  if ishomexzm:
    homesize = float('{:.2f}'.format(os.stat(initvars['SYSMNT'] + '/layer-base/1/saves/homes.xzm').st_size / 1024 / 1024))
  else:
    homesize = 0
  with subprocess.Popen([ 'du', '/home', '-sm'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) as home:
    homedirsize = str(home.stdout.readline().decode('utf-8').split()[0])
  if type(config['synctimeout']) == int:
    timeoutstr = str(config['synctimeout']) + ' ' + min_
  else:
    timeoutstr = config['synctimeout']
  title = f'''
{h['ROOTFS']}{available_}: {available['rootfs']}{Mb}, {used_}: {used['rootfs']}%
{h['DISK_USAGE']}{available_}: {available['disk']}{Mb}, {used_}: {used['disk']}%
{h['MEM']}{available_}: {available['free']}{Mb}, {used_}: {used['free']}%
{h['HOME_DIR_SIZE']}{homedirsize}{Mb}
'''
  if ishomexzm:
    title += f'''
{h['HOME_MODULE_SIZE']}{homesize}{Mb}'
{h['LAST_SAVE']}{delta} {min_}
{h['SAVE_TIMEOUT']}{timeoutstr}
'''
  ind_app.set_title(title)

  color = 'green'
  if ishomexzm:
    if int(homedirsize) / 1.5 >= int(available['disk']) or \
    int(homesize) >= 500:
      color = 'yellow'

  if not ischanges and int(used['rootfs']) >= 80:
    color = 'yellow'

  if int(used['free']) >= 80:
    color = 'yellow'

  if ishomexzm:
    if int(homedirsize) / 2 >= int(available['disk']) or \
    int(homesize) >= 1000:
      color = 'red'

  if istoxzm and not os.path.exists('/run/initramfs/shutdown'):
    color = 'red'

  if not ischanges and int(used['rootfs']) >= 95:
    color = 'red'

  if int(used['free']) >= 95:
    color = 'red'
  if color == 'red':
    if app.ind_app.get_icon != red:
      app.ind_app.set_icon_full(red, "red")
      app.menu_rmback.show()
  elif color == 'yellow':
    if app.ind_app.get_icon != yellow:
      app.ind_app.set_icon_full(yellow, "yellow")
      app.menu_rmback.show()
  elif color == 'green':
    if app.ind_app.get_icon != green:
      app.ind_app.set_icon_full(green, "green")
      app.menu_rmback.hide()
  if ishomexzm:
    if type(config['synctimeout']) == int:
      app.submenu_save_item[config['synctimeout']].set_label( str(config["synctimeout"]) + ' ' + _('min') + ' *' )
    else:
      app.submenu_save_item[config['synctimeout']].set_label(disabled_ + ' *')

  if new_interval:
    new_interval = False
    GLib.timeout_add(config['timeout'] * 1000, fn_getvalues, ind_app)
    print('set timeout:',  config['timeout'])
    return False
  else:
    return True

def show_saves(app, times):
  for itime  in times:
    if type(itime) == int:
      app.submenu_save_item[itime] = Gtk.MenuItem( label=str(itime) + ' ' + _('min') )
    else:
      app.submenu_save_item[itime] = Gtk.MenuItem( label=str(itime))
    app.submenu_save_item[itime].connect("activate", fn_save[itime], '')
    app.submenu_save_item[itime].show()
    app.submenu_save.append(app.submenu_save_item[itime])

def show_icon_update(app, times):
  for itime  in times:
    app.submenu_icon_item[itime] = Gtk.MenuItem( label=str(itime) + ' ' + _('sec') )
    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])

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

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

    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.update_button = builder.get_object('SUBMIT')
    self.exit_button = builder.get_object('EXIT')
    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)

    # indicator
    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(_("Barium applet") + ' ' + get_os_name())
    self.ind_app.set_status (appindicator.IndicatorStatus.ACTIVE)

    # create a menu
    menu = Gtk.Menu()

    if ishomexzm:
      menu_save_now = Gtk.MenuItem(label=_('Save home now'))
      menu_save_now.connect("activate", self.handler.synchome, '')
      menu_save_now.show()
      menu.append(menu_save_now)

      ######## autosave
      submenuItem = Gtk.MenuItem(label=_('Auto saving of /home'))
      self.submenu_save = Gtk.Menu()
      submenuItem.set_submenu(self.submenu_save)
      menu.append(submenuItem)
      submenuItem.show()
      show_saves(self, config['times'])

    ######## update applet
    submenuItem2 = Gtk.MenuItem(label=_('Applet update interval'))
    self.submenu_icon_update = Gtk.Menu()
    submenuItem2.set_submenu(self.submenu_icon_update)
    menu.append(submenuItem2)
    submenuItem2.show()
    show_icon_update(self, config['upd_times'])
    self.submenu_icon_item[config['timeout']].set_label(str(config['timeout']) + ' ' + _('sec') + ' *' )

    ######## updates
    menu_updates = Gtk.MenuItem(label=_('Check updates'))
    menu_updates.connect("activate", self.handler.check_updates, '')
    menu_updates.show()
    menu.append(menu_updates)

    if isluks:
      ######## luks-manager menu
      submenuItem3 = Gtk.MenuItem(label=_('Luks manager'))
      submenuItem3.connect("activate", self.handler.luks, '')
      submenuItem3.show()
      menu.append(submenuItem3)

    if istoxzm:
      ######## saves toggle
      if os.path.exists('/run/initramfs/shutdown'):
        toggle_label = _('Disable saves')
      elif os.path.exists('/run/initramfs/shutdown.bak'):
        toggle_label = _('Enable saves')
      else:
        toggle_label = None

      if toggle_label:
        menu_toggle_saves = Gtk.MenuItem(label=toggle_label)
        menu_toggle_saves.connect("activate", self.handler.toggle_saves, '')
        menu_toggle_saves.show()
        menu.append(menu_toggle_saves)
        self.menu_toggle_saves = menu_toggle_saves

    ######### delete backup mods
    menu_rmback = Gtk.MenuItem(label=_('Delete backup modules'))
    menu_rmback.connect("activate", self.handler.rmbak, '')
    menu.append(menu_rmback)
    self.menu_rmback = menu_rmback

    if isbackup:
      ######### switch to backup
      menu_tobackup = Gtk.MenuItem(label=_('Switch to backup'))
      menu_tobackup.connect("activate", self.handler.tobackup, '')
      menu_tobackup.show()
      menu.append(menu_tobackup)
      self.menu_tobackup = menu_tobackup

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

    ######### 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 len(sys.argv) > 1:
    print( sys.argv[0], '- tray applet to monitoring base parameters of Barium OS\n'
'and managing saves, please start it with no args, or by desktop file')
    quit()
  cfgdir = '/home/' + os.environ['USER'] + '/.config/barium_applet'
  if os.path.exists(cfgdir + '/dump'):
    with open(cfgdir + '/dump', 'rb') as f:
      config = pickle.load(f)
  else:
    os.makedirs(cfgdir, exist_ok=True)
    pickle_write()

  with open('/proc/cmdline', 'r') as f:
    cmdline = f.readline()

  with open('/proc/mounts', 'r') as f:
    mounts = f.readlines()

  with open('/etc/initvars', 'r') as f:
    initvars = {}
    for line in f.read().splitlines():
      initvars[line.split('=')[0]] = line.split('=', 1 )[1]

  isbackup = False
  if 'uird.from=/ROSA-SYSTEM.bak' in cmdline:
    isbackup = True

  ischanges = False
  if initvars['uird_mode'] == 'changes' in cmdline:
    ischanges = True

  isluks = False
  for f in os.listdir('/dev/mapper'):
    for line in mounts:
      if '/dev/mapper/' + f  in line:
        isluks = True
        break
    if isluks:
      break

  istoxzm = False
  if initvars['uird_mode'] == 'toxzm' and os.path.exists('/run/initramfs/remount'):
    istoxzm = True

  ishomexzm = False
  if os.path.exists(initvars['SYSMNT'] + '/layer-base/1/saves/homes.xzm') and istoxzm:
    ishomexzm = True

  if detect_DE == 'plasma':
    time.sleep(5)

  # run app
  app = Applet()
  clear()
  timer_id = GLib.timeout_add(config['timeout'] * 1000, fn_getvalues, app.ind_app)
  notify_init(_('Barium applet'))
  app.run()

