~bratsche/decibel-audio-player/notifications

« back to all changes in this revision

Viewing changes to src/modules/__init__.py

  • Committer: Bazaar Package Importer
  • Date: 2008-11-07 02:15:10 UTC
  • Revision ID: jamesw@ubuntu.com-20081107021510-kur5qpiammaiaxad
Tags: upstream-ubuntu-0.05.2
ImportĀ upstreamĀ versionĀ 0.05.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18
18
 
19
 
import gobject, gtk, os, sys, traceback
 
19
import gobject, gtk, gtk.glade, os, sys, traceback
20
20
 
21
 
from tools     import prefsManager
 
21
from gtk       import gdk
 
22
from tools     import consts, prefs
22
23
from gettext   import gettext as _
23
24
from threading import Lock, Semaphore, Thread
24
25
 
29
30
    # --== COMMANDS ==--
30
31
 
31
32
    # GStreamer player
32
 
    MSG_CMD_PLAY,           # Play a file         Parameters: 'filename'
33
 
    MSG_CMD_STOP,           # Stop playing        Parameters:
34
 
    MSG_CMD_SEEK,           # Jump to a position  Parameters: 'seconds'
35
 
    MSG_CMD_BUFFER,         # Buffer a file       Parameters: 'filename'
36
 
    MSG_CMD_TOGGLE_PAUSE,   # Toggle play/pause   Parameters:
 
33
    MSG_CMD_PLAY,             # Play a file         Parameters: 'filename'
 
34
    MSG_CMD_STOP,             # Stop playing        Parameters:
 
35
    MSG_CMD_SEEK,             # Jump to a position  Parameters: 'seconds'
 
36
    MSG_CMD_BUFFER,           # Buffer a file       Parameters: 'filename'
 
37
    MSG_CMD_TOGGLE_PAUSE,     # Toggle play/pause   Parameters:
37
38
 
38
39
    # Tracklist
39
 
    MSG_CMD_NEXT,           # Play the next track       Parameters:
40
 
    MSG_CMD_PREVIOUS,       # Play the previous track   Parameters:
41
 
    MSG_CMD_TRACKLIST_SET,  # Replace the tracklist     Parameters: 'files', 'playNow'
42
 
    MSG_CMD_TRACKLIST_ADD,  # Extend the tracklist      Parameters: 'files'
43
 
    MSG_CMD_TRACKLIST_CLR,  # Clear the tracklist       Parameters:
 
40
    MSG_CMD_NEXT,             # Play the next track       Parameters:
 
41
    MSG_CMD_PREVIOUS,         # Play the previous track   Parameters:
 
42
    MSG_CMD_TRACKLIST_SET,    # Replace the tracklist     Parameters: 'files', 'playNow'
 
43
    MSG_CMD_TRACKLIST_ADD,    # Extend the tracklist      Parameters: 'files'
 
44
    MSG_CMD_TRACKLIST_CLR,    # Clear the tracklist       Parameters:
44
45
 
45
46
    # Explorer manager
46
 
    MSG_CMD_EXPLORER_ADD,   # Add a new explorer    Parameters: 'module', 'name', 'pixbuf', 'widget'
47
 
 
 
47
    MSG_CMD_EXPLORER_ADD,     # Add a new explorer    Parameters: 'modName', 'expName', 'icon', 'widget', 'usrParam'
 
48
    MSG_CMD_EXPLORER_REMOVE,  # Remove an explorer    Parameters: 'modName', 'expName'
48
49
 
49
50
    # --== EVENTS ==--
50
51
 
52
53
    MSG_EVT_PAUSED,           # Paused                              Parameters:
53
54
    MSG_EVT_STOPPED,          # Stopped                             Parameters:
54
55
    MSG_EVT_UNPAUSED,         # Unpaused                            Parameters:
55
 
    MSG_EVT_NEW_TRACK,        # The current track has changed       Parameters: 'trackInfo'
 
56
    MSG_EVT_NEW_TRACK,        # The current track has changed       Parameters: 'track'
56
57
    MSG_EVT_NEED_BUFFER,      # The next track should be buffered   Parameters:
57
58
    MSG_EVT_TRACK_ENDED,      # The current track has ended         Parameters:
58
59
    MSG_EVT_TRACK_POSITION,   # The track position has changed      Parameters: 'seconds'
65
66
    MSG_EVT_APP_QUIT,         # The application is quitting         Parameters:
66
67
    MSG_EVT_APP_STARTED,      # The application has just started    Parameters:
67
68
 
 
69
    # Modules
 
