summaryrefslogtreecommitdiffstats
path: root/admin/Sources/Clients/ogagent/src/OGAgentUser.py
blob: 1c9bf30e88e22ecc898bdfc3aaee468557442d34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#!/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)