231 lines
7.5 KiB
Python
Executable File
231 lines
7.5 KiB
Python
Executable File
#!/usr/bin/python3
|
|
# SPDX-License-Identifier: WTFPL
|
|
|
|
"""
|
|
|
|
YMS native GUI.
|
|
Runs as user SYSTEM with id=0
|
|
|
|
Configuration file located at ~/.config/ymsgui.conf:
|
|
[DB]
|
|
host = localhost
|
|
db = yms
|
|
user = ymsgui
|
|
pass = topsecret
|
|
|
|
[Documents]
|
|
basepath = /var/local/lib/yms
|
|
|
|
[Data]
|
|
shapedir = /usr/local/share/yms/shapes
|
|
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import locale
|
|
import gettext
|
|
import configparser
|
|
|
|
os.environ["NO_AT_BRIDGE"] = "1" # fix dbus warnings
|
|
import gi
|
|
gi.require_version("Gtk", "3.0")
|
|
gi.require_version('Rsvg', '2.0')
|
|
from gi.repository import GLib, Gtk, Gdk, Rsvg
|
|
|
|
from yms import database
|
|
from yms import gtkutils, combobox, treeview
|
|
from yms.model import user
|
|
from yms.model import vessel
|
|
from yms.lookup import Lookup
|
|
|
|
from yms.view.base import BaseView
|
|
from yms.view.storage import StorageView
|
|
from yms.view.boxes import BoxesView
|
|
from yms.view.equipment import EquipmentView
|
|
from yms.view.inventory import InventoryView
|
|
from yms.view.provisions import ProvisionsView
|
|
from yms.view.maintenance import MaintenanceView
|
|
from yms.view.projects import ProjectsView
|
|
from yms.view.cables import CablesView
|
|
from yms.view.fuses import FusesView
|
|
from yms.view.documents import DocumentsView
|
|
from yms.view.status import StatusView
|
|
|
|
class AppContext:
|
|
|
|
cfg = {
|
|
'cfgfile': '~/.config/ymsgui.conf',
|
|
'host': 'localhost',
|
|
'db': 'yms',
|
|
'user': 'yms',
|
|
'docpath': '/var/local/lib/yms',
|
|
'shapedir': '/usr/local/share/yms/shapes'
|
|
}
|
|
|
|
# mapping between PHP and Python
|
|
datefmt_map = {
|
|
'Y-m-d': '%Y-%m-%d', # ISO / international / technical
|
|
'd.m.Y': '%d.%m.%Y', # Germany / Central & Northern Europe
|
|
'd/m/Y': '%d/%m/%Y', # UK / France / Spain / Italy / Australia / NZ
|
|
'd-m-Y': '%d-%m-%Y', # Netherlands / Denmark / Belgium
|
|
'd M Y': '%d %b %Y', # nautical documents / logbooks / unambiguous
|
|
'm.d.Y': '%m.%d.%Y', # USA
|
|
'm/d/Y': '%m/%d/%Y', # USA
|
|
'Y/m/d': '%Y/%m/%d' # Japan / China
|
|
}
|
|
|
|
def __init__(self):
|
|
self.db = None
|
|
self.user = None
|
|
self.vessel = None
|
|
self.ui_path = os.path.join(self.get_sharepath(), "yms", "ui")
|
|
self.textdomain = "ymsgui"
|
|
self.datefmt = '%d.%m.%Y'
|
|
|
|
def set_db(self, db):
|
|
self.db = db
|
|
self.lookup = Lookup(db)
|
|
|
|
def load(self):
|
|
self.user = user.User(db, 0)
|
|
self.vessel = vessel.Vessel(db)
|
|
# load date format from db settings
|
|
sql = "SELECT valstr FROM settings WHERE userid=0 AND sno=2"
|
|
self.db.cur.execute(sql)
|
|
row = self.db.cur.fetchone()
|
|
if row is not None:
|
|
try:
|
|
self.datefmt = self.datefmt_map[row[0]]
|
|
except KeyError:
|
|
# almost impossible case where db value is not in mapping
|
|
print("internal error: format mapping missinf")
|
|
else:
|
|
# perhaps create missing record here?
|
|
pass
|
|
self.lookup.load()
|
|
|
|
def get_sharepath(self):
|
|
path = os.path.realpath(__file__)
|
|
if path.startswith("/usr/local/"):
|
|
return "/usr/local/share"
|
|
if path.startswith("/usr/"):
|
|
return "/usr/share"
|
|
return ""
|
|
|
|
class Frontend:
|
|
|
|
def __init__(self, context):
|
|
self.db = context.db
|
|
self.modules = {
|
|
"base": BaseView(context),
|
|
"storage": StorageView(context),
|
|
"box": BoxesView(context),
|
|
"equipment": EquipmentView(context),
|
|
"inventory": InventoryView(context),
|
|
"provisions": ProvisionsView(context),
|
|
"cables": CablesView(context),
|
|
"fuses": FusesView(context),
|
|
"maintenance": MaintenanceView(context),
|
|
"projects": ProjectsView(context),
|
|
"documents": DocumentsView(context),
|
|
"status": StatusView(context)
|
|
}
|
|
builder = Gtk.Builder()
|
|
builder.set_translation_domain(context.textdomain)
|
|
ui_path = os.path.join(context.ui_path)
|
|
builder.add_from_file(os.path.join(ui_path, "main.ui"))
|
|
builder.connect_signals(self)
|
|
self.window = builder.get_object("wnd_main")
|
|
self.notebook = builder.get_object("ntb_modules")
|
|
for module in self.modules.values():
|
|
self.notebook.append_page(module.topwidget, Gtk.Label(label=module.title))
|
|
|
|
self.db_poll_ts = context.db.get_timestamp()
|
|
GLib.timeout_add_seconds(30, self.db_change_poller)
|
|
|
|
print("Frontend initialized.")
|
|
|
|
def run(self):
|
|
#self.label['vessel'].set_text(self._vessel.vesselname)
|
|
self.window.show_all()
|
|
Gtk.main()
|
|
|
|
def db_change_poller(self):
|
|
# get all list ids which changes since last query
|
|
print("db list change poller")
|
|
#sql = "SELECT revid FROM revision WHERE changed>%s"
|
|
self.db_poll_ts = self.db.get_timestamp()
|
|
return True
|
|
|
|
def on_ntb_modules_switch_page(self, notebook, page, page_num):
|
|
print("Neue Seite:", page_num)
|
|
# TODO here re-load lists
|
|
|
|
def on_destroy(self, widget, data=None):
|
|
Gtk.main_quit()
|
|
|
|
def create_config(cfgfile):
|
|
# create inital config file
|
|
with open(cfgfile, 'w') as fh:
|
|
fh.write('[DB]\n')
|
|
fh.write('host = {}\n'.format(cfg['host']))
|
|
fh.write('db = {}\n'.format(cfg['db']))
|
|
fh.write('user = {}\n'.format(cfg['user']))
|
|
fh.write('pass = changeme!\n\n')
|
|
fh.write('[Documents]\n')
|
|
fh.write('basepath = {}\n\n'.format(cfg['docpath']))
|
|
fh.write('[Data]\n')
|
|
fh.write('shapedir = {}\n'.format(cfg['shapedir']))
|
|
# secure configuration
|
|
os.chmod(cfgfile, 0o600)
|
|
|
|
if __name__ == "__main__":
|
|
|
|
context = AppContext()
|
|
|
|
locale.setlocale(locale.LC_ALL, '')
|
|
gettext.bindtextdomain(context.textdomain, 'locale')
|
|
gettext.textdomain(context.textdomain)
|
|
gettext.install(context.textdomain)
|
|
|
|
config = configparser.ConfigParser()
|
|
cfgfile = os.path.expanduser(context.cfg['cfgfile'])
|
|
if not config.read(cfgfile):
|
|
create_config(cfgfile)
|
|
gtkutils.messagebox(
|
|
_("Configuration file '{}' not found.\n"
|
|
"Default configuration has been created.\n"
|
|
"Please edit that file according to your needs.\n\n"
|
|
"Then restart this program."
|
|
).format(cfgfile), title="YMS")
|
|
else:
|
|
context.cfg['host'] = config.get('DB', 'host')
|
|
context.cfg['db'] = config.get('DB', 'db')
|
|
db = database.Database(context.cfg['host'], context.cfg['db'])
|
|
context.set_db(db)
|
|
connected = False
|
|
if context.cfg['host'] == 'socket':
|
|
connected = db.connect_local()
|
|
else:
|
|
context.cfg['user'] = config.get('DB', 'user')
|
|
context.cfg['pass'] = config.get('DB', 'pass')
|
|
connected = db.connect(context.cfg['user'], context.cfg['pass'])
|
|
if connected:
|
|
context.load()
|
|
# check if shapedir exists
|
|
context.cfg['shapedir'] = config.get('Data', 'shapedir')
|
|
# everything seems to be fine: continue to start frontend
|
|
app = Frontend(context)
|
|
app.run()
|
|
context.db.disconnect()
|
|
else:
|
|
errno, errtxt = context.db.last_error()
|
|
gtkutils.messagebox(
|
|
_("Database connection cannot be established.\n\n"
|
|
"Server: {}\nDatabase: {}\n"
|
|
"Error: {} ({})").format(context.cfg['host'], context.cfg['db'], errtxt, errno),
|
|
title="YMS")
|
|
|
|
# Another fine product of the sirius cybernetics corporation
|