Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

#!/usr/bin/env python 

# -*- coding: utf-8 -*- 

 

############################################################################### 

#  Copyright Kitware Inc. 

# 

#  Licensed under the Apache License, Version 2.0 ( the "License" ); 

#  you may not use this file except in compliance with the License. 

#  You may obtain a copy of the License at 

# 

#    http://www.apache.org/licenses/LICENSE-2.0 

# 

#  Unless required by applicable law or agreed to in writing, software 

#  distributed under the License is distributed on an "AS IS" BASIS, 

#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

#  See the License for the specific language governing permissions and 

#  limitations under the License. 

############################################################################### 

 

""" 

This module contains the Girder events framework. It maintains a global mapping 

of events to listeners, and contains utilities for callers to handle or trigger 

events identified by a name. 

 

Listeners should bind to events by calling: 

 

    girder.events.bind('event.name', 'my.handler', handlerFunction) 

 

And events should be fired in one of two ways; if the event should be handled 

synchronouly, fire it with: 

 

    girder.events.trigger('event.name', info) 

 

And if the event should be handled asynchronously, use: 

 

    girder.events.daemon.trigger('event.name', info, callback) 

 

For obvious reasons, the asynchronous method does not return a value to the 

caller. Instead, the caller may optionally pass the callback argument as a 

function to be called when the task is finished. That callback function will 

receive the Event object as its only argument. 

""" 

 

import copy 

import Queue 

import threading 

import types 

 

from .constants import TerminalColor 

from girder import logger 

 

 

class Event(object): 

    """ 

    An Event object is created when an event is triggered. It is passed to 

    each of the listeners of the event, which have a chance to add information 

    to the event, and also optionally stop the event from being further 

    propagated to other listeners, and also optionally instruct the caller that 

    it should not execute its default behavior. 

    """ 

 

    # We might have a lot of events, so we use __slots__ to make them smaller 

    __slots__ = ( 

        'info', 

        'name', 

        'propagate', 

        'defaultPrevented', 

        'responses', 

        'currentHandlerName' 

    ) 

 

    def __init__(self, name, info): 

        self.name = name 

        self.info = info 

        self.propagate = True 

        self.defaultPrevented = False 

        self.responses = [] 

        self.currentHandlerName = None 

 

    def preventDefault(self): 

        """ 

        This can be used to instruct the triggerer of the event that the default 

        behavior it would normally perform should not be performed. The 

        semantics of this action are specific to the context of the event 

        being handled, but a common use of this method is for a plugin to 

        provide an alternate behavior that will replace the normal way the 

        event is handled by the core system. 

        """ 

        self.defaultPrevented = True 

 

    def stopPropagation(self): 

        """ 

        Listeners should call this on the event they were passed in order to 

        stop any other listeners to the event from being executed. 

        """ 

        self.propagate = False 

 

    def addResponse(self, response): 

        """ 

        Listeners that wish to return data back to the caller who triggered this 

        event should call this to append their own response to the event. 

 

        :param response: The response value, which can be any type. 

        """ 

        self.responses.append(response) 

 

 

class AsyncEventsThread(threading.Thread): 

    """ 

    This class is used to execute the pipeline for events asynchronously. 

    This should not be invoked directly by callers; instead, they should use 

    girder.events.daemon.trigger(). 

    """ 

    def __init__(self): 

        threading.Thread.__init__(self) 

        self.daemon = True 

        self.terminate = False 

        self.eventQueue = Queue.Queue() 

 

    def run(self): 

        """ 

        Loops over all queued events. If the queue is empty, this thread gets 

        put to sleep until someone calls trigger() on it with a new event to 

        dispatch. 

        """ 

        print TerminalColor.info('Started asynchronous event manager thread.') 

 

        while(not self.terminate): 

            eventName, info, callback = self.eventQueue.get(block=True) 

            try: 

                event = trigger(eventName, info) 

                if isinstance(callback, types.FunctionType): 

                    callback(event) 

            except: 

                logger.exception('In handler for event "{}":'.format(eventName)) 

                pass  # Must continue the event loop even if handler failed 

 

        print TerminalColor.info('Stopped asynchronous event manager thread.') 

 

    def trigger(self, eventName, info=None, callback=None): 

        """ 

        Adds a new event on the queue to trigger asynchronously. 

 

        :param eventName: The event name to pass to the girder.events.trigger 

        :param info: The info object to pass to girder.events.trigger 

        """ 

        self.eventQueue.put((eventName, info, callback)) 

 

    def stop(self): 

        """ 

        Gracefully stops this thread. Will finish the currently processing 

        event before stopping. 

        """ 

        self.terminate = True 

 

 

def bind(eventName, handlerName, handler): 

    """ 

    Bind a listener (handler) to the event identified by eventName. It is 

    convention that plugins will use their own name as the handlerName, so that 

    the trigger() caller can see which plugin(s) responded to the event. 

 

    :param eventName: The name that identifies the event. 

    :type eventName: str 

    :param handlerName: The name that identifies the handler calling bind(). 

    :type handlerName: str 

    :param handler: The function that will be called when the event is fired. 

                    It must accept a single argument, which is the Event that 

                    was created by trigger(). This function should not return 

                    a value; any data that it needs to pass back to the 

                    triggerer should be passed via the addResponse() method of 

                    the Event. 

    :type handler: function 

    """ 

    global _mapping 

    if eventName not in _mapping: 

        _mapping[eventName] = {} 

 

    _mapping[eventName][handlerName] = handler 

 

 

def unbind(eventName, handlerName): 

    """ 

    Removes the binding between the event and the given listener. 

 

    :param eventName: The name that identifies the event. 

    :type eventName: str 

    :param handlerName: The name that identifies the handler calling bind(). 

    :type handlerName: str 

    """ 

    global _mapping 

    if eventName in _mapping and handlerName in _mapping[eventName]: 

        del _mapping[eventName][handlerName] 

 

 

def unbindAll(): 

    """ 

    Clears the entire event map. Any bound listeners will be unbound. 

    """ 

    global _mapping 

    _mapping = {} 

 

 

def trigger(eventName, info=None): 

    """ 

    Fire an event with the given name. All listeners bound on that name will be 

    called until they are exhausted or one of the handlers calls the 

    stopPropagation() method on the event. 

 

    :param eventName: The name that identifies the event. 

    :type eventName: str 

    :param info: The info argument to pass to the handler function. The 

                 type of this argument is opaque, and can be anything. 

    """ 

    global _mapping 

    e = Event(eventName, info) 

    for handlerName, handler in _mapping.get(eventName, {}).iteritems(): 

        e.currentHandlerName = handlerName 

        handler(e) 

 

        if e.propagate is False: 

            break 

 

    return e 

 

 

_mapping = {} 

daemon = AsyncEventsThread()