Completed
Push — pulsed_with_queued_connections ( 6b1460...30fbbf )
by
unknown
02:58
created

Manager.loadConfigureModule()   F

Complexity

Conditions 12

Size

Total Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
c 1
b 0
f 0
dl 0
loc 61
rs 2.9445

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Manager.loadConfigureModule() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
"""
3
This file contains the Qudi Manager class.
4
5
Qudi is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9
10
Qudi is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
GNU General Public License for more details.
14
15
You should have received a copy of the GNU General Public License
16
along with Qudi. If not, see <http://www.gnu.org/licenses/>.
17
18
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
19
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
20
21
Derived form ACQ4:
22
Copyright 2010  Luke Campagnola
23
Originally distributed under MIT/X11 license. See documentation/MITLicense.txt for more infomation.
24
"""
25
26
import logging
27
logger = logging.getLogger(__name__)
28
29
import os
30
import sys
31
import re
32
import time
0 ignored issues
show
Unused Code introduced by
The import time seems to be unused.
Loading history...
33
import importlib
34
35
from qtpy import QtCore
36
from . import config
37
38
from .util import ptime
0 ignored issues
show
Unused Code introduced by
Unused ptime imported from util
Loading history...
39
from .util.mutex import Mutex   # Mutex provides access serialization between threads
40
from .util.modules import toposort, isBase
41
from collections import OrderedDict
42
from .logger import register_exception_handler
43
from .threadmanager import ThreadManager
44
from .remote import RemoteObjectManager
45
from .base import Base
46
47
48
class Manager(QtCore.QObject):
49
    """The Manager object is responsible for:
50
      - Loading/configuring device modules and storing their handles
51
      - Providing unified timestamps
52
      - Making sure all devices/modules are properly shut down
53
        at the end of the program
54
55
      @signal sigConfigChanged: the configuration has changed, please reread your configuration
56
      @signal sigModulesChanged: the available modules have changed
57
      @signal sigModuleHasQuit: the module whose name is passed is now deactivated
58
      @signal sigAbortAll: abort all running things as quicly as possible
59
      @signal sigManagerQuit: the manager is quitting
60
      @signal sigManagerShow: show whatever part of the GUI is important
61
      """
62
63
    # Prepare Signal declarations for Qt: Allows Python to interface with Qt
64
    # signal and slot delivery mechanisms.
65
    sigConfigChanged = QtCore.Signal()
66
    sigModulesChanged = QtCore.Signal()
67
    sigModuleHasQuit = QtCore.Signal(object)
68
    sigLogDirChanged = QtCore.Signal(object)
69
    sigAbortAll = QtCore.Signal()
70
    sigManagerQuit = QtCore.Signal(object, bool)
71
    sigShowManager = QtCore.Signal()
72
73
    def __init__(self, args, **kwargs):
74
        """Constructor for Qudi main management class
75
76
          @param args: argparse command line arguments
77
        """
78
        # used for keeping some basic methods thread-safe
79
        self.lock = Mutex(recursive=True)
80
        self.tree = OrderedDict()
81
        self.tree['config'] = OrderedDict()
82
        self.tree['defined'] = OrderedDict()
83
        self.tree['loaded'] = OrderedDict()
84
85
        self.tree['defined']['hardware'] = OrderedDict()
86
        self.tree['loaded']['hardware'] = OrderedDict()
87
88
        self.tree['defined']['gui'] = OrderedDict()
89
        self.tree['loaded']['gui'] = OrderedDict()
90
91
        self.tree['defined']['logic'] = OrderedDict()
92
        self.tree['loaded']['logic'] = OrderedDict()
93
94
        self.tree['global'] = OrderedDict()
95
        self.tree['global']['startup'] = list()
96
97
        self.hasGui = not args.no_gui
98
        self.currentDir = None
99
        self.baseDir = None
100
        self.alreadyQuit = False
101
        self.remoteServer = True
102
103
        try:
104
            # Initialize parent class QObject
105
            super().__init__(**kwargs)
106
107
            # Register exception handler
108
            register_exception_handler(self)
109
110
            # Thread management
111
            self.tm = ThreadManager()
112
            logger.debug('Main thread is {0}'.format(QtCore.QThread.currentThreadId()))
113
114
            # Task runner
115
            self.tr = None
116
117
            # Gui setup if we have gui
118
            if self.hasGui:
119
                import core.gui
120
                self.gui = core.gui.Gui()
121
                self.gui.setTheme()
122
                self.gui.setAppIcon()
123
124
            # Read in configuration file
125
            if args.config == '':
126
                config_file = self._getConfigFile()
127
            else:
128
                config_file = args.config
129
            self.configDir = os.path.dirname(config_file)
130
            self.readConfig(config_file)
131
132
            # Create remote module server
133
            try:
134
                if 'serverport' in self.tree['global']:
135
                    remotePort = self.tree['global']['serverport']
136
                    logger.info('Remote port is configured to {0}'.format(
137
                        remotePort))
138
                else:
139
                    remotePort = 12345
140
                    logger.info('Remote port is the standard {0}'.format(
141
                        remotePort))
142
                serveraddress = 'localhost'
143
                if 'serveraddress' in self.tree['global']:
144
                    serveraddress = self.tree['global']['serveraddress']
145
                else:
146
                    # bind to all available interfaces
147
                    serveraddress = ''
148
                if 'certfile' in self.tree['global']:
149
                    certfile = self.tree['global']['certfile']
150
                else:
151
                    certfile = None
152
                if 'keyfile' in self.tree['global']:
153
                    keyfile = self.tree['global']['keyfile']
154
                else:
155
                    keyfile = None
156
                self.rm = RemoteObjectManager(
157
                    self,
158
                    serveraddress,
159
                    remotePort,
160
                    certfile=certfile,
161
                    keyfile=keyfile)
162
                self.rm.createServer()
163
            except:
164
                self.remoteServer = False
165
                logger.exception('Remote server could not be started.')
166
167
            logger.info('Qudi started.')
168
169
            # Load startup things from config here
170
            if 'startup' in self.tree['global']:
171
                # walk throug the list of loadable modules to be loaded on
172
                # startup and load them if appropriate
173
                for key in self.tree['global']['startup']:
174
                    if key in self.tree['defined']['hardware']:
