source: ogAgent-Git/src/OGAgentUser.py @ 53f2a33

qndtest
Last change on this file since 53f2a33 was af35fd9, checked in by Ramón M. Gómez <ramongomez@…>, 5 years ago

#992: OGAgent sends the session type when user logs in.

  • Property mode set to 100644
File size: 11.6 KB
RevLine 
[11f7a07]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2014 Virtual Cable S.L.
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without modification,
8# are permitted provided that the following conditions are met:
9#
10#    * Redistributions of source code must retain the above copyright notice,
11#      this list of conditions and the following disclaimer.
12#    * Redistributions in binary form must reproduce the above copyright notice,
13#      this list of conditions and the following disclaimer in the documentation
14#      and/or other materials provided with the distribution.
15#    * Neither the name of Virtual Cable S.L. nor the names of its contributors
16#      may be used to endorse or promote products derived from this software
17#      without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[815ea50]29"""
[11f7a07]30@author: Adolfo Gómez, dkmaster at dkmon dot com
[815ea50]31"""
[11f7a07]32from __future__ import unicode_literals
33
34import sys
35import time
36import json
37import six
[98fc98d]38import atexit
[815ea50]39from PyQt4 import QtCore, QtGui  # @UnresolvedImport
[11f7a07]40
[815ea50]41from opengnsys import VERSION, ipc, operations, utils
[11f7a07]42from opengnsys.log import logger
43from opengnsys.service import IPC_PORT
44from about_dialog_ui import Ui_OGAAboutDialog
45from message_dialog_ui import Ui_OGAMessageDialog
46from opengnsys.scriptThread import ScriptExecutorThread
47from opengnsys.config import readConfig
48from opengnsys.loader import loadModules
49
[aac3fb9]50# Set default characters encoding to UTF-8
51reload(sys)
52if hasattr(sys, 'setdefaultencoding'):
53    sys.setdefaultencoding('utf-8')
54
[11f7a07]55trayIcon = None
56
[815ea50]57
[98fc98d]58def sigAtExit():
[11f7a07]59    if trayIcon:
60        trayIcon.quit()
61
62
63# About dialog
64class OGAAboutDialog(QtGui.QDialog):
65    def __init__(self, parent=None):
66        QtGui.QDialog.__init__(self, parent)
67        self.ui = Ui_OGAAboutDialog()
68        self.ui.setupUi(self)
69        self.ui.VersionLabel.setText("Version " + VERSION)
70
71    def closeDialog(self):
72        self.hide()
73
74
75class OGAMessageDialog(QtGui.QDialog):
76    def __init__(self, parent=None):
77        QtGui.QDialog.__init__(self, parent)
78        self.ui = Ui_OGAMessageDialog()
79        self.ui.setupUi(self)
80
81    def message(self, message):
82        self.ui.message.setText(message)
83        self.show()
84
85    def closeDialog(self):
86        self.hide()
87
88
89class MessagesProcessor(QtCore.QThread):
90    logoff = QtCore.pyqtSignal(name='logoff')
91    message = QtCore.pyqtSignal(tuple, name='message')
92    script = QtCore.pyqtSignal(QtCore.QString, name='script')
93    exit = QtCore.pyqtSignal(name='exit')
94
95    def __init__(self, port):
96        super(self.__class__, self).__init__()
97        # Retries connection for a while
98        for _ in range(10):
99            try:
100                self.ipc = ipc.ClientIPC(port)
101                self.ipc.start()
102                break
103            except Exception:
104                logger.debug('IPC Server is not reachable')
105                self.ipc = None
106                time.sleep(2)
107
108        self.running = False
109
110    def stop(self):
111        self.running = False
112        if self.ipc:
113            self.ipc.stop()
114
115    def isAlive(self):
116        return self.ipc is not None
117
[af35fd9]118    def sendLogin(self, user_data):
[11f7a07]119        if self.ipc:
[af35fd9]120            self.ipc.sendLogin(user_data)
[11f7a07]121
122    def sendLogout(self, userName):
123        if self.ipc:
124            self.ipc.sendLogout(userName)
125
126    def run(self):
127        if self.ipc is None:
128            return
129        self.running = True
130
131        # Wait a bit so we ensure IPC thread is running...
132        time.sleep(2)
133
134        while self.running and self.ipc.running:
135            try:
136                msg = self.ipc.getMessage()
137                if msg is None:
138                    break
139                msgId, data = msg
140                logger.debug('Got Message on User Space: {}:{}'.format(msgId, data))
141                if msgId == ipc.MSG_MESSAGE:
142                    module, message, data = data.split('\0')
143                    self.message.emit((module, message, data))
144                elif msgId == ipc.MSG_LOGOFF:
145                    self.logoff.emit()
146                elif msgId == ipc.MSG_SCRIPT:
147                    self.script.emit(QtCore.QString.fromUtf8(data))
148            except Exception as e:
149                try:
150                    logger.error('Got error on IPC thread {}'.format(utils.exceptionToMessage(e)))
151                except:
152                    logger.error('Got error on IPC thread (an unicode error??)')
153
154        if self.ipc.running is False and self.running is True:
155            logger.warn('Lost connection with Service, closing program')
156
157        self.exit.emit()
158
159
160class OGASystemTray(QtGui.QSystemTrayIcon):
161    def __init__(self, app_, parent=None):
162        self.app = app_
163        self.config = readConfig(client=True)
164
[98fc98d]165        # Get opengnsys section as dict
[11f7a07]166        cfg = dict(self.config.items('opengnsys'))
[98fc98d]167
[11f7a07]168        # Set up log level
169        logger.setLevel(cfg.get('log', 'INFO'))
[98fc98d]170
[11f7a07]171        self.ipcport = int(cfg.get('ipc_port', IPC_PORT))
[98fc98d]172
[11f7a07]173        # style = app.style()
174        # icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_ComputerIcon))
175        icon = QtGui.QIcon(':/images/img/oga.png')
176
177        QtGui.QSystemTrayIcon.__init__(self, icon, parent)
178        self.menu = QtGui.QMenu(parent)
179        exitAction = self.menu.addAction("About")
180        exitAction.triggered.connect(self.about)
181        self.setContextMenu(self.menu)
182        self.ipc = MessagesProcessor(self.ipcport)
[98fc98d]183
[11f7a07]184        if self.ipc.isAlive() is False:
185            raise Exception('No connection to service, exiting.')
[98fc98d]186
[11f7a07]187        self.timer = QtCore.QTimer()
188        self.timer.timeout.connect(self.timerFnc)
189
190        self.stopped = False
191
192        self.ipc.message.connect(self.message)
193        self.ipc.exit.connect(self.quit)
194        self.ipc.script.connect(self.executeScript)
195        self.ipc.logoff.connect(self.logoff)
196
197        self.aboutDlg = OGAAboutDialog()
198        self.msgDlg = OGAMessageDialog()
199
200        self.timer.start(1000)  # Launch idle checking every 1 seconds
201
202        self.ipc.start()
[98fc98d]203
[11f7a07]204    def initialize(self):
205        # Load modules and activate them
206        # Also, sends "login" event to service
207        self.modules = loadModules(self, client=True)
208        logger.debug('Modules: {}'.format(list(v.name for v in self.modules)))
[98fc98d]209
[11f7a07]210        # Send init to all modules
211        validMods = []
212        for mod in self.modules:
213            try:
214                logger.debug('Activating module {}'.format(mod.name))
215                mod.activate()
216                validMods.append(mod)
217            except Exception as e:
218                logger.exception()
219                logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
[98fc98d]220
[11f7a07]221        self.modules[:] = validMods  # copy instead of assignment
222
223        # If this is running, it's because he have logged in, inform service of this fact
[af35fd9]224        self.ipc.sendLogin((operations.getCurrentUser(), operations.getSessionLanguage(),
225                            operations.get_session_type()))
[11f7a07]226
227    def deinitialize(self):
228        for mod in reversed(self.modules):  # Deinitialize reversed of initialization
229            try:
230                logger.debug('Deactivating module {}'.format(mod.name))
231                mod.deactivate()
232            except Exception as e:
233                logger.exception()
234                logger.error("Deactivation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
235
236    def timerFnc(self):
237        pass
238
239    def message(self, msg):
[815ea50]240        """
[11f7a07]241        Processes the message sent asynchronously, msg is an QString
[815ea50]242        """
[11f7a07]243        try:
244            logger.debug('msg: {}, {}'.format(type(msg), msg))
245            module, message, data = msg
246        except Exception as e:
247            logger.error('Got exception {} processing message {}'.format(e, msg))
248            return
249
250        for v in self.modules:
251            if v.name == module:  # Case Sensitive!!!!
252                try:
253                    logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data))
254                    v.processMessage(message, json.loads(data))
255                    return
256                except Exception as e:
257                    logger.error('Got exception {} processing generic message on {}'.format(e, v.name))
[98fc98d]258
[11f7a07]259        logger.error('Module {} not found, messsage {} not sent'.format(module, message))
260
261    def executeScript(self, script):
262        logger.debug('Executing script')
[98fc98d]263        script = six.text_type(script.toUtf8()).decode('base64')
[11f7a07]264        th = ScriptExecutorThread(script)
265        th.start()
266
267    def logoff(self):
268        logger.debug('Logoff invoked')
269        operations.logoff()  # Invoke log off
270
271    def about(self):
272        self.aboutDlg.exec_()
273
[98fc98d]274    def cleanup(self):
[11f7a07]275        logger.debug('Quit invoked')
276        if self.stopped is False:
277            self.stopped = True
278            try:
279                self.deinitialize()
280            except Exception:
281                logger.exception()
282                logger.error('Got exception deinitializing modules')
[98fc98d]283
[11f7a07]284            try:
285                # If we close Client, send Logoff to Broker
286                self.ipc.sendLogout(operations.getCurrentUser())
[0698264]287                time.sleep(1)
[11f7a07]288                self.timer.stop()
289                self.ipc.stop()
290            except Exception:
[0698264]291                # May we have lost connection with server, simply log and exit in that case
[e21cba1]292                logger.exception()
293                logger.exception("Got an exception, processing quit")
[11f7a07]294
[98fc98d]295            try:
296                # operations.logoff()  # Uncomment this after testing to logoff user
297                pass
298            except Exception:
299                pass
300
301    def quit(self):
[815ea50]302        # logger.debug("Exec quit {}".format(self.stopped))
[98fc98d]303        if self.stopped is False:
304            self.cleanup()
305            self.app.quit()
[11f7a07]306
[aac3fb9]307    def closeEvent(self, event):
[98fc98d]308        logger.debug("Exec closeEvent")
309        event.accept()
310        self.quit()
[11f7a07]311
[815ea50]312
[11f7a07]313if __name__ == '__main__':
314    app = QtGui.QApplication(sys.argv)
315
316    if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
317        # QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.")
318        sys.exit(1)
319
320    # This is important so our app won't close on messages windows (alerts, etc...)
321    QtGui.QApplication.setQuitOnLastWindowClosed(False)
322
323    try:
324        trayIcon = OGASystemTray(app)
325    except Exception as e:
326        logger.exception()
[815ea50]327        logger.error('OGA Service is not running, or it can\'t contact with OGA Server. User Tools stopped: {}'.format(
328            utils.exceptionToMessage(e)))
[11f7a07]329        sys.exit(1)
330
331    try:
332        trayIcon.initialize()  # Initialize modules, etc..
333    except Exception as e:
334        logger.exception()
335        logger.error('Exception initializing OpenGnsys User Agent {}'.format(utils.exceptionToMessage(e)))
336        trayIcon.quit()
337        sys.exit(1)
338
[98fc98d]339    app.aboutToQuit.connect(trayIcon.cleanup)
[11f7a07]340    trayIcon.show()
341
342    # Catch kill and logout user :)
[98fc98d]343    atexit.register(sigAtExit)
[11f7a07]344
345    res = app.exec_()
346
347    logger.debug('Exiting')
348    trayIcon.quit()
349
350    sys.exit(res)
Note: See TracBrowser for help on using the repository browser.