| 15 | | """Yields a list of realms that it is able to handle |
|---|
| 16 | | subscriptions for.""" |
|---|
| 17 | | |
|---|
| | 19 | """Returns an iterable that lists all the realms that this subscriber |
|---|
| | 20 | is capable of handling subscriptions for. |
|---|
| | 21 | |
|---|
| | 22 | Although these usually correspond to realms within Trac, there is no |
|---|
| | 23 | actual requirement for that. Conspiracy between a specialied |
|---|
| | 24 | producer, subscriber and formatter could result in messages about all |
|---|
| | 25 | kinds of things not directly relatable to Trac resources. |
|---|
| | 26 | |
|---|
| | 27 | If a single realm is handled, use 'yield' instead of 'return'.""" |
|---|
| | 28 | |
|---|
| 28 | | ('method', 'username', 'format') |
|---|
| 29 | | |
|---|
| 30 | | The method should correspond to a distribution method that is |
|---|
| 31 | | provided by an IAnnouncementDistributor component. The |
|---|
| 32 | | destination itself varies depending on the method; if the |
|---|
| 33 | | method is 'email', the destination will be an email address. |
|---|
| | 40 | ('transport', 'name', 'address') |
|---|
| | 41 | |
|---|
| | 42 | The transport should be one that a distributor (and formatter) can |
|---|
| | 43 | handle, but if not? The events will be dropped later at the |
|---|
| | 44 | appropriate stage. |
|---|
| | 45 | |
|---|
| | 46 | A subscriber must return at least the name or the address, but it |
|---|
| | 47 | doesn't have to return both. In many cases returning both is |
|---|
| | 48 | actually undesirable-- in such a case resolvers will be bypassed |
|---|
| | 49 | entirely. |
|---|
| 37 | | def get_format_scheme(): |
|---|
| 38 | | """email""" |
|---|
| 39 | | |
|---|
| 40 | | def get_format_realms(scheme): |
|---|
| 41 | | """Yields a list of realms.""" |
|---|
| 42 | | |
|---|
| 43 | | def get_format_styles(scheme, realm): |
|---|
| 44 | | """Yields a list of (format, weight)""" |
|---|
| 45 | | |
|---|
| 46 | | def format(format, event): |
|---|
| 47 | | """Returns the event rendered according to the appropriate |
|---|
| 48 | | method. |
|---|
| | 53 | """Formatters are responsible for converting an event into a message |
|---|
| | 54 | appropriate for a given transport. |
|---|
| | 55 | |
|---|
| | 56 | For transports like 'aim' or 'irc', this may be a short summary of a |
|---|
| | 57 | change. For 'email', it may be a plaintext or html overview of all |
|---|
| | 58 | the changes and perhaps the existing state. |
|---|
| | 59 | |
|---|
| | 60 | It's up to a formatter to determine what ends up ultimately being sent |
|---|
| | 61 | to the end-user. It's capable of pulling data out of the target object |
|---|
| | 62 | that wasn't changed, picking and choosing details for whatever reason. |
|---|
| | 63 | |
|---|
| | 64 | Since a formatter must be intimately familiar with the realm that |
|---|
| | 65 | originated the event, formatters are tied to specific transport + realm |
|---|
| | 66 | combinations. This means there may be a proliferation of formatters as |
|---|
| | 67 | options expand. |
|---|
| | 68 | """ |
|---|
| | 69 | |
|---|
| | 70 | def get_format_transport(): |
|---|
| | 71 | """Returns an iterable of the transports this formatter is capable of |
|---|
| | 72 | handling. |
|---|
| | 73 | |
|---|
| | 74 | If a single item is to be returned, use yield instead of return.""" |
|---|
| | 75 | |
|---|
| | 76 | def get_format_realms(transport): |
|---|
| | 77 | """Returns an iterable of realms that this formatter knows how to |
|---|
| | 78 | handle for the specified transport. |
|---|
| | 79 | |
|---|
| | 80 | If a single item is to be returned, use yield instead of return.""" |
|---|
| | 81 | |
|---|
| | 82 | def get_format_styles(transport, realm): |
|---|
| | 83 | """Returns an iterable of styles that this formatter supports for |
|---|
| | 84 | a specified transport and realm. |
|---|
| | 85 | |
|---|
| | 86 | Many formatters may simply return a single style and never have more; |
|---|
| | 87 | that's fine. But if its useful to encapsulate code for several similar |
|---|
| | 88 | styles a formatter can handle more then one. For example, 'plaintext' |
|---|
| | 89 | and 'html' may be useful variants the same formatter handles. |
|---|
| | 90 | |
|---|
| | 91 | Style names should be distinct between formatters for a specific |
|---|
| | 92 | transport. |
|---|
| | 93 | |
|---|
| | 94 | If a single item is to be returned, use yield instead of return.""" |
|---|
| | 95 | |
|---|
| | 96 | def format(transport, realm, style, event): |
|---|
| | 97 | """Converts the event into the specified style. If the transport or |
|---|
| | 98 | realm passed into this method are not ones this formatter can handle, |
|---|
| | 99 | it should return silently and without error. |
|---|
| | 100 | |
|---|
| | 101 | The exact return type of this method is intentionally undefined. It |
|---|
| | 102 | will be whatever the distributor that it is designed to work with |
|---|
| | 103 | expects. |
|---|
| 52 | | def get_distribution_scheme(): |
|---|
| 53 | | """Yields a list of methods that this distributor knows how |
|---|
| 54 | | to use to deliver an event announcement. |
|---|
| 55 | | """ |
|---|
| 56 | | |
|---|
| 57 | | def distribute(destinations, message): |
|---|
| 58 | | """Distributes an actual message.""" |
|---|
| | 107 | """The Distributor is responsible for actually delivering an event to the |
|---|
| | 108 | desired subscriptions. |
|---|
| | 109 | |
|---|
| | 110 | A distributor should attempt to avoid blocking; using subprocesses is |
|---|
| | 111 | preferred to threads. |
|---|
| | 112 | |
|---|
| | 113 | Each distributor handles a single transport, and only one distributor |
|---|
| | 114 | in the system should handle that. For example, there should not be |
|---|
| | 115 | two distributors for the 'email' transport. |
|---|
| | 116 | """ |
|---|
| | 117 | |
|---|
| | 118 | def get_distribution_transports(): |
|---|
| | 119 | """Returns a string representing the transport supported.""" |
|---|
| | 120 | |
|---|
| | 121 | def distribute(transport, recipients, event): |
|---|
| | 122 | """This method is meant to actually distribute the event to the |
|---|
| | 123 | specified recipients, over the specified transport. |
|---|
| | 124 | |
|---|
| | 125 | If it is passed a transport it does not support, it should return |
|---|
| | 126 | silently and without error. |
|---|
| | 127 | |
|---|
| | 128 | The recipients is a list of (name, address) pairs with either (but not |
|---|
| | 129 | both) being allowed to be None. If name is provided but address isn't, |
|---|
| | 130 | then the distributor should defer to IAnnouncementAddressResolver |
|---|
| | 131 | implementations to determine what the address should be. |
|---|
| | 132 | |
|---|
| | 133 | If the name is None but the address is not, then the distributor |
|---|
| | 134 | should rely on the address being correct and use it-- if possible. |
|---|
| | 135 | |
|---|
| | 136 | The distributor may initiate as many transactions as are necessecary |
|---|
| | 137 | to deliver a message, but should use as few as possible; for example |
|---|
| | 138 | in the EmailDistributor, if all of the recipients are receiving a |
|---|
| | 139 | plain text form of the message, a single message with many BCC's |
|---|
| | 140 | should be used. |
|---|
| | 141 | |
|---|
| | 142 | The distributor is responsible for determining which of the |
|---|
| | 143 | IAnnouncementFormatters should get the privilege of actually turning |
|---|
| | 144 | an event into content. In cases where multiple formatters are capable |
|---|
| | 145 | of converting an event into a message for a given transport, a |
|---|
| | 146 | user preference would be a dandy idea. |
|---|
| | 147 | """ |
|---|
| | 150 | """Represents a single 'box' in the Announcements preference panel. |
|---|
| | 151 | |
|---|
| | 152 | Any component can always implement IPreferencePanelProvider to get |
|---|
| | 153 | preferences from users, of course. However, considering there may be |
|---|
| | 154 | several components related to the Announcement system, and many may |
|---|
| | 155 | have different preferences for a user to set, that would clutter up |
|---|
| | 156 | the preference interfac quite a bit. |
|---|
| | 157 | |
|---|
| | 158 | The IAnnouncementPreferenceProvider allows several boxes to be |
|---|
| | 159 | chained in the same panel to group the preferenecs related to the |
|---|
| | 160 | Announcement System. |
|---|
| | 161 | |
|---|
| | 162 | Implementing announcement preference boxes should be essentially |
|---|
| | 163 | identical to implementing entire panels. |
|---|
| | 164 | """ |
|---|
| | 165 | |
|---|
| 62 | | """Returns (name, label)""" |
|---|
| 63 | | |
|---|
| 64 | | def render_announcement_preference_box(req, panel): |
|---|
| 65 | | """Returns (template, data)""" |
|---|
| | 167 | """Accepts a request object, and returns an iterable of |
|---|
| | 168 | (name, label) pairs; one for each box that the implementation |
|---|
| | 169 | can generate. |
|---|
| | 170 | |
|---|
| | 171 | If a single item is returned, be sure to 'yield' it instead of |
|---|
| | 172 | returning it.""" |
|---|
| | 173 | |
|---|
| | 174 | def render_announcement_preference_box(req, box): |
|---|
| | 175 | """Accepts a request object, and the name (as from the previous |
|---|
| | 176 | method) of the box that should be rendered. |
|---|
| | 177 | |
|---|
| | 178 | Returns a tuple of (template, data) with the template being a |
|---|
| | 179 | filename in a directory provided by an ITemplateProvider which |
|---|
| | 180 | shall be rendered into a single <div> element, when combined |
|---|
| | 181 | with the data member. |
|---|
| | 182 | """ |
|---|
| 69 | | """Returns an address or name.""" |
|---|
| | 188 | """Accepts a session name, and returns an address. |
|---|
| | 189 | |
|---|
| | 190 | This address explicitly does not always have to mean an email address, |
|---|
| | 191 | nor does it have to be an address stored within the Trac system at |
|---|
| | 192 | all. |
|---|
| | 193 | |
|---|
| | 194 | Implementations of this interface are never 'detected' automatically, |
|---|
| | 195 | and must instead be specifically named for a particular distributor. |
|---|
| | 196 | This way, some may find email addresses (for EmailDistributor), and |
|---|
| | 197 | others may find AIM screen name. |
|---|
| | 198 | |
|---|
| | 199 | If no address for the specified name can be found, None should be |
|---|
| | 200 | returned. The next resolver will be attempted in the chain. |
|---|
| | 201 | """ |
|---|
| | 225 | """AnnouncementSystem represents the entry-point into the announcement |
|---|
| | 226 | system, and is also the central controller that handles passing notices |
|---|
| | 227 | around. |
|---|
| | 228 | |
|---|
| | 229 | An announcement begins when something-- an announcement provider-- |
|---|
| | 230 | constructs an AnnouncementEvent (or subclass) and calls the send method |
|---|
| | 231 | on the AnnouncementSystem. |
|---|
| | 232 | |
|---|
| | 233 | Every event is classified by two required fields-- realm and category. |
|---|
| | 234 | In general, the realm corresponds to the realm of a Resource within Trac; |
|---|
| | 235 | ticket, wiki, milestone, and such. This is not a requirement, however. |
|---|
| | 236 | Realms can be anything distinctive-- if you specify novel realms to solve |
|---|
| | 237 | a particular problem, you'll simply also have to specify subscribers and |
|---|
| | 238 | formatters who are able to deal with data in those realms. |
|---|
| | 239 | |
|---|
| | 240 | The other classifier is a category that is defined by the providers and |
|---|
| | 241 | has no particular meaning; for the providers that implement the |
|---|
| | 242 | I*ChangeListener interfaces, the categories will often correspond to the |
|---|
| | 243 | kinds of events they receive. For tickets, they would be 'created', |
|---|
| | 244 | 'changed' and 'deleted'. |
|---|
| | 245 | |
|---|
| | 246 | There is no requirement for an event to have more then realm and category |
|---|
| | 247 | to classify an event, but if more is provided in a subclass that the |
|---|
| | 248 | subscribers can use to pick through events, all power to you. |
|---|
| | 249 | """ |
|---|
| 142 | | supported_subscribers = [] |
|---|
| 143 | | for sp in self.subscribers: |
|---|
| 144 | | categories = sp.get_subscription_categories(evt.realm) |
|---|
| 145 | | if ('*' in categories) or (evt.category in categories): |
|---|
| 146 | | supported_subscribers.append(sp) |
|---|
| 147 | | |
|---|
| 148 | | self.log.debug( |
|---|
| 149 | | "AnnouncementSystem found the following subscribers capable of " |
|---|
| 150 | | "handling '%s, %s': %s" % (evt.realm, evt.category, |
|---|
| 151 | | ', '.join([ss.__class__.__name__ for ss in supported_subscribers])) |
|---|
| 152 | | ) |
|---|
| 153 | | |
|---|
| 154 | | subscriptions = set() |
|---|
| 155 | | for sp in supported_subscribers: |
|---|
| 156 | | subscriptions.update( |
|---|
| 157 | | x for x in sp.check_event(evt) if x |
|---|
| | 309 | """Accepts a single AnnouncementEvent instance (or subclass), and |
|---|
| | 310 | returns nothing. |
|---|
| | 311 | |
|---|
| | 312 | There is no way (intentionally) to determine what the |
|---|
| | 313 | AnnouncementSystem did with a particular event besides looking through |
|---|
| | 314 | the debug logs. |
|---|
| | 315 | """ |
|---|
| | 316 | |
|---|
| | 317 | try: |
|---|
| | 318 | |
|---|
| | 319 | supported_subscribers = [] |
|---|
| | 320 | for sp in self.subscribers: |
|---|
| | 321 | categories = sp.get_subscription_categories(evt.realm) |
|---|
| | 322 | if ('*' in categories) or (evt.category in categories): |
|---|
| | 323 | supported_subscribers.append(sp) |
|---|
| | 324 | |
|---|
| | 325 | self.log.debug( |
|---|
| | 326 | "AnnouncementSystem found the following subscribers capable of " |
|---|
| | 327 | "handling '%s, %s': %s" % (evt.realm, evt.category, |
|---|
| | 328 | ', '.join([ss.__class__.__name__ for ss in supported_subscribers])) |
|---|
| 175 | | for distributor in self.distributors: |
|---|
| 176 | | scheme = distributor.get_distribution_scheme() |
|---|
| 177 | | if scheme in packages: |
|---|
| 178 | | distributor.distribute(scheme, packages[scheme], evt) |
|---|
| 179 | | |
|---|
| 180 | | return |
|---|
| | 352 | for distributor in self.distributors: |
|---|
| | 353 | transport = distributor.get_distribution_transport() |
|---|
| | 354 | if transport in packages: |
|---|
| | 355 | distributor.distribute(transport, packages[transport], evt) |
|---|
| | 356 | except: |
|---|
| | 357 | self.log.error("AnnouncementSystem failed.", exc_info=True) |
|---|