70
    MSG_EVT_MOD_LOADED,       # The module has been loaded by request of the user      Parameters:
 
71
    MSG_EVT_MOD_UNLOADED,     # The module has been unloaded by request of the user    Parameters:
 
72
 
68
73
    # Explorer manager
69
 
    MSG_EVT_EXPLORER_CHANGED, # A new explorer has been selected    Parameters: 'module', 'name'
 
74
    MSG_EVT_EXPLORER_CHANGED, # A new explorer has been selected    Parameters: 'modName', 'expName', 'usrParam'
70
75
 
71
76
    # End value
72
77
    MSG_END_VALUE
73
 
) = range(24)
74
 
 
75
 
 
76
 
mMutex    = Lock()                                             # This mutex protects all the functions from concurrent access
77
 
mModules  = {}                                                 # All known modules, together with an 'active' boolean
78
 
mHandlers = dict([(msg, []) for msg in xrange(MSG_END_VALUE)]) # For each message, store the list of registered modules
79
 
 
80
 
 
81
 
def load(wTree):
82
 
    """ Dynamically load all modules """
83
 
    # Make sure the path is known to the loader
84
 
    modDir = os.path.dirname(__file__)
85
 
    if not modDir in sys.path:
86
 
        sys.path.append(modDir)
87
 
    # Find all modules
88
 
    for filename in os.listdir(modDir):
89
 
        if filename.endswith('.py') and filename != '__init__.py':
90
 
            try:
91
 
                modName = filename.rsplit('.', 1)[0]
92
 
                mod     = __import__(modName)
93
 
                mModules[getattr(mod, modName)(wTree)] = True
94
 
            except:
95
 
                print _('Unable to load module "%s"!') % (os.path.join(modDir, filename))
96
 
                traceback.print_exc()
 
78
) = range(27)
 
79
 
 
80
 
 
81
# Values associated with a module
 
82
(
 
83
    MOD_PMODULE,      # The actual Python module object
 
84
    MOD_CLASSNAME,    # The classname of the module
 
85
    MOD_DESC,         # Description
 
86
    MOD_INSTANCE,     # Instance, None if not currently enabled
 
87
    MOD_MANDATORY,    # If True, the module cannot be unloaded
 
88
    MOD_CONFIGURABLE, # If True, the module may be configured
 
89
    MOD_DEPS          # Special dependencies of the module (Python modules that may not be installed on the system (e.g., pynotify, pyosd))
 
90
) = range(7)
 
91
 
 
92
 
 
93
mWTree          = None
 
94
mModules        = {}                                                 # All known modules, together with an 'active' boolean
 
95
mHandlers       = dict([(msg, []) for msg in xrange(MSG_END_VALUE)]) # For each message, store the set of registered modules
 
96
mModulesLock    = Lock()                                             # This lock protects the modules list from concurrent access
 
97
mHandlersLock   = Lock()                                             # This lock protects the handlers list from concurrent access
 
98
mEnabledModules = None                                               # List of modules currently enabled
 
99
 
 
100
 
 
101
# Make sure the path to the modules is known to the Python loader
 
102
mModDir = os.path.dirname(__file__)
 
103
if not mModDir in sys.path:
 
104
    sys.path.append(mModDir)
 
105
 
 
106
 
 
107
def __checkDeps(deps):
 
108
    """ Given a list of Python modules, return a new list containing the modules that are not currently installed or None """
 
109
    unmetDeps = []
 
110
    for dep in deps:
 
111
        try:    __import__(dep)
 
112
        except: unmetDeps.append(dep)
 
113
 
 
114
    if len(unmetDeps) == 0: return None
 
115
    else:                   return unmetDeps
 
116
 
 
117
 
 
118
def initialLoad(wTree):
 
119
    """
 
120
        Find all modules, instantiate those that are mandatory or that have been previously enabled by the user
 
121
        This should be the first called function of this module
 
122
    """
 
123
    global mWTree, mEnabledModules
 
124
 
 
125
    mWTree          = wTree
 
126
    mEnabledModules = prefs.get(__name__, 'enabled_modules', [])
 
127
 
 
128
    for filename in [os.path.splitext(f)[0] for f in os.listdir(mModDir) if f.endswith('.py') and f != '__init__.py']:
 
129
        try:
 
130
            pModule = __import__(filename)
 
131
            # The module name
 
132
            if hasattr(pModule, 'MOD_NAME'): name = getattr(pModule, 'MOD_NAME')
 
133
            else:                            name = '%s %d' % (_('Unnamed module'), len(mModules))
 
134
            # The module description
 