175
                        self.startModule('hardware', key)
176
                        self.sigModulesChanged.emit()
177
                    elif key in self.tree['defined']['logic']:
178
                        self.startModule('logic', key)
179
                        self.sigModulesChanged.emit()
180
                    elif self.hasGui and key in self.tree['defined']['gui']:
181
                        self.startModule('gui', key)
182
                        self.sigModulesChanged.emit()
183
                    else:
184
                        logger.error('Loading startup module {} failed, not '
185
                                     'defined anywhere.'.format(key))
186
        except:
187
            logger.exception('Error while configuring Manager:')
188
        finally:
189
            if (len(self.tree['loaded']['logic']) == 0
190
                    and len(self.tree['loaded']['gui']) == 0):
191
                logger.critical('No modules loaded during startup.')
192
193
    def getMainDir(self):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
194
        """Returns the absolut path to the directory of the main software.
195
196
             @return string: path to the main tree of the software
197
198
        """
199
        return os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
200
201
    def _getConfigFile(self):
202
        """ Search all the default locations to find a configuration file.
203
204
          @return sting: path to configuration file
205
        """
206
        path = self.getMainDir()
207
        # we first look for config/load.cfg which can point to another
208
        # config file using the "configfile" key
209
        loadConfigFile = os.path.join(path, 'config', 'load.cfg')
210
        if os.path.isfile(loadConfigFile):
211
            logger.info('load.cfg config file found at {0}'.format(
212
                loadConfigFile))
213
            try:
214
                confDict = config.load(loadConfigFile)
215
                if ('configfile' in confDict
216
                        and isinstance(confDict['configfile'], str)):
217
                    # check if this config file is existing
218
                    # try relative filenames
219
                    configFile = os.path.join(path, 'config',
220
                                              confDict['configfile'])
221
                    if os.path.isfile(configFile):
222
                        return configFile
223
                    # try absolute filename or relative to pwd
224
                    if os.path.isfile(confDict['configfile']):
225
                        return confDict['configfile']
226
                    else:
227
                        logger.critical('Couldn\'t find config file '
228
                                        'specified in load.cfg: {0}'.format(
229
                                            confDict['configfile']))
230
            except Exception:
231
                logger.exception('Error while handling load.cfg.')
232
        # try config/example/custom.cfg next
233
        cf = os.path.join(path, 'config', 'example', 'custom.cfg')
234
        if os.path.isfile(cf):
235
            return cf
236
        # try config/example/default.cfg
237
        cf = os.path.join(path, 'config', 'example', 'default.cfg')
238
        if os.path.isfile(cf):
239
            return cf
240
        raise Exception('Could not find any config file.')
241
242
    def _appDataDir(self):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
243
        """Get the system specific application data directory.
244
245
          @return string: path to application directory
246
        """
247
        # return the user application data directory
248
        if sys.platform == 'win32':
249
            # resolves to "C:/Documents and Settings/User/Application Data/"
250
            # on XP and "C:\User\Username\AppData\Roaming" on win7
251
            return os.path.join(os.environ['APPDATA'], 'qudi')
252
        elif sys.platform == 'darwin':
253
            return os.path.expanduser('~/Library/Preferences/qudi')
254
        else:
255
            return os.path.expanduser('~/.local/qudi')
256
257
    @QtCore.Slot(str)
258
    def readConfig(self, configFile):
259
        """Read configuration file and sort entries into categories.
260
261
          @param string configFile: path to configuration file
262
        """
263
        print("============= Starting Manager configuration from {0} =================".format(
264
            configFile))
265
        logger.info("Starting Manager configuration from {0}".format(
266
            configFile))
267
        cfg = config.load(configFile)
268
        # Read modules, devices, and stylesheet out of config
269
        self.configure(cfg)
270
271
        self.configFile = configFile
272
        print("\n============= Manager configuration complete =================\n")
273
        logger.info('Manager configuration complete.')
274
275
    @QtCore.Slot(dict)
276
    def configure(self, cfg):
277
        """Sort modules from configuration into categories
278
279
          @param dict cfg: dictionary from configuration file
280
281
          There are the main categories hardware, logic, gui, startup
282
          and global.
283
          Startup modules can be logic or gui and are loaded
284
          directly on 'startup'.
285
          'global' contains settings for the whole application.
286
          hardware, logic and gui contain configuration of and
287
          for loadable modules.
288
        """
289
290
        for key in cfg:
291
            try:
292
                # hardware
293
                if key == 'hardware' and cfg['hardware'] is not None:
294
                    for m in cfg['hardware']:
295
                        if 'module.Class' in cfg['hardware'][m]:
296
                            self.tree['defined']['hardware'][
297
                                m] = cfg['hardware'][m]
298
                        else:
299
                            logger.warning('    --> Ignoring device {0} -- '
300
                                           'no module specified'.format(m))
301
302
                # logic
303
                elif key == 'logic' and cfg['logic'] is not None:
304
                    for m in cfg['logic']:
305
                        if 'module.Class' in cfg['logic'][m]:
306
                            self.tree['defined']['logic'][m] = cfg['logic'][m]
307
                        else:
308
                            logger.warning('    --> Ignoring logic {0} -- '
309
                                           'no module specified'.format(m))
310
311
                # GUI
312
                elif key == 'gui' and cfg['gui'] is not None and self.hasGui:
313
                    for m in cfg['gui']:
314
                        if 'module.Class' in cfg['gui'][m]:
315
                            self.tree['defined']['gui'][m] = cfg['gui'][m]
316
                        else:
317
                            logger.warning('    --> Ignoring GUI {0} -- no '
318
                                           'module specified'.format(m))
319
320
                # Load on startup
321
                elif key == 'startup':
322
                    logger.warning('Old style startup loading not supported. '
323
                                   'Please update your config file.')
324
325
                # global config
326
                elif key == 'global' and cfg['global'] is not None:
327
                    for m in cfg['global']:
328
                        if m == 'startup':
329
                            self.tree['global']['startup'] = cfg[
330
                                'global']['startup']
331
                        elif m == 'stylesheet' and self.hasGui:
332
                            self.tree['global']['stylesheet'] = cfg['global']['stylesheet']
333
                            stylesheetpath = os.path.join(
334
                                self.getMainDir(),
335
                                'artwork',
336
                                'styles',
337
                                'application',
338
                                cfg['global']['stylesheet'])
