#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (c) 2014 Virtual Cable S.L. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of Virtual Cable S.L. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' from __future__ import unicode_literals import sys import time import signal import json import six import atexit from PyQt4 import QtGui from PyQt4 import QtCore from opengnsys import ipc from opengnsys import utils from opengnsys.log import logger from opengnsys.service import IPC_PORT from opengnsys import operations from about_dialog_ui import Ui_OGAAboutDialog from message_dialog_ui import Ui_OGAMessageDialog from opengnsys.scriptThread import ScriptExecutorThread from opengnsys import VERSION from opengnsys.config import readConfig from opengnsys.loader import loadModules # Set default characters encoding to UTF-8 reload(sys) if hasattr(sys, 'setdefaultencoding'): sys.setdefaultencoding('utf-8') trayIcon = None def sigAtExit(): if trayIcon: trayIcon.quit() #def sigTerm(sigNo, stackFrame): # logger.debug("Exec sigTerm") # if trayIcon: # trayIcon.quit() # About dialog class OGAAboutDialog(QtGui.QDialog): def __init__(self, parent=None): QtGui.QDialog.__init__(self, parent) self.ui = Ui_OGAAboutDialog() self.ui.setupUi(self) self.ui.VersionLabel.setText("Version " + VERSION) def closeDialog(self): self.hide() class OGAMessageDialog(QtGui.QDialog): def __init__(self, parent=None): QtGui.QDialog.__init__(self, parent) self.ui = Ui_OGAMessageDialog() self.ui.setupUi(self) def message(self, message): self.ui.message.setText(message) self.show() def closeDialog(self): self.hide() class MessagesProcessor(QtCore.QThread): logoff = QtCore.pyqtSignal(name='logoff') message = QtCore.pyqtSignal(tuple, name='message') script = QtCore.pyqtSignal(QtCore.QString, name='script') exit = QtCore.pyqtSignal(name='exit') def __init__(self, port): super(self.__class__, self).__init__() # Retries connection for a while for _ in range(10): try: self.ipc = ipc.ClientIPC(port) self.ipc.start() break except Exception: logger.debug('IPC Server is not reachable') self.ipc = None time.sleep(2) self.running = False def stop(self): self.running = False if self.ipc: self.ipc.stop() def isAlive(self): return self.ipc is not None def sendLogin(self, userName, language): if self.ipc: self.ipc.sendLogin(userName, language) def sendLogout(self, userName): if self.ipc: self.ipc.sendLogout(userName) def run(self): if self.ipc is None: return self.running = True # Wait a bit so we ensure IPC thread is running... time.sleep(2) while self.running and self.ipc.running: try: msg = self.ipc.getMessage() if msg is None: break msgId, data = msg logger.debug('Got Message on User Space: {}:{}'.format(msgId, data)) if msgId == ipc.MSG_MESSAGE: module, message, data = data.split('\0') self.message.emit((module, message, data)) elif msgId == ipc.MSG_LOGOFF: self.logoff.emit() elif msgId == ipc.MSG_SCRIPT: self.script.emit(QtCore.QString.fromUtf8(data)) except Exception as e: try: logger.error('Got error on IPC thread {}'.format(utils.exceptionToMessage(e))) except: logger.error('Got error on IPC thread (an unicode error??)') if self.ipc.running is False and self.running is True: logger.warn('Lost connection with Service, closing program') self.exit.emit() class OGASystemTray(QtGui.QSystemTrayIcon): def __init__(self, app_, parent=None): self.app = app_ self.config = readConfig(client=True) # Get opengnsys section as dict cfg = dict(self.config.items('opengnsys')) # Set up log level logger.setLevel(cfg.get('log', 'INFO')) self.ipcport = int(cfg.get('ipc_port', IPC_PORT)) # style = app.style() # icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_ComputerIcon)) icon = QtGui.QIcon(':/images/img/oga.png') QtGui.QSystemTrayIcon.__init__(self, icon, parent) self.menu = QtGui.QMenu(parent) exitAction = self.menu.addAction("About") exitAction.triggered.connect(self.about) self.setContextMenu(self.menu) self.ipc = MessagesProcessor(self.ipcport) if self.ipc.isAlive() is False: raise Exception('No connection to service, exiting.') self.timer = QtCore.QTimer() self.timer.timeout.connect(self.timerFnc) self.stopped = False self.ipc.message.connect(self.message) self.ipc.exit.connect(self.quit) self.ipc.script.connect(self.executeScript) self.ipc.logoff.connect(self.logoff) self.aboutDlg = OGAAboutDialog() self.msgDlg = OGAMessageDialog() self.timer.start(1000) # Launch idle checking every 1 seconds self.ipc.start() def initialize(self): # Load modules and activate them # Also, sends "login" event to service self.modules = loadModules(self, client=True) logger.debug('Modules: {}'.format(list(v.name for v in self.modules))) # Send init to all modules validMods = [] for mod in self.modules: try: logger.debug('Activating module {}'.format(mod.name)) mod.activate() validMods.append(mod) except Exception as e: logger.exception() logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e))) self.modules[:] = validMods # copy instead of assignment # If this is running, it's because he have logged in, inform service of this fact self.ipc.sendLogin(operations.getCurrentUser(), operations.getSessionLanguage()) def deinitialize(self): for mod in reversed(self.modules): # Deinitialize reversed of initialization try: logger.debug('Deactivating module {}'.format(mod.name)) mod.deactivate() except Exception as e: logger.exception() logger.error("Deactivation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e))) def timerFnc(self): pass def message(self, msg): ''' Processes the message sent asynchronously, msg is an QString ''' try: logger.debug('msg: {}, {}'.format(type(msg), msg)) module, message, data = msg except Exception as e: logger.error('Got exception {} processing message {}'.format(e, msg)) return for v in self.modules: if v.name == module: # Case Sensitive!!!! try: logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data)) v.processMessage(message, json.loads(data)) return except Exception as e: logger.error('Got exception {} processing generic message on {}'.format(e, v.name)) logger.error('Module {} not found, messsage {} not sent'.format(module, message)) def executeScript(self, script): logger.debug('Executing script') script = six.text_type(script.toUtf8()).decode('base64') th = ScriptExecutorThread(script) th.start() def logoff(self): logger.debug('Logoff invoked') operations.logoff() # Invoke log off def about(self): self.aboutDlg.exec_() def cleanup(self): logger.debug('Quit invoked') if self.stopped is False: self.stopped = True try: self.deinitialize() except Exception: logger.exception() logger.error('Got exception deinitializing modules') try: # If we close Client, send Logoff to Broker self.ipc.sendLogout(operations.getCurrentUser()) time.sleep(1) self.timer.stop() self.ipc.stop() except Exception: # May we have lost connection with server, simply log and exit in that case logger.exception() logger.exception("Got an exception, processing quit") try: # operations.logoff() # Uncomment this after testing to logoff user pass except Exception: pass def quit(self): #logger.debug("Exec quit {}".format(self.stopped)) if self.stopped is False: self.cleanup() self.app.quit() def closeEvent(self, event): logger.debug("Exec closeEvent") event.accept() self.quit() if __name__ == '__main__': app = QtGui.QApplication(sys.argv) if not QtGui.QSystemTrayIcon.isSystemTrayAvailable(): # QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.") sys.exit(1) # This is important so our app won't close on messages windows (alerts, etc...) QtGui.QApplication.setQuitOnLastWindowClosed(False) try: trayIcon = OGASystemTray(app) except Exception as e: logger.exception() logger.error('OGA Service is not running, or it can\'t contact with OGA Server. User Tools stopped: {}'.format(utils.exceptionToMessage(e))) sys.exit(1) try: trayIcon.initialize() # Initialize modules, etc.. except Exception as e: logger.exception() logger.error('Exception initializing OpenGnsys User Agent {}'.format(utils.exceptionToMessage(e))) trayIcon.quit() sys.exit(1) app.aboutToQuit.connect(trayIcon.cleanup) trayIcon.show() # Catch kill and logout user :) #signal.signal(signal.SIGTERM, sigTerm) atexit.register(sigAtExit) res = app.exec_() logger.debug('Exiting') trayIcon.quit() sys.exit(res)