Files
YMS/yms/view/base.py
T

439 lines
18 KiB
Python

# SPDX-License-Identifier: WTFPL
"""
Spezial:
Notebook mit Vessel, User, Lists, Settings
"""
import os
import pyudev
from gi.repository import GLib, Gtk, Gdk, Rsvg
from datetime import datetime
import subprocess
import locale
from .view import View
from yms import gtkutils, combobox, treeview
from yms.lookup import Dropdown
from yms.model import user
class BaseView(View):
def __init__(self, context):
super().__init__(context, _("Base Data"), "base.ui")
self.context = context
self.user = user.User(context.db) # current user record
self.newuser = False
self.backupdevice = None # USB-Stick detected only on demand
# Settings
self.sys_currency = context.db.load_setting(4)[0] or '€'
self.entry['sys_currency'].set_text(self.sys_currency)
self.sys_max_width = context.db.load_setting(20)[1] or 1024
self.spin['sys_max_width'].set_value(self.sys_max_width)
self.sys_max_height = context.db.load_setting(21)[1] or 768
self.spin['sys_max_height'].set_value(self.sys_max_height)
# combos
roles = {'captain': 'Captain', 'helmsman': 'Helmsman', 'sailor': 'Sailor'}
combobox.load_from_dict(self.combo['user_role'], roles)
langs = {'de': 'Deutsch', 'en': 'English'}
combobox.load_from_dict(self.combo['user_lang'], langs)
today = datetime.now()
datedict = {
key: f"{prefix} - {today.strftime(fmt)}"
for key, (fmt, prefix) in context.datefmt_map.items()
}
self.datefmt = context.db.load_setting(2)[0] or 'Y-m-d'
combobox.load_from_dict(self.combo['datefmt'], datedict, self.datefmt)
# shipyard
self.db.cur.execute("SELECT compid, compname FROM company WHERE comptype=3 ORDER BY compname")
shipyards = dict(self.db.cur.fetchall())
combobox.load_from_dict(self.combo['shipyard'], {-1: _("─── none ───")} | shipyards)
# get current vessel data
self.db.cur.execute("SELECT valint FROM settings WHERE userid=0 AND sno=1")
row = self.db.cur.fetchone()
if not row:
self.context.vessel.vid = 0
self.context.vessel.vesselname = "Ghost"
self.db.cur.execute("INSERT INTO settings (userid, sno, valint) VALUES (0, 1, 0)")
self.db.conn.commit()
else:
# check if vessel from settings exist
self.db.cur.execute("SELECT count(*) FROM vessel WHERE vid=%s", (row[0],))
res = self.db.cur.fetchone()[0]
if res > 0:
self.context.vessel.vid = row[0]
else:
# TODO set vessel to first in list
self.context.vessel.vid = 0
self.context.vessel.vesselname = "Ghost"
# default storage for current vessel
sql = "SELECT sid, sname FROM storage WHERE stype=0 AND vid=%s ORDER BY sname"
self.db.cur.execute(sql, (self.context.vessel.vid,))
storages = dict(self.db.cur.fetchall())
combobox.load_from_dict(self.combo['storage'], {-1: _("─── none ───")} | storages)
# get list of vessels
vessellist = {}
self.db.dcur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid")
for row in self.db.dcur.fetchall():
vessellist[row['vid']] = (
row['vesselname'] if row['model'] is None
else row['vesselname'] + " - " + row['model']
)
combobox.load_from_dict(self.combo['vessel'], vessellist, self.context.vessel.vid)
# vessel load triggered from combobox init
# prepare lists
self.lst_user = Gtk.ListStore(int, str)
self.db.cur.execute("SELECT userid, login FROM user WHERE userid>0 ORDER BY login")
for row in self.db.cur.fetchall():
self.lst_user.append(row)
treeview.add_column(self.tree['user'], 'ID', 0)
treeview.add_column(self.tree['user'], 'Login', 1)
self.tree['user'].set_model(self.lst_user)
# list of dropdown lists
self.lst_lists = Gtk.ListStore(int, str, int)
# self.db.cur.execute("SELECT ddval, ddtext FROM dropdown WHERE ddid=0 AND ddval>0 ORDER BY ddval")
sql = (
"SELECT dd.ddval, dd.ddtext, COUNT(n.ddval) AS records "
"FROM dropdown AS dd LEFT JOIN dropdown AS n ON dd.ddval=n.ddid "
"WHERE dd.ddid=0 "
"GROUP BY dd.ddval, dd.ddtext "
"ORDER BY dd.ddval"
)
self.db.cur.execute(sql)
for row in self.db.cur.fetchall():
self.lst_lists.append(row)
treeview.add_column(self.tree['ddlists'], _('ID'), 0)
treeview.add_column(self.tree['ddlists'], _('List'), 1)
treeview.add_column(self.tree['ddlists'], _('Records'), 2)
self.tree['ddlists'].set_model(self.lst_lists)
# Settings date format selection list
# self.context.datefmt_map
# load shape of vessel if available
if self.context.vessel.vid > 0:
self.db.cur.execute("SELECT shapefile FROM vessel WHERE vid=%s", (self.context.vessel.vid, ))
filename = self.db.cur.fetchone()[0] or ''
shapefilepath = os.path.join(context.cfg['shapedir'], filename)
if os.path.isfile(shapefilepath):
handle = Rsvg.Handle()
self._svg = handle.new_from_file(shapefilepath)
#self._svg = handle.new_from_file(os.path.join(sys.path[0], cfg['shapedir'], "elizabethan29.svg"))
#viewport = Rsvg.Rectangle()
#viewport.x = 0
#viewport.y = 0
#viewport.width = 320
#viewport.height = 240
#self._svg.render_document(ctx, viewport)
# WIP generic remarks handling
self.text['vessel_remarks'].set_size_request(200, 70)
def load_dropdowns(self):
# non static dropdown (re-)load feature
# default storage for current vessel
sql = "SELECT sid, sname FROM storage WHERE stype=0 AND vid=%s ORDER BY sname"
self.db.cur.execute(sql, (self.context.vessel.vid,))
storages = dict(self.db.cur.fetchall())
combobox.refresh_dict(self.combo['storage'], {-1: _("─── none ───")} | storages)
def find_usb_sticks(self):
result = []
for name in os.listdir("/sys/block"):
device = "/sys/block/" + name
removable = device + "/removable"
if not os.path.exists(removable):
continue
with open(removable) as f:
if f.read().strip() != "1":
continue
device_path = os.path.realpath(device + "/device")
if "/usb" in device_path.lower():
result.append(name)
return result
def is_mounted(self, device):
with open("/proc/mounts") as f:
for line in f:
mounted_device = line.split()[0]
if mounted_device == device:
return True
return False
def on_but_usbdevice_clicked(self, widget):
self.backupdevice = None
devlist = self.find_usb_sticks()
if len(devlist) == 0:
self.label['usb_device'].set_text('n/a')
return
if len(devlist) != 1:
print(devlist)
print("Multiple devices not supported yet")
self.label['usb_device'].set_text('multiple')
return
usbdev = devlist[0]
self.label['usb_device'].set_text(f"/dev/{usbdev}")
"""
serial number via python3-pyudev:
import pyudev
Settings
7 - usb backup device serial
8 - usb backup partition uuid
9 - last backup timestamp
"""
# first get stick hardware data
udevctx = pyudev.Context()
device = pyudev.Devices.from_sys_path(udevctx, os.path.join("/sys/class/block", usbdev))
serial = device.get("ID_SERIAL_SHORT")
vendor = device.get("ID_VENDOR")
model = device.get("ID_MODEL")
self.label['usb_serial'].set_text(serial or 'n/a')
self.label['usb_vendor'].set_text(vendor or 'n/a')
self.label['usb_model'].set_text(model or 'n/a')
# TODO second get partition, filesystem type and label
# there must be only one ext4 partition on disk
partitions = [
child for child in device.children
if child.device_type == "partition"
]
partition = partitions[0]
print(f"Partition={partition}")
if len(partitions) != 1:
print("error: more than one partition!")
return
partuuid = partition.properties.get("ID_PART_ENTRY_UUID")
fstype = partition.get("ID_FS_TYPE")
plabel = partition.get("ID_FS_LABEL")
self.label['usb_partition'].set_text(plabel)
self.label['usb_uuid'].set_text(partuuid)
self.label['usb_fstype'].set_text(fstype)
if fstype != "ext4":
print(f"error: invalid fileystem type {fstype}, ext4 expected")
return
self.backupdevice = f"/dev/{usbdev}1" # partition!
# check if device is mounted
if not self.is_mounted(self.backupdevice):
print("device is not mounted. trying to mount now")
pass
# For backup there must be an directory names ymsbackup which contains
# backup description and backup sets
# Desription and identification file backup.json?
# if plabel != 'BACKUP' or not os.path.isdir('ymsbackup')
mountpoint = os.path.join("/media", os.getlogin(), "BACKUP")
if not os.path.isdir(mountpoint):
print(f"Device is not mounted at {mountpoint}")
def on_but_usbinit_clicked(self, widget):
if self.backupdevice is None:
return
# UUID of devive -> settings
# check if backups already exist
answer = gtkutils.questionbox(
_("Initialize USB-Stick\n"
"All data on that device will be lost!\n"
"Are you really sure?"
), title="YMS")
if answer == False:
return
if self.label['usb_fstype'].get_text() != 'ext4':
print("formatting {self.backupdevice} with ext4")
#try:
# subprocess.run(["sudo", "mkfs.ext4", "-L", "BACKUP", self.backupdevice], check=True)
#except subprocess.CalledProcessError as e:
# print("formatting failed: ", e)
myself = os.getlogin()
mountpoint = os.path.join("/media", myself, "backup")
basedir = os.path.join(mountpoint, 'ymsbackup')
print(basedir)
# mkdir basedir
# chown (os.getlogin(), 'yms')
# chmod 770
else:
#
pass
def on_cmb_vessel_changed(self, widget):
self.context.vessel.load(combobox.get_list_value(widget))
sql = ("UPDATE settings SET valint=%s WHERE userid=0 AND sno=1")
self.db.cur.execute(sql, (self.context.vessel.vid,))
self.db.conn.commit()
# Refresh dependent data
self.load_dropdowns()
# Show data
self.display_vessel(self.context.vessel)
def on_but_vessel_add_clicked(self, widget):
answer = gtkutils.questionbox(
_("Add new vessel.\n"
"Are you sure?"
), title="YMS")
if answer == False:
return
self.context.vessel.create()
model = self.combo['vessel'].get_model()
model.append((self.context.vessel.vid, self.context.vessel.vesselname))
combobox.activate_row(self.combo['vessel'], self.context.vessel.vid)
self.edit_vessel(self.context.vessel)
self.stack['vessel'].set_visible_child(self.box['vessel_edit'])
def on_but_vessel_edit_clicked(self, widget):
self.edit_vessel(self.context.vessel)
self.stack['vessel'].set_visible_child(self.box['vessel_edit'])
def on_but_vessel_save_clicked(self, widget):
print("Save vessel")
vessel = self.context.vessel
vessel.vesselname = self.entry['vesselname'].get_text()
vessel.model = self.entry['model'].get_text()
vessel.year_built = self.spin['year_built'].get_value_as_int()
vessel.loa = self.spin['loa'].get_value()
vessel.lwl = self.spin['lwl'].get_value()
vessel.beam = self.spin['beam'].get_value()
vessel.draught = self.spin['draught'].get_value()
vessel.draught_min = self.spin['draught_min'].get_value()
vessel.displacement = self.spin['displacement'].get_value_as_int()
vessel.ballast = self.spin['ballast'].get_value_as_int()
vessel.remarks = gtkutils.get_text_contents(self.text['vessel_remarks'])
vessel.save()
self.stack['vessel'].set_visible_child(self.box['vessel'])
def on_but_vessel_cancel_clicked(self, widget):
self.stack['vessel'].set_visible_child(self.box['vessel'])
def on_but_vessel_delete_clicked(self, widget):
pass
def on_but_settings_save_clicked(self, widget):
print("Save settings")
#self.entry['sys_currency']
#self.spin['sys_max_width']
#self.spin['sys_max_height']
def display_vessel(self, vessel):
self.label['vesselname'].set_text(vessel.vesselname)
self.label['model'].set_text(vessel.model or '')
self.label['shipyard'].set_text(str(vessel.shipyard))
self.label['year_built'].set_text(self.format_int(vessel.year_built, False, default='n/a'))
self.label['loa'].set_text(self.format_float(vessel.loa, 2, suffix="m"))
self.label['lwl'].set_text(self.format_float(vessel.lwl, 2, suffix="m"))
self.label['beam'].set_text(self.format_float(vessel.beam, 2, suffix="m"))
self.label['draught'].set_text(self.format_float(vessel.draught, 2, suffix="m"))
self.label['draught_min'].set_text(self.format_float(vessel.draught_min, 2, suffix="m"))
self.label['displacement'].set_text(self.format_int(vessel.displacement, 2, suffix="kg"))
self.label['ballast'].set_text(self.format_int(vessel.ballast, 2, suffix="kg"))
self.label['belt_bow'].set_text(self.format_float(vessel.beltpos_bow, 2, suffix="m"))
self.label['belt_aft'].set_text(self.format_float(vessel.beltpos_aft, 2, suffix="m"))
self.label['hull_shape'].set_text(vessel.shapefile or '')
self.label['storage_def'].set_text(str(vessel.sid_default))
self.label['remarks'].set_text(vessel.remarks or '')
def edit_vessel(self, vessel):
self.entry['vesselname'].set_text(vessel.vesselname)
self.entry['model'].set_text(vessel.model or '')
self.spin['year_built'].set_value(vessel.year_built or 0)
self.spin['loa'].set_value(vessel.loa or 0)
self.spin['lwl'].set_value(vessel.lwl or 0)
self.spin['beam'].set_value(vessel.beam or 0)
self.spin['draught'].set_value(vessel.draught or 0)
self.spin['draught_min'].set_value(vessel.draught_min or 0)
self.spin['displacement'].set_value(vessel.displacement or 0)
self.spin['ballast'].set_value(vessel.ballast or 0)
self.text['vessel_remarks'].get_buffer().set_text(vessel.remarks or '')
combobox.activate_row(self.combo['shipyard'], vessel.shipyard or -1)
combobox.activate_row(self.combo['storage'], vessel.sid_default or -1)
def display_user(self, user):
self.entry['user_login'].set_text(user.login)
self.entry['user_displayname'].set_text(user.displayname)
combobox.activate_row(self.combo['user_role'], user.role)
combobox.activate_row(self.combo['user_lang'], user.language)
self.check['user_locked'].set_active('locked' in user.flags)
self.check['user_deleted'].set_active('deleted' in user.flags)
self.entry['user_pass'].set_text('')
def on_tvw_user_row_activated(self, widget, path, column):
# load selected user
self.context.user.user_id = treeview.get_value(widget, 0)
self.context.user.load()
self.display_user(self.context.user)
def on_but_user_new_clicked(self, widget):
# get first free user id and create a new record with name "user<nnn>"
self.context.user.create()
self.lst_user.append((self.context.user.user_id, self.context.user.login))
def on_but_user_del_clicked(self, widget):
print("Delete user")
answer = gtkutils.questionbox(
_("Delete user!\n"
"Are you sure?"
), title="YMS")
if answer == False:
return
self.context.user.delete()
selection = self.tree['user'].get_selection()
model, tree_iter = selection.get_selected()
model.remove(tree_iter)
# TODO Select some other user from new list selection
self.context.user.user_id = treeview.get_value(self.tree['user'])
self.context.user.load()
self.display_user(self.context.user)
def on_but_user_newpass_clicked(self, widget):
self.entry['user_pass'].set_text(self.context.user.genpass())
def on_but_user_save_clicked(self, widget):
newlogin = self.entry['user_login'].get_text()
# check if changed login exists and throw error in that case
if newlogin != self.context.user.login:
sql = "SELECT COUNT(login) FROM user WHERE login=%s"
self.db.cur.execute(sql, (newlogin, ))
if self.db.cur.fetchone()[0] > 0:
# TODO set error indicator
print("Error: Login already exists!")
return
sql = "UPDATE user SET login=%s WHERE userid=%s"
self.db.cur.execute(sql, (newlogin, self.context.user.user_id))
treeview.set_value(self.tree['user'], 1, newlogin)
self.context.user.displayname = self.entry['user_displayname'].get_text()
self.context.user.role = combobox.get_list_value(self.combo['user_role'])
self.context.user.language = combobox.get_list_value(self.combo['user_lang'])
self.context.user.flags = []
if self.check['user_locked'].get_active(): self.context.user.flags.append('locked')
if self.check['user_deleted'].get_active(): self.context.user.flags.append('deleted')
self.context.user.save()
# password field is different. if filled set new password
newpass = self.entry['user_pass'].get_text()
if len(newpass) >= 8:
try:
self.context.user.setpass(newpass)
finally:
newpass = None
self.entry['user_pass'].set_text('')
else:
# Password to short
pass