339
                            if not os.path.isfile(stylesheetpath):
340
                                logger.warning(
341
                                    'Stylesheet not found at {0}'.format(stylesheetpath))
342
                                continue
343
                            with open(stylesheetpath, 'r') as stylesheetfile:
344
                                stylesheet = stylesheetfile.read()
345
                                self.gui.setStyleSheet(stylesheet)
346
                        else:
347
                            self.tree['global'][m] = cfg['global'][m]
348
349
                # Copy in any other configurations.
350
                # dicts are extended, all others are overwritten.
351
                else:
352
                    if isinstance(cfg[key], dict):
353
                        if key not in self.tree['config']:
354
                            self.tree['config'][key] = {}
355
                        for key2 in cfg[key]:
356
                            self.tree['config'][key][key2] = cfg[key][key2]
357
                    else:
358
                        self.tree['config'][key] = cfg[key]
359
            except:
360
                logger.exception('Error in configuration:')
361
        # print self.tree['config']
362
        self.sigConfigChanged.emit()
363
364
    def readConfigFile(self, fileName, missingOk=True):
365
        """Actually check if the configuration file exists and read it
366
367
          @param string fileName: path to configuration file
368
          @param bool missingOk: suppress exception if file does not exist
369
370
          @return dict: configuration from file
371
        """
372
        with self.lock:
373
            if os.path.isfile(fileName):
374
                return config.load(fileName)
375
            else:
376
                fileName = self.configFileName(fileName)
377
                if os.path.isfile(fileName):
378
                    return config.load(fileName)
379
                else:
380
                    if missingOk:
381
                        return {}
382
                    else:
383
                        raise Exception(
384
                            'Config file {0} not found.'.format(fileName))
385
386
    @QtCore.Slot(dict, str)
387
    def writeConfigFile(self, data, fileName):
388
        """Write a file into the currently used config directory.
389
390
          @param dict data: dictionary to write into file
391
          @param string fileName: path for filr to be written
392
        """
393
        with self.lock:
394
            fileName = self.configFileName(fileName)
395
            dirName = os.path.dirname(fileName)
396
            if not os.path.exists(dirName):
397
                os.makedirs(dirName)
398
            config.save(fileName, data)
399
400
    def configFileName(self, name):
401
        """Get the full path of a configuration file from its filename.
402
403
          @param string name: filename of file in configuration directory
404
405
          @return string: full path to file
406
        """
407
        with self.lock:
408
            return os.path.join(self.configDir, name)
409
410
    @QtCore.Slot(str)
411
    def saveConfig(self, filename):
412
        """Save configuration to a file.
413
414
          @param str filename: path where the config flie should be saved
415
        """
416
        saveconfig = OrderedDict()
417
        saveconfig.update(self.tree['defined'])
418
        saveconfig['global'] = self.tree['global']
419
420
        self.writeConfigFile(saveconfig, filename)
421
        logger.info('Saved configuration to {0}'.format(filename))
422
423
    @QtCore.Slot(str, bool)
424
    def loadConfig(self, filename, restart=False):
425
        """ Load configuration from file.
426
427
          @param str filename: path of file to be loaded
428
        """
429
        maindir = self.getMainDir()
430
        configdir = os.path.join(maindir, 'config')
431
        loadFile = os.path.join(configdir, 'load.cfg')
432
        if filename.startswith(configdir):
433
            filename = re.sub(
434
                '^' + re.escape('/'),
435
                '',
436
                re.sub(
437
                    '^' + re.escape(configdir),
438
                    '',
439
                    filename)
440
                )
441
        loadData = {'configfile': filename}
442
        config.save(loadFile, loadData)
443
        logger.info('Set loaded configuration to {0}'.format(filename))
444
        if restart:
445
            logger.info('Restarting Qudi after configuration reload.')
446
            self.restart()
447
448
    @QtCore.Slot(str, str)
449
    def reloadConfigPart(self, base, mod):
450
        """Reread the configuration file and update the internal configuration of module
451
452
        @params str modname: name of module where config file should be reloaded.
453
        """
454
        configFile = self._getConfigFile()
455
        cfg = self.readConfigFile(configFile)
456
        try:
457
            if cfg[base][mod]['module.Class'] == self.tree['defined'][base][mod]['module.Class']:
458
                self.tree['defined'][base][mod] = cfg[base][mod]
459
        except KeyError:
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
460
            pass
461
462
    ##################
463
    # Module loading #
464
    ##################
465
466
    def importModule(self, baseName, module):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
467
        """Load a python module that is a loadable Qudi module.
468
469
          @param string baseName: the module base package (hardware, logic, or gui)
470
          @param string module: the python module name inside the base package
471
472
          @return object: the loaded python module
473
        """
474
475
        logger.info('Loading module ".{0}.{1}"'.format(baseName, module))
476
        if not isBase(baseName):
477
            raise Exception('You are trying to cheat the '
478
                            'system with some category {0}'.format(baseName))
479
480
        # load the python module
481
        mod = importlib.__import__('{0}.{1}'.format(
482
            baseName, module), fromlist=['*'])
483
        # print('refcnt:', sys.getrefcount(mod))
484
        return mod
485
486
    def configureModule(self, moduleObject, baseName, className, instanceName,
487
                        configuration=None):
488
        """Instantiate an object from the class that makes up a Qudi module
489
           from a loaded python module object.
490
491
          @param object moduleObject: loaded python module
492
          @param string baseName: module base package (hardware, logic or gui)
493
          @param string className: name of the class we want an object from
494
                                 (same as module name usually)
495
          @param string instanceName: unique name thet the Qudi module instance
496
                                 was given in the configuration
497
          @param dict configuration: configuration options for the Qudi module
498
499
          @return object: Qudi module instance (object of the class derived
500
                          from Base)
501
502
          This method will add the resulting Qudi module instance to internal
503
          bookkeeping.
504
        """
505
        if configuration is None:
506
            configuration = {}
507
        logger.info('Configuring {0} as {1}'.format(
508
            className, instanceName))
509
        with self.lock:
510
            if isBase(baseName):
511
                if self.isModuleLoaded(baseName, instanceName):
512
                    raise Exception(
513
                        '{0} already exists with name {1}'.format(baseName, instanceName))