135
            if hasattr(pModule, 'MOD_DESC'): desc = getattr(pModule, 'MOD_DESC')
 
136
            else:                            desc = ''
 
137
            # Is it a mandatory module?
 
138
            if hasattr(pModule, 'MOD_IS_MANDATORY'): isMandatory = getattr(pModule, 'MOD_IS_MANDATORY')
 
139
            else:                                    isMandatory = True
 
140
            # Can it be configured?
 
141
            if hasattr(pModule, 'MOD_IS_CONFIGURABLE'): isConfigurable = getattr(pModule, 'MOD_IS_CONFIGURABLE')
 
142
            else:                                       isConfigurable = False
 
143
            # Does it require special Python modules?
 
144
            if hasattr(pModule, 'MOD_DEPS'): deps = getattr(pModule, 'MOD_DEPS')
 
145
            else:                            deps = []
 
146
            # Should it be instanciated immediately?
 
147
            if isMandatory or name in mEnabledModules:
 
148
                unmetDeps = __checkDeps(deps)
 
149
                if unmetDeps is None: instance = getattr(pModule, filename)(wTree)
 
150
                else:                 instance = None
 
151
            else:
 
152
                instance = None
 
153
            # Ok, add it to the dictionary
 
154
            mModules[name] = [pModule, filename, desc, instance, isMandatory, isConfigurable, deps]
 
155
        except:
 
156
            print _('ERROR: Unable to load module "%s"!') % os.path.join(mModDir, filename)
 
157
            traceback.print_exc()
 
158
    # Remove modules that are no longer there from the list of enabled modules
 
159
    mEnabledModules[:] = [module for module in mEnabledModules if module in mModules]
 
160
    prefs.set(__name__, 'enabled_modules', mEnabledModules)
97
161
    # Start the threaded modules, this has no effect on non-threaded ones
98
 
    for module in mModules:
99
 
        module.start()
 
162
    for module in [m for m in mModules.itervalues() if m[MOD_INSTANCE] is not None]:
 
163
        module[MOD_INSTANCE].start()
 
164
 
 
165
 
 
166
def unload(name):
 
167
    """ Unload the given module """
 
168
    mModulesLock.acquire()
 
169
    data               = mModules[name]
 
170
    instance           = data[MOD_INSTANCE]
 
171
    data[MOD_INSTANCE] = None
 
172
    mModulesLock.release()
 
173
 
 
174
    if instance is None:
 
175
        return
 
176
 
 
177
    mHandlersLock.acquire()
 
178
    # Warn the module that it is going to be unloaded
 
179
    if instance in mHandlers[MSG_EVT_MOD_UNLOADED]:
 
180
        instance.postMsg(MSG_EVT_MOD_UNLOADED, {})
 
181
    # Remove it from all registered handlers
 
182
    for handlers in [h for h in mHandlers.itervalues() if instance in h]:
 
183
        handlers.remove(instance)
 
184
    mHandlersLock.release()
 
185
 
 
186
    mEnabledModules.remove(name)
 
187
    prefs.set(__name__, 'enabled_modules', mEnabledModules)
 
188
 
 
189
 
 
190
def load(name):
 
191
    """ Load the given module, return an error message if not possible or None """
 
192
    mModulesLock.acquire()
 
193
    modData = mModules[name]
 
194
 
 
195
    # Check dependencies
 
196
    unmetDeps = __checkDeps(modData[MOD_DEPS])
 
197
    if unmetDeps is not None:
 
198
        mModulesLock.release()
 
199
        errMsg  = _('The following Python modules are not available:')
 
200
        errMsg += '\n     * '
 
201
        errMsg += '\n     * '.join(unmetDeps)
 
202
        errMsg += '\n\n'
 
203
        errMsg += _('You must install them if you want to enable this module.')
 
204
        return errMsg
 
205
 
 
206
    try:
 
207
        instance              = getattr(modData[MOD_PMODULE], modData[MOD_CLASSNAME])(mWTree)
 
208
        modData[MOD_INSTANCE] = instance
 
209
        mEnabledModules.append(name)
 
210
        prefs.set(__name__, 'enabled_modules', mEnabledModules)
 
211
    except:
 
212
        return traceback.format_exc()
 
213
    finally:
 
214
        mModulesLock.release()
 
215
 
 
216
    instance.start()
 
217
 
 
218
    mHandlersLock.acquire()
 
219
    if instance in mHandlers[MSG_EVT_MOD_LOADED]:
 
220
        instance.postMsg(MSG_EVT_MOD_LOADED, {})
 
