source: ogAgent-Git/src/OGAgentUser.py @ 324ffda

configure-ptt-chedecorare-oglive-methodsejecutarscript-b64fix-cfg2objfixes-winlgromero-filebeatmainno-ptt-paramogcore1oglogoglog2override-moduleping1ping2ping3ping4report-progress
Last change on this file since 324ffda was 8c6a652, checked in by Natalia Serrano <natalia.serrano@…>, 9 months ago

refs #500 #501 #502 implement job manager

  • Property mode set to 100755
File size: 11.8 KB
Line 
1#!/usr/bin/env python3
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.
29"""
30@author: Adolfo Gómez, dkmaster at dkmon dot com
31"""
32import atexit
33import base64
34import json
35import sys
36import time
37import os
38from PyQt6 import QtCore, QtGui, QtWidgets
39
40from about_dialog_ui import Ui_OGAAboutDialog
41from message_dialog_ui import Ui_OGAMessageDialog
42from opengnsys import VERSION, ipc, operations, utils
43from opengnsys.config import readConfig
44from opengnsys.loader import loadModules
45from opengnsys.log import logger
46from opengnsys.jobmgr import JobMgr
47from opengnsys.service import IPC_PORT
48
49trayIcon = None
50
51
52def sigAtExit():
53    if trayIcon:
54        trayIcon.quit()
55
56
57# About dialog
58class OGAAboutDialog(QtWidgets.QDialog):
59    def __init__(self, parent=None):
60        QtWidgets.QDialog.__init__(self, parent)
61        self.ui = Ui_OGAAboutDialog()
62        self.ui.setupUi(self)
63        self.ui.VersionLabel.setText("Version " + VERSION)
64
65    def closeDialog(self):
66        self.hide()
67
68
69class OGAMessageDialog(QtWidgets.QDialog):
70    def __init__(self, parent=None):
71        QtWidgets.QDialog.__init__(self, parent)
72        self.ui = Ui_OGAMessageDialog()
73        self.ui.setupUi(self)
74
75    def message(self, message):
76        self.ui.message.setText(message)
77        self.show()
78
79    def closeDialog(self):
80        self.hide()
81
82
83class MessagesProcessor(QtCore.QThread):
84    logoff = QtCore.pyqtSignal(name='logoff')
85    message = QtCore.pyqtSignal(tuple, name='message')
86    script = QtCore.pyqtSignal(str, name='script')
87    exit = QtCore.pyqtSignal(name='exit')
88
89    def __init__(self, port):
90        super(self.__class__, self).__init__()
91        # Retries connection for a while
92        for _ in range(10):
93            try:
94                self.ipc = ipc.ClientIPC(port)
95                self.ipc.start()
96                break
97            except Exception:
98                logger.debug('IPC Server is not reachable')
99                self.ipc = None
100                time.sleep(2)
101
102        self.running = False
103
104    def stop(self):
105        self.running = False
106        if self.ipc:
107            self.ipc.stop()
108
109    def isAlive(self):
110        return self.ipc is not None
111
112    def sendLogin(self, user_data):
113        if self.ipc:
114            self.ipc.sendLogin(user_data)
115
116    def sendLogout(self, username):
117        if self.ipc:
118            self.ipc.sendLogout(username)
119
120    def sendMessage(self, module, message, data):
121        if self.ipc:
122            self.ipc.sendMessage(module, message, data)
123
124    def run(self):
125        if self.ipc is None:
126            return
127        self.running = True
128
129        # Wait a bit so we ensure IPC thread is running...
130        time.sleep(2)
131
132        while self.running and self.ipc.running:
133            try:
134                msg = self.ipc.getMessage()
135                if msg is None:
136                    break
137                msg_id, data = msg
138                logger.debug('Got Message on User Space: {}:{}'.format(msg_id, data))
139                if msg_id == ipc.MSG_MESSAGE:
140                    module, message, data = data.decode('utf-8').split('\0')
141                    self.message.emit((module, message, data))
142                elif msg_id == ipc.MSG_LOGOFF:
143                    self.logoff.emit()
144                elif msg_id == ipc.MSG_SCRIPT:
145                    self.script.emit(data.decode('utf-8'))
146            except Exception as e:
147                logger.error('Got error on IPC thread {}'.format(utils.exceptionToMessage(e)))
148
149        if self.ipc.running is False and self.running is True:
150            logger.warn('Lost connection with Service, closing program')
151
152        self.exit.emit()
153
154
155class OGASystemTray(QtWidgets.QSystemTrayIcon):
156    jobmgr = JobMgr()
157    def __init__(self, app_, parent=None):
158        self.app = app_
159        self.config = readConfig(client=True)
160        self.modules = None
161        # Get opengnsys section as dict
162        cfg = dict(self.config.items('opengnsys'))
163        # Set up log level
164        logger.setLevel(cfg.get('log', 'INFO'))
165
166        self.ipcport = int(cfg.get('ipc_port', IPC_PORT))
167
168        QtCore.QDir.addSearchPath('images', os.path.join(os.path.dirname(__file__), 'img'))
169        icon = QtGui.QIcon('images:oga.png')
170
171        QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
172        self.menu = QtWidgets.QMenu(parent)
173        exit_action = self.menu.addAction("About")
174        exit_action.triggered.connect(self.about)
175        self.setContextMenu(self.menu)
176        self.ipc = MessagesProcessor(self.ipcport)
177
178        if self.ipc.isAlive() is False:
179            raise Exception('No connection to service, exiting.')
180
181        self.timer = QtCore.QTimer()
182        self.timer.timeout.connect(self.timerFnc)
183
184        self.stopped = False
185
186        self.ipc.message.connect(self.message)
187        self.ipc.exit.connect(self.quit)
188        self.ipc.script.connect(self.executeScript)
189        self.ipc.logoff.connect(self.logoff)
190
191        self.aboutDlg = OGAAboutDialog()
192        self.msgDlg = OGAMessageDialog()
193
194        self.timer.start(1000)  # Launch idle checking every 1 seconds
195
196        self.ipc.start()
197
198    def initialize(self):
199        # Load modules and activate them
200        # Also, sends "login" event to service
201        self.modules = loadModules(self, client=True)
202        logger.debug('Modules: {}'.format(list(v.name for v in self.modules)))
203
204        # Send init to all modules
205        valid_mods = []
206        for mod in self.modules:
207            try:
208                logger.debug('Activating module {}'.format(mod.name))
209                mod.activate()
210                valid_mods.append(mod)
211            except Exception as e:
212                logger.exception()
213                logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
214        self.modules[:] = valid_mods  # copy instead of assignment
215        # If this is running, it's because he have logged in, inform service of this fact
216        self.ipc.sendLogin((operations.getCurrentUser(), operations.getSessionLanguage(),
217                            operations.get_session_type()))
218
219    def deinitialize(self):
220        for mod in reversed(self.modules):  # Deinitialize reversed of initialization
221            try:
222                logger.debug('Deactivating module {}'.format(mod.name))
223                mod.deactivate()
224            except Exception as e:
225                logger.exception()
226                logger.error("Deactivation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
227
228    def timerFnc(self):
229        pass
230
231    def message(self, msg):
232        """
233        Processes the message sent asynchronously, msg is an QString
234        """
235        try:
236            logger.debug('msg: {}, {}'.format(type(msg), msg))
237            module, message, data = msg
238        except Exception as e:
239            logger.error('Got exception {} processing message {}'.format(e, msg))
240            return
241
242        for v in self.modules:
243            if v.name == module:  # Case Sensitive!!!!
244                try:
245                    logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data))
246                    v.processMessage(message, json.loads(data))
247                    return
248                except Exception as e:
249                    logger.error('Got exception {} processing generic message on {}'.format(e, v.name))
250
251        logger.error('Module {} not found, messsage {} not sent'.format(module, message))
252
253    ## when is this run??
254    def executeScript(self, script):
255        script = base64.b64decode(script.encode('ascii'))
256        logger.debug('Executing received script "{}"'.format(script))
257        self.jobmgr.launch_job (script, True)
258
259    def logoff(self):
260        logger.debug('Logoff invoked')
261        operations.logoff()  # Invoke log off
262
263    def about(self):
264        self.aboutDlg.exec()
265
266    def cleanup(self):
267        logger.debug('Quit invoked')
268        if self.stopped is False:
269            self.stopped = True
270            try:
271                self.deinitialize()
272            except Exception:
273                logger.exception()
274                logger.error('Got exception deinitializing modules')
275
276            try:
277                # If we close Client, send Logoff to Broker
278                self.ipc.sendLogout(operations.getCurrentUser())
279                time.sleep(1)
280                self.timer.stop()
281                self.ipc.stop()
282            except Exception:
283                # May we have lost connection with server, simply log and exit in that case
284                logger.exception()
285                #  File "/home/nati/Downloads/work/opengnsys/ogagent/src/OGAgentUser.py", line 286, in cleanup
286                #      logger.exception("Got an exception, processing quit")
287                #TypeError: Logger.exception() takes 1 positional argument but 2 were given
288                #logger.exception("Got an exception, processing quit")
289
290            try:
291                # operations.logoff()  # Uncomment this after testing to logoff user
292                pass
293            except Exception:
294                pass
295
296    def quit(self):
297        # logger.debug("Exec quit {}".format(self.stopped))
298        if self.stopped is False:
299            self.cleanup()
300            self.app.quit()
301
302    def closeEvent(self, event):
303        logger.debug("Exec closeEvent")
304        event.accept()
305        self.quit()
306
307
308if __name__ == '__main__':
309    app = QtWidgets.QApplication(sys.argv)
310
311    if not QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
312        # QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.")
313        sys.exit(1)
314
315    # This is important so our app won't close on messages windows (alerts, etc...)
316    QtWidgets.QApplication.setQuitOnLastWindowClosed(False)
317
318    try:
319        trayIcon = OGASystemTray(app)
320    except Exception as e:
321        logger.exception()
322        logger.error('OGA Service is not running, or it can\'t contact with OGA Server. User Tools stopped: {}'.format(
323            utils.exceptionToMessage(e)))
324        sys.exit(1)
325
326    try:
327        trayIcon.initialize()  # Initialize modules, etc..
328    except Exception as e:
329        logger.exception()
330        logger.error('Exception initializing OpenGnsys User Agent {}'.format(utils.exceptionToMessage(e)))
331        trayIcon.quit()
332        sys.exit(1)
333
334    app.aboutToQuit.connect(trayIcon.cleanup)
335    trayIcon.show()
336
337    # Catch kill and logout user :)
338    atexit.register(sigAtExit)
339
340    res = app.exec()
341
342    logger.debug('Exiting')
343    trayIcon.quit()
344
345    sys.exit(res)
Note: See TracBrowser for help on using the repository browser.