#!/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.companies import CompaniesView 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.tasks import TasksView from yms.view.measurement import MeasurementView from yms.view.cables import CablesView from yms.view.fuses import FusesView from yms.view.documents import DocumentsView from yms.view.status import StatusView __author__ = "Thomas Hooge" __copyright__ = "Copyright 2024-2026" __version__ = "0.2.2" __email__ = "thomas@hoogi.de" __status__ = "Development" 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', 'YYYY-MM-DD'), # ISO / international / technical 'd.m.Y': ('%d.%m.%Y', 'DD.MM.YYYY'), # Germany / Central & Northern Europe 'd/m/Y': ('%d/%m/%Y', 'DD/MM/YYYY'), # UK / France / Spain / Italy / Australia / NZ 'd-m-Y': ('%d-%m-%Y', 'DD-MM-YYYY'), # Netherlands / Denmark / Belgium 'd M Y': ('%d %b %Y', 'DD Mon YYYY'), # nautical documents / logbooks / unambiguous 'm.d.Y': ('%m.%d.%Y', 'MM.DD.YYYY'), # USA 'm/d/Y': ('%m/%d/%Y', 'MM/DD/YYYY'), # USA 'Y/m/d': ('%Y/%m/%d', 'YYYY/MM/DD') # Japan / China } def __init__(self): self.__version__ = __version__ 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' # system settings stored in database self.curr_sym = '€' self.img_max_width = 1024 self.img_max_height = 768 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]] print(row[0]) except KeyError: # almost impossible case where db value is not in mapping print("internal error: date format mapping missing") 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), "companies": CompaniesView(context), "equipment": EquipmentView(context), "inventory": InventoryView(context), "provisions": ProvisionsView(context), "measurement": MeasurementView(context), "cables": CablesView(context), "fuses": FusesView(context), "maintenance": MaintenanceView(context), "projects": ProjectsView(context), "tasks": TasksView(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) 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(cfg, 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, 'locale') config = configparser.ConfigParser() cfgfile = os.path.expanduser(context.cfg['cfgfile']) if not config.read(cfgfile): create_config(context.cfg, 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