221
    mHandlersLock.release()
 
222
 
 
223
    return None
 
224
 
 
225
 
 
226
def getModules():
 
227
    """ Return a copy of all known modules """
 
228
    mModulesLock.acquire()
 
229
    copy = mModules.items()
 
230
    mModulesLock.release()
 
231
 
 
232
    return copy
100
233
 
101
234
 
102
235
def register(module, msgList):
103
236
    """ Register the given module for all messages in the given list """
104
 
    mMutex.acquire()
 
237
    mHandlersLock.acquire()
105
238
    for msg in msgList:
106
 
        if module not in mHandlers[msg]:
107
 
            mHandlers[msg].append(module)
108
 
    mMutex.release()
109
 
 
110
 
 
111
 
def unload(module):
112
 
    """ Unload the given module if it is active """
113
 
    mMutex.acquire()
114
 
    if mModules[module]:
115
 
        for handlers in mHandlers.values():
116
 
            if module in handlers:
117
 
                handlers.remove(module)
118
 
        mModules[module] = False
119
 
    mMutex.release()
 
239
        mHandlers[msg].append(module)
 
240
    mHandlersLock.release()
 
241
 
 
242
 
 
243
def configure(modName, parentDlg):
 
244
    """ Ask the given module to display its configuration dialog """
 
245
    mModulesLock.acquire()
 
246
    mModules[modName][MOD_INSTANCE].configure(parentDlg)
 
247
    mModulesLock.release()
120
248
 
121
249
 
122
250
def __postMsg(msg, params={}):
124
252
        This is the 'real' postMsg() function
125
253
        It MUST be executed in the main GTK loop
126
254
    """
127
 
    mMutex.acquire()
 
255
    mHandlersLock.acquire()
128
256
    for module in mHandlers[msg]:
129
257
        module.postMsg(msg, params)
130
 
    mMutex.release()
 
258
    mHandlersLock.release()
131
259
 
132
260
 
133
261
def __postQuitMsg():
137
265
    """
138
266
    __postMsg(MSG_EVT_APP_QUIT)
139
267
 
140
 
    for module in mModules:
141
 
        module.join()
 
268
    for module in [m for m in mModules.itervalues() if m[MOD_INSTANCE] is not None]:
 
269
        module[MOD_INSTANCE].join()
142
270
 
143
 
    prefsManager.save()
 
271
    prefs.save()
144
272
    gtk.main_quit()
145
273
 
146
274
 
171
299
    """
172
300
        This is the base class for all kinds of modules
173
301
    """
 
302
 
 
303
 
174
304
    def join(self):                    pass
175
305
    def start(self):                   pass
176
306
    def __init__(self):                pass
 
307
    def configure(self, parent):       pass
177
308
    def postMsg(self, msg, params):    pass
178
309
    def handleMsg(self, msg, params):  pass
179
310
 
199
330
        Thread.__init__(self)
200
331
        ModuleBase.__init__(self)
201
332
 
202
 
        self.queue     = []              # List of queued messages
203
 
        self.mutex     = Lock()          # Protect access to the queue
204
 
        self.semaphore = Semaphore(0)    # Block the thread until some messages are available
 
333
        self.queue        = []              # List of queued messages
 
334
        self.mutex        = Lock()          # Protect access to the queue
 
335
        self.semaphore    = Semaphore(0)    # Block the thread until some messages are available
 
336
        self.gtkSemaphore = Semaphore(0)    # Used to execute some code in the GTK main loop
205
337
 
206
338
        # Some messages required by this class
207
 
        register(self, [MSG_EVT_APP_QUIT])
 
339
        register(self, [MSG_EVT_APP_QUIT, MSG_EVT_MOD_UNLOADED])
 
340
 
 
341
 
 
342
    def __gtkExecute(self, func):
 
343
        """ Private function, must be executed in the GTK main loop """
 
344
        func()
 
345
        self.gtkSemaphore.release()
 
346
 
 
347
 
 
348
    def gtkExecute(self, func):
 
349
        """ Execute func in the GTK main loop, and block the execution until done """
 
350
        gobject.idle_add(self.__gtkExecute, func)
 
351
        self.gtkSemaphore.acquire()
208
352
 
209
353
 
210
354
    def postMsg(self, msg, params):
231
375
            self.mutex.release()
232
376
            self.handleMsg(msg, params)
233
377
            # The thread may need to stop
234
 
            if msg == MSG_EVT_APP_QUIT:
 
378
            if msg == MSG_EVT_APP_QUIT or msg == MSG_EVT_MOD_UNLOADED:
235
379
                break