514
            else:
515
                raise Exception('You are trying to cheat the system with some '
516
                                'category {0}'.format(baseName))
517
518
        if configuration is None:
519
            configuration = {}
520
521
        # get class from module by name
522
        print(moduleObject, className)
523
        modclass = getattr(moduleObject, className)
524
525
        # FIXME: Check if the class we just obtained has the right inheritance
526
        if not issubclass(modclass, Base):
527
            raise Exception('Bad inheritance, for instance {0!s} from {1!s}.{2!s}.'.format(
528
                instanceName, baseName, className))
529
530
        # Create object from class
531
        instance = modclass(manager=self, name=instanceName, config=configuration)
532
533
        with self.lock:
534
            self.tree['loaded'][baseName][instanceName] = instance
535
536
        self.sigModulesChanged.emit()
537
        return instance
538
539
    def connectModule(self, base, mkey):
540
        """ Connects the given module in mkey to main object with the help of base.
541
542
          @param string base: module base package (hardware, logic or gui)
543
          @param string mkey: module which you want to connect
544
545
          @return int: 0 on success, -1 on failure
546
        """
547
        thismodule = self.tree['defined'][base][mkey]
548
        if not self.isModuleLoaded(base, mkey):
549
            logger.error('Loading of {0} module {1} as {2} was not '
550
                         'successful, not connecting it.'.format(
551
                             base, thismodule['module.Class'], mkey))
552
            return -1
553
        loaded_module = self.tree['loaded'][base][mkey]
554
        if 'connect' not in thismodule:
555
            return 0
556
        if 'in' not in loaded_module.connector:
557
            logger.error('{0} module {1} loaded as {2} is supposed to '
558
                         'get connected but it does not declare any IN '
559
                         'connectors.'.format(base, thismodule['module.Class'], mkey))
560
            return -1
561
        if 'module.Class' not in thismodule:
562
            logger.error('{0} module {1} ({2}) connection configuration '
563
                         'is broken: no module defined.'.format(
564
                             base, mkey, thismodule['module.Class']))
565
            return -1
566
        if not isinstance(thismodule['connect'], OrderedDict):
567
            logger.error('{0} module {1} ({2}) connection configuration '
568
                         'is broken: connect is not a dictionary.'
569
                         ''.format(base, mkey, thismodule['module.Class']))
570
            return -1
571
572
        connections = thismodule['connect']
573
        for c in connections:
574
            connectorIn = loaded_module.connector['in']
575
            if c not in connectorIn:
576
                logger.error('IN connector {0} of {1} module {2} loaded '
577
                             'as {3} is supposed to get connected but is not declared '
578
                             'in the module.'.format(
579
                                 c, base, thismodule['module.Class'], mkey))
580
                continue
581
            if not isinstance(connectorIn[c], OrderedDict):
582
                logger.error('IN connector is no dictionary.')
583
                continue
584
            if 'class' not in connectorIn[c]:
585
                logger.error('No class key in connection declaration.')
586
                continue
587
            if not isinstance(connectorIn[c]['class'], str):
588
                logger.error('Value for class key is not a string.')
589
                continue
590
            if 'object' not in connectorIn[c]:
591
                logger.error('No object key in connection declaration.')
592
                continue
593
            if connectorIn[c]['object'] is not None:
594
                logger.warning('IN connector {0} of {1} module {2}'
595
                               ' loaded as {3} is already connected.'
596
                               ''.format(c, base, thismodule['module.Class'], mkey))
597
                continue
598
            if not isinstance(connections[c], str):
599
                logger.error('{0} module {1} ({2}) connection configuration '
600
                             'is broken, value for key {3} is not a string.'
601
                             ''.format(base, mkey, thismodule['module.Class'], c))
602
                continue
603
            if '.' not in connections[c]:
604
                logger.error('{0} module {1} ({2}) connection configuration '
605
                             'is broken, value {3} for key {4} does not contain '
606
                             'a dot.'.format(base, mkey,
607
                                             thismodule['module.Class'], connections[c], c))
608
                continue
609
            destmod = connections[c].split('.')[0]
610
            destcon = connections[c].split('.')[1]
611
            destbase = ''
612
            if destmod in self.tree['loaded']['hardware'] and destmod in self.tree['loaded']['logic']:
613
                logger.error('Unique name {0} is in both hardware and logic '
614
                             'module list. Connection is not well defined, cannot '
615
                             'connect {1} ({2}) to  it.'.format(
616
                                 destmod, mkey, thismodule['module.Class']))
617
                continue
618
619
            # Connect to hardware module
620
            elif destmod in self.tree['loaded']['hardware']:
621
                destbase = 'hardware'
622
            elif destmod in self.tree['loaded']['logic']:
623
                destbase = 'logic'
624
            else:
625
                logger.error('Unique name {0} is neither in hardware or '
626
                             'logic module list. Cannot connect {1} ({2}) to it.'
627
                             ''.format(connections[c], mkey,
628
                                       thismodule['module.Class']))
629
                continue
630
631
            if 'out' not in self.tree['loaded'][destbase][destmod].connector:
632
                logger.error('Module {0} loaded as {1} is supposed to '
633
                             'get connected to module loaded as {2} that does not '
634
                             'declare any OUT connectors.'.format(
635
                                 thismodule['module.Class'], mkey, destmod))
636
                continue
637
            outputs = self.tree['loaded'][destbase][destmod].connector['out']
638
            if destcon not in outputs:
639
                logger.error('OUT connector {0} not declared in module {1}.{2} '
640
                             'but connected to IN connector {3} of module {4}.'
641
                             ''.format(destcon, destbase, destmod, c,
642
                                       thismodule['module.Class']))
643
                continue
644
            if not isinstance(outputs[destcon], OrderedDict):
645
                logger.error('OUT connector not a dictionary.')
646
                continue
647
            if 'class' not in outputs[destcon]:
648
                logger.error('No class key in OUT connector dictionary.')
649
                continue
650
            if not isinstance(outputs[destcon]['class'], str):
651
                logger.error('Class value not a string.')
652
                continue
653
#            if not issubclass(self.tree['loaded'][destbase][destmod].__class__, outputs[destcon]['class']):
654
#                logger.error('not the correct class for declared interface.')
655
#                return
656
657
            # Finally set the connection object
658
            logger.info('Connecting {0} module {1}.IN.{2} to {3} {4}.{5}'
659
                        ''.format(base, mkey, c, destbase, destmod, destcon))
660
            connectorIn[c]['object'] = self.tree['loaded'][destbase][destmod]
661
662
        # check that all IN connectors are connected
663
        for c, v in self.tree['loaded'][base][mkey].connector['in'].items():
664
            if v['object'] is None:
665
                logger.error('IN connector {} of module {}.{} is empty, '
666
                             'connection not complete.'.format(c, base, mkey))
667
                return -1
668
        return 0
669
670
    def loadConfigureModule(self, base, key):
671
        """Loads the configuration Module in key with the help of base class.
672
673
          @param string base: module base package (hardware, logic or gui)
674
          @param string key: module which is going to be loaded
675
676
          @return int: 0 on success, -1 on fatal error, 1 on error
677
        """
678
        defined_module = self.tree['defined'][base][key]
679
        if 'module.Class' in defined_module:
680
            if 'remote' in defined_module:
681
                if not self.remoteServer:
682
                    logger.error('Remote functionality not working, check your log.')
683
                    return -1
684
                if not isinstance(defined_module['remote'], str):
685
                    logger.error('Remote URI of {0} module {1} not a string.'.format(base, key))
686
                    return -1
687
                try:
688
                    instance = self.rm.getRemoteModuleUrl(defined_module['remote'])
689
                    logger.info('Remote module {0} loaded as .{1}.{2}.'
690
                        ''.format(defined_module['remote'], base, key))
691
                    with self.lock:
692
                        if isBase(base):
693
                            self.tree['loaded'][base][key] = instance
694
                            self.sigModulesChanged.emit()
695
                        else:
696
                            raise Exception(
697
                                'You are trying to cheat the system with some category {0}'
698
                                ''.format(base))
699
                except:
700
                    logger.exception('Error while loading {0} module: {1}'.format(base, key))
701
                    return -1
702
            else:
703
                try:
704
                    # class_name is the last part of the config entry
705
                    class_name = re.split('\.', defined_module['module.Class'])[-1]
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \. was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
706
                    # module_name is the whole line without this last part (and
707
                    # with the trailing dot removed also)
708
                    module_name = re.sub(
709
                        '.' + class_name + '$',
710
                        '',
711
                        defined_module['module.Class'])
712
713
                    modObj = self.importModule(base, module_name)
714
                    self.configureModule(modObj, base, class_name, key, defined_module)
715
                    if 'remoteaccess' in defined_module and defined_module['remoteaccess']:
716
                        if not self.remoteServer:
717
                            logger.error('Remote module sharing does not work '
718
                                         'as server startup failed earlier, check '
719
                                         'your log.')
720
                            return 1
721
                        self.rm.shareModule(key, self.tree['loaded'][base][key])
722
                except:
723
                    logger.exception(
724
                        'Error while loading {0} module: {1}'.format(base, key))
725
                    return -1
726
        else:
727
            logger.error('Missing module declaration in configuration: '
728
                         '{0}.{1}'.format(base, key))
729
            return -1
730
        return 0
731
732
    def reloadConfigureModule(self, base, key):
733
        """Reloads the configuration module in key with the help of base class.
734
735
          @param string base: module base package (hardware, logic or gui)
736
          @param string key: module which is going to be loaded
737
738
          @return int: 0 on success, -1 on failure
739
        """
740
        defined_module = self.tree['defined'][base][key]
741
        if 'remote' in defined_module:
742
            if not self.remoteServer:
743
                logger.error('Remote functionality not working, check your log.')
744
                return -1
745
            if not isinstance(defined_module['remote'], str):
746
                logger.error('Remote URI of {0} module {1} not a string.'.format(base, key))
747
                return -1
748
            try:
749
                instance = self.rm.getRemoteModuleUrl(defined_module['remote'])
750
                logger.info('Remote module {0} loaded as .{1}.{2}.'
751
                            ''.format(defined_module['remote'], base, key))
752
                with self.lock:
753
                    if isBase(base):
754
                        self.tree['loaded'][base][key] = instance
755
                        self.sigModulesChanged.emit()
756
                    else:
757
                        raise Exception(
758
                            'You are trying to cheat the system with some category {0}'
759
                            ''.format(base))
760
            except:
761
                logger.exception('Error while loading {0} module: {1}'.format(base, key))
762
        elif (key in self.tree['loaded'][base]
763
                and 'module.Class' in defined_module):
764
            try:
765
                # state machine: deactivate
766
                if self.isModuleActive(base, key):
767
                    self.deactivateModule(base, key)
768
            except:
769
                logger.exception('Error while deactivating {0} module: {1}'.format(base, key))
770
                return -1
771
            try:
772
                with self.lock:
773
                    self.tree['loaded'][base].pop(key, None)
774
                # reload config part associated with module
775
                self.reloadConfigPart(base, key)
776
                # class_name is the last part of the config entry
777
                class_name = re.split('\.', defined_module['module.Class'])[-1]
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \. was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
778
                # module_name is the whole line without this last part (and
779
                # with the trailing dot removed also)
780
                module_name = re.sub(
781
                    '.' + class_name + '$',
782
                    '',
783
                    defined_module['module.Class'])
784
785
                modObj = self.importModule(base, module_name)
786
                # des Pudels Kern
787
                importlib.reload(modObj)
788
                self.configureModule(modObj, base, class_name, key, defined_module)
789
            except:
790
                logger.exception('Error while reloading {0} module: {1}'.format(base, key))
791
                return -1
792
        else:
793
            logger.error('Module not loaded or not loadable (missing module '
794
                         'declaration in configuration): {0}.{1}'.format(base, key))
795
        return 0
796
797
    def isModuleDefined(self, base, name):
798
        """Check if module is present in module definition.
799
          @param str base: module base package
800
          @param str name: unique module name
801
          @return bool: module is present in definition
802
        """
803
        return (
804
            isBase(base)
805
            and base in self.tree['defined']
806
            and name in self.tree['defined'][base]
807
            )
808
809
    def isModuleLoaded(self, base, name):
810
        """Check if module was loaded.
811
          @param str base: module base package
812
          @param str name: unique module name
813
          @return bool: module is loaded
814
        """
815
        return (
816
            isBase(base)
817
            and base in self.tree['loaded']
818
            and name in self.tree['loaded'][base]
819
            )
820
821
    def isModuleActive(self, base, name):
822
        """Returns whether a given module is active.
823
824
          @param string base: module base package (hardware, logic or gui)
825
          @param string key: module which is going to be activated.
826
        """
827
        if not self.isModuleLoaded(base, name):
828
            logger.error('{0} module {1} not loaded.'.format(base, name))
829
            return False
830
        return self.tree['loaded'][base][name].getState() in ('idle', 'running')
831
832
    @QtCore.Slot(str, str)
833
    def activateModule(self, base, name):
834
        """Activate the module given in key with the help of base class.
835
836
          @param string base: module base package (hardware, logic or gui)
837
          @param string name: module which is going to be activated.
838
839
        """
840
        if not self.isModuleLoaded(base, name):
841
            logger.error('{0} module {1} not loaded.'.format(base, name))
842
            return
843
        module = self.tree['loaded'][base][name]
844
        if module.getState() != 'deactivated' and (
845
                self.isModuleDefined(base, name)
846
                and 'remote' in self.tree['defined'][base][name]
847
                and self.remoteServer):
848
            logger.debug('No need to activate remote module {0}.{1}.'.format(base, name))
849
            return
850
        if module.getState() != 'deactivated':
851
            logger.error('{0} module {1} not deactivated'.format(base, name))
852
            return
853
        try:
854
            module.setStatusVariables(self.loadStatusVariables(base, name))
855
            # start main loop for qt objects
856
            if base == 'logic':
857
                modthread = self.tm.newThread('mod-{0}-{1}'.format(base, name))
858
                module.moveToThread(modthread)
859
                modthread.start()
860
                success = QtCore.QMetaObject.invokeMethod(
861
                    module,
862
                    "_wrap_activation",
863
                    QtCore.Qt.BlockingQueuedConnection,
864
                    QtCore.Q_RETURN_ARG(bool))
865
            else:
866
                success = module._wrap_activation()
867
            logger.debug('Activation success: {}'.format(success))
868
        except:
869
            logger.exception(
870
                '{0} module {1}: error during activation:'.format(base, name))
871
        QtCore.QCoreApplication.instance().processEvents()
872
873
    @QtCore.Slot(str, str)
874
    def deactivateModule(self, base, name):
875
        """Activated the module given in key with the help of base class.
876
877
          @param string base: module base package (hardware, logic or gui)
878
          @param string name: module which is going to be activated.
879
880
        """
881
        logger.info('Deactivating {0}.{1}'.format(base, name))
882
        if not self.isModuleLoaded(base, name):
883
            logger.error('{0} module {1} not loaded.'.format(base, name))
884
            return
885
        module = self.tree['loaded'][base][name]
886
        if not module.getState() in ('idle', 'running'):
887
            logger.error('{0} module {1} not active (idle or running).'.format(base, name))
888
            return
889
        try:
890
            if base == 'logic':
891
                success = QtCore.QMetaObject.invokeMethod(
892
                    module,
893
                    '_wrap_deactivation',
894
                    QtCore.Qt.BlockingQueuedConnection,
895
                    QtCore.Q_RETURN_ARG(bool))
896
                QtCore.QMetaObject.invokeMethod(
897
                    module,
898
                    'moveToThread',
899
                    QtCore.Qt.BlockingQueuedConnection,
900
                    QtCore.Q_ARG(QtCore.QThread, self.tm.thread))
901
                self.tm.quitThread('mod-{0}-{1}'.format(base, name))
902
                self.tm.joinThread('mod-{0}-{1}'.format(base, name))
903
            else:
904
                success = module._wrap_deactivation()
905
            self.saveStatusVariables(base, name, module.getStatusVariables())
906
            logger.debug('Deactivation success: {}'.format(success))
907
        except:
908
            logger.exception('{0} module {1}: error during deactivation:'.format(base, name))
909
        QtCore.QCoreApplication.instance().processEvents()
910
911
    def getSimpleModuleDependencies(self, base, key):
912
        """ Based on object id, find which connections to replace.
913
914
          @param str base: Module category
915
          @param str key: Unique configured module name for module where
916
                          we want the dependencies
917
918
          @return dict: module dependencies in the right format for the
919
                        toposort function
920
        """
921
        deplist = list()
922
        if not self.isModuleLoaded(base, key):
923
            logger.error('{0} module {1} not loaded.'.format(base, key))
924
            return None
925
        for mbase in self.tree['loaded']:
926
            for mkey,target in self.tree['loaded'][mbase].items():
927
                if not hasattr(target, 'connector'):
928
                    logger.error('No connector in module .{0}.{1}!'.format(mbase, mkey))
929
                    continue
930
                for conn in target.connector['in']:
931
                    if not 'object' in target.connector['in'][conn]:
932
                        logger.error('Malformed connector {2} in module '
933
                                     '.{0}.{1}!'.format(mbase, mkey, conn))
934
                        continue
935
                    if target.connector['in'][conn]['object'] is self.tree['loaded'][base][key]:
936
                        deplist.append((mbase, mkey))
937
        return {key: deplist}
938
939
    def getRecursiveModuleDependencies(self, base, key):
940
        """ Based on input connector declarations, determine in which other modules are needed for a specific module to run.
941
942
          @param str base: Module category
943
          @param str key: Unique configured module name for module where we want the dependencies
944
945
          @return dict: module dependencies in the right format for the toposort function
946
        """
947
        deps = dict()
948
        if not self.isModuleDefined(base, key):
949
            logger.error('{0} module {1}: no such module defined'.format(base, key))
950
            return None
951
        defined_module =  self.tree['defined'][base][key]
952
        if 'connect' not in defined_module:
953
            return dict()
954
        if not isinstance(defined_module['connect'], OrderedDict):
955
            logger.error('{0} module {1}: connect is not a dictionary'.format(base, key))
956
            return None
957
        connections = defined_module['connect']
958
        deplist = set()
959
        for c in connections:
960
            if not isinstance(connections[c], str):
961
                logger.error('Value for class key is not a string.')
962
                return None
963
            if not '.' in connections[c]:
964
                logger.error('{0}.{1}: connection {2}: {3} has wrong format'
965
                             'for connection target'.format(base, key, c, connections[c]))
966
                return None
967
            destmod = connections[c].split('.')[0]
968
            destbase = ''
969
            if destmod in self.tree['defined']['hardware'] and destmod in self.tree['defined']['logic']:
970
                logger.error('Unique name {0} is in both hardware and '
971
                             'logic module list. Connection is not well defined.'
972
                             ''.format(destmod))
973
                return None
974
            elif destmod in self.tree['defined']['hardware']:
975
                destbase = 'hardware'
976
            elif destmod in self.tree['defined']['logic']:
977
                destbase = 'logic'
978
            else:
979
                logger.error('Unique name {0} is neither in hardware or '
980
                             'logic module list. Cannot connect {1} '
981
                             'to it.'.format(connections[c], key))
982
                return None
983
            deplist.add(destmod)
984
            subdeps = self.getRecursiveModuleDependencies(destbase, destmod)
985
            if subdeps is not None:
986
                deps.update(subdeps)
987
            else:
988
                return None
989
        if len(deplist) > 0:
990
            deps.update({key: list(deplist)})
991
        return deps
992
993
    @QtCore.Slot(str, str)
994
    def startModule(self, base, key):
995
        """ Figure out the module dependencies in terms of connections, load and activate module.
996
997
          @param str base: Module category
998
          @param str key: Unique module name
999
1000
          @return int: 0 on success, -1 on error
1001
1002
            If the module is already loaded, just activate it.
1003
            If the module is an active GUI module, show its window.
1004
        """
1005
        deps = self.getRecursiveModuleDependencies(base, key)
1006
        sorteddeps = toposort(deps)
1007
        if len(sorteddeps) == 0:
1008
            sorteddeps.append(key)
1009
1010
        for mkey in sorteddeps:
1011
            for mbase in ('hardware', 'logic', 'gui'):
1012
                if mkey in self.tree['defined'][mbase] and not mkey in self.tree['loaded'][mbase]:
1013
                    success = self.loadConfigureModule(mbase, mkey)
1014
                    if success < 0:
1015
                        logger.warning('Stopping module loading after loading failure.')
1016
                        return -1
1017
                    elif success > 0:
1018
                        logger.warning('Nonfatal loading error, going on.')
1019
                    success = self.connectModule(mbase, mkey)
1020
                    if success < 0:
1021
                        logger.warning('Stopping loading module {0}.{1} after '
1022
                                       'connection failure.'.format(mbase, mkey))
1023
                        return -1
1024
                    if mkey in self.tree['loaded'][mbase]:
1025
                        self.activateModule(mbase, mkey)
1026
                elif mkey in self.tree['defined'][mbase] and mkey in self.tree['loaded'][mbase]:
1027
                    if self.tree['loaded'][mbase][mkey].getState() == 'deactivated':
1028
                        self.activateModule(mbase, mkey)
1029
                    elif self.tree['loaded'][mbase][mkey].getState() != 'deactivated' and mbase == 'gui':
1030
                        self.tree['loaded'][mbase][mkey].show()
1031
        return 0
1032
1033
    @QtCore.Slot(str, str)
1034
    def stopModule(self, base, key):
1035
        """ Figure out the module dependencies in terms of connections and deactivate module.
1036
1037
          @param str base: Module category
1038
          @param str key: Unique module name
1039
1040
        """
1041
        deps = self.getRecursiveModuleDependencies(base, key)
1042
        sorteddeps = toposort(deps)
1043
        if len(sorteddeps) == 0:
1044
            sorteddeps.append(key)
1045
1046
        for mkey in reversed(sorteddeps):
1047
            for mbase in ('hardware', 'logic', 'gui'):
1048
                if mkey in self.tree['defined'][mbase] and mkey in self.tree['loaded'][mbase]:
1049
                    if self.tree['loaded'][mbase][mkey].getState() in ('idle', 'running'):
1050
                        logger.info('Deactivating module {0}.{1}'.format(mbase, mkey))
1051
                        self.deactivateModule(mbase, mkey)
1052
1053
    @QtCore.Slot(str, str)
1054
    def restartModuleSimple(self, base, key):
1055
        """Deactivate, reloade, activate module.
1056
          @param str base: Module category
1057
          @param str key: Unique module name
1058
1059
          @return int: 0 on success, -1 on error
1060
1061
            Deactivates and activates all modules that depend on it in order to
1062
            ensure correct connections.
1063
        """
1064
        deps = self.getSimpleModuleDependencies(base, key)
1065
        if deps is None:
1066
            return
1067
        # Remove references
1068
        for destbase,destmod in deps[key]:
1069
            for c,v in self.tree['loaded'][destbase][destmod].connector['in'].items():
0 ignored issues
show
Unused Code introduced by
The variable c seems to be unused.
Loading history...
1070
                if v['object'] is self.tree['loaded'][base][key]:
1071
                    if self.isModuleActive(destbase, destmod):
1072
                        self.deactivateModule(destbase, destmod)
1073
                    v['object'] = None
1074
1075
        # reload and reconnect
1076
        success = self.reloadConfigureModule(base, key)
1077
        if success < 0:
1078
            logger.warning('Stopping module {0}.{1} loading after loading '
1079
                           'failure.'.format(base, key))
1080
            return -1
1081
        success = self.connectModule(base, key)
1082
        if success < 0:
1083
            logger.warning('Stopping module {0}.{1} loading after '
1084
                           'connection failure.'.format(base, key))
1085
            return -1
1086
        self.activateModule(base, key)
1087
1088
        for depmod in deps[key]:
1089
            destbase, destmod = depmod
1090
            self.connectModule(destbase, destmod)
1091
            self.activateModule(destbase, destmod)
1092
        return 0
1093
1094
    @QtCore.Slot(str, str)
1095
    def restartModuleRecursive(self, base, key):
1096
        """ Figure out the module dependencies in terms of connections, reload and activate module.
1097
1098
          @param str base: Module category
1099
          @param str key: Unique configured module name
1100
1101
        """
1102
        deps = self.getSimpleModuleDependencies(base, key)
1103
        sorteddeps = toposort(deps)
1104
1105
        for mkey in sorteddeps:
1106
            for mbase in ['gui', 'logic', 'hardware']:
1107
                # load if the config changed
1108
                if mkey in self.tree['defined'][mbase] and not mkey in self.tree['loaded'][mbase]:
1109
                    success = self.loadConfigureModule(mbase, mkey)
1110
                    if success < 0:
1111
                        logger.warning('Stopping loading module {0}.{1} after '
1112
                                       'loading error.'.format(mbase, mkey))
1113
                        return -1
1114
                    success = self.connectModule(mbase, mkey)
1115
                    if success < 0:
1116
                        logger.warning('Stopping loading module {0}.{1} after '
1117
                                       'connection error'.format(mbase, mkey))
1118
                        return -1
1119
                    if mkey in self.tree['loaded'][mbase]:
1120
                        self.activateModule(mbase, mkey)
1121
                # reload if already there
1122
                elif mkey in self.tree['loaded'][mbase]:
1123
                    success = self.reloadConfigureModule(mbase, mkey)
1124
                    if success < 0:
1125
                        logger.warning('Stopping loading module {0}.{1} after '
1126
                                       'loading error'.format(mbase, mkey))
1127
                        return -1
1128
                    success = self.connectModule(mbase, mkey)
1129
                    if success < 0:
1130
                        logger.warning('Stopping loading module {0}.{1} after '
1131
                                       'connection error'.format(mbase, mkey))
1132
                        return -1
1133
                    if mkey in self.tree['loaded'][mbase]:
1134
                        self.activateModule(mbase, mkey)
1135
1136
    @QtCore.Slot()
1137
    def startAllConfiguredModules(self):
1138
        """Connect all Qudi modules from the currently loaded configuration and
1139
            activate them.
1140
        """
1141
        # FIXME: actually load all the modules in the correct order and connect
1142
        # the interfaces
1143
        for base,bdict in self.tree['defined'].items():
1144
            for key in bdict:
1145
                self.startModule(base, key)
1146
1147
        logger.info('Activation finished.')
1148
1149
    def getStatusDir(self):
1150
        """ Get the directory where the app state is saved, create it if necessary.
1151
1152
          @return str: path of application status directory
1153
        """
1154
        appStatusDir = os.path.join(self.configDir, 'app_status')
1155
        if not os.path.isdir(appStatusDir):
1156
            os.makedirs(appStatusDir)
1157
        return appStatusDir
1158
1159
    @QtCore.Slot(str, str, dict)
1160
    def saveStatusVariables(self, base, module, variables):
1161
        """ If a module has status variables, save them to a file in the application status directory.
1162
1163
          @param str base: the module category
1164
          @param str module: the unique module name
1165
          @param dict variables: a dictionary of status variable names and values
1166
        """
1167
        if len(variables) > 0:
1168
            try:
1169
                statusdir = self.getStatusDir()
1170
                classname = self.tree['loaded'][base][module].__class__.__name__
1171
                filename = os.path.join(statusdir,
1172
                    'status-{0}_{1}_{2}.cfg'.format(classname, base, module))
1173
                config.save(filename, variables)
1174
            except:
1175
                print(variables)
1176
                logger.exception('Failed to save status variables of module '
1177
                        '{0}.{1}:\n{2}'.format(base, module, repr(variables)))
1178
1179
    def loadStatusVariables(self, base, module):
1180
        """ If a status variable file exists for a module, load it into a dictionary.
1181
1182
          @param str base: the module category
1183
          @param str module: the unique mduel name
1184
1185
          @return dict: dictionary of satus variable names and values
1186
        """
1187
        try:
1188
            statusdir = self.getStatusDir()
1189
            classname = self.tree['loaded'][base][module].__class__.__name__
1190
            filename = os.path.join(
1191
                statusdir, 'status-{0}_{1}_{2}.cfg'.format(classname, base, module))
1192
            if os.path.isfile(filename):
1193
                variables = config.load(filename)
1194
            else:
1195
                variables = OrderedDict()
1196
        except:
1197
            logger.exception('Failed to load status variables.')
1198
            variables = OrderedDict()
1199
        return variables
1200
1201
    @QtCore.Slot(str, str)
1202
    def removeStatusFile(self, base, module):
1203
        try:
1204
            statusdir = self.getStatusDir()
1205
            classname = self.tree['defined'][base][
1206
                module]['module.Class'].split('.')[-1]
1207
            filename = os.path.join(
1208
                statusdir, 'status-{0}_{1}_{2}.cfg'.format(classname, base, module))
1209
            if os.path.isfile(filename):
1210
                os.remove(filename)
1211
        except:
1212
            logger.exception('Failed to remove module status file.')
1213
1214
    @QtCore.Slot()
1215
    def quit(self):
1216
        """Nicely request that all modules shut down."""
1217
        for mbase,bdict in self.tree['loaded'].items():
1218
            for module in bdict:
1219
                self.stopModule(mbase, module)
1220
                QtCore.QCoreApplication.processEvents()
1221
        self.sigManagerQuit.emit(self, False)
1222
1223
    @QtCore.Slot()
1224
    def restart(self):
1225
        """Nicely request that all modules shut down for application restart."""
1226
        for mbase,bdict in self.tree['loaded'].items():
1227
            for module in bdict:
1228
                if self.isModuleActive(mbase, module):
1229
                    self.deactivateModule(mbase, module)
1230
                QtCore.QCoreApplication.processEvents()
1231
        self.sigManagerQuit.emit(self, True)
1232
1233
    @QtCore.Slot(object)
1234
    def registerTaskRunner(self, reference):
1235
        """ Register/deregister/replace a task runner object.
1236
1237
        @param object reference: reference to a task runner or null class
1238
1239
        If a reference is passed that is not None, it is kept and passed out as the task runner instance.
1240
        If a None is passed, the reference is discarded.
1241
        Id another reference is passed, the current one is replaced.
1242
1243
        """
1244
        with self.lock:
1245
            if self.tr is None and reference is not None:
1246
                self.tr = reference
1247
                logger.info('Task runner registered.')
1248
            elif self.tr is not None and reference is None:
1249
                logger.info('Task runner removed.')
1250
            elif self.tr is None and reference is None:
1251
                logger.error('You tried to remove the task runner but none was registered.')
1252
            else:
1253
                logger.warning('Replacing task runner.')
1254
1255