Merged [15040]: Trying some magic: 5 seconds after the last unreachable host is repor...
[adiumx.git] / Source / AdiumContentFiltering.m
blob3b25b467f61e1f27e82954aa16880c5b9316dabe
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "AdiumContentFiltering.h"
19 @interface AdiumContentFiltering (PRIVATE)
20 - (void)_registerContentFilter:(id)inFilter
21                                    filterArray:(NSMutableArray *)inFilterArray;
23 - (NSAttributedString *)_filterAttributedString:(NSAttributedString *)attributedString
24                                                                   contentFilter:(NSArray *)inContentFilterArray
25                                                                   filterContext:(id)filterContext;
27 int filterSort(id<AIContentFilter> filterA, id<AIContentFilter> filterB, void *context);
29 @end
31 @implementation AdiumContentFiltering
33 /*!
34  * @brief Init
35  */
36 - (id)init
38         if((self = [super init])){
39                 stringsRequiringPolling = [[NSMutableSet alloc] init];
40                 delayedFilteringDict = [[NSMutableDictionary alloc] init];
41         }
42         
43         return(self);
46 /*!
47  * @brief Dealloc
48  */
49 - (void)dealloc
50 {       
51         [super dealloc];
55 //Content Filtering ----------------------------------------------------------------------------------------------------
56 #pragma mark Content Filtering
58  * @brief Register a content filter.
59  *
60  * If the particular filter wants to apply to multiple types or directions, it should register multiple times.
61  */
62 - (void)registerContentFilter:(id<AIContentFilter>)inFilter
63                                            ofType:(AIFilterType)type
64                                         direction:(AIFilterDirection)direction
66         NSParameterAssert(type >= 0 && type < FILTER_TYPE_COUNT);
67         NSParameterAssert(direction >= 0 && direction < FILTER_DIRECTION_COUNT);
69         if (!contentFilter[type][direction]) {
70                 contentFilter[type][direction] = [[NSMutableArray alloc] init];
71         }
72         
73         [self _registerContentFilter:inFilter
74                                          filterArray:contentFilter[type][direction]];
78  * @brief Register a delayed content filter
79  *
80  * Delayed content filters return YES or NO from their filter method; YES means they began a filtering process.
81  * When finished, the filter is responsible for notifying this class that the attributed string is ready.
82  * A unique ID will be passed to identify each string.
83  */
84 - (void)registerDelayedContentFilter:(id<AIDelayedContentFilter>)inFilter
85                                                           ofType:(AIFilterType)type
86                                                    direction:(AIFilterDirection)direction
88         NSParameterAssert(type >= 0 && type < FILTER_TYPE_COUNT);
89         NSParameterAssert(direction >= 0 && direction < FILTER_DIRECTION_COUNT);
91         if (!delayedContentFilter[type][direction]) {
92                 delayedContentFilter[type][direction] = [[NSMutableArray alloc] init];
93         }
94         
95         [self _registerContentFilter:inFilter
96                                          filterArray:delayedContentFilter[type][direction]];
100  * @brief Add a content filter to the specified array
102  * Adds, then sorts by priority
103  */
104 - (void)_registerContentFilter:(id)inFilter
105                                    filterArray:(NSMutableArray *)inFilterArray
107         NSParameterAssert(inFilter != nil);
108         
109         [inFilterArray addObject:inFilter];
110         [inFilterArray sortUsingFunction:filterSort context:nil];       
114  * @brief Unregister a filter.
116  * Looks in both contentFilter and delayedContentFilter, for all types and directions
117  */
118 - (void)unregisterContentFilter:(id<AIContentFilter>)inFilter
120         NSParameterAssert(inFilter != nil);
122         int i, j;
123         for (i = 0; i < FILTER_TYPE_COUNT; i++) {
124                 for (j = 0; j < FILTER_DIRECTION_COUNT; j++) {
125                         [contentFilter[i][j] removeObject:inFilter];
126                         [delayedContentFilter[i][j] removeObject:inFilter];
127                 }
128         }
132  * @brief Register a string to be filtered which requires polling to be updated
133  */
134 - (void)registerFilterStringWhichRequiresPolling:(NSString *)inPollString
136         [stringsRequiringPolling addObject:inPollString];
140  * @brief Is polling required to update the passed string?
141  */
142 - (BOOL)shouldPollToUpdateString:(NSString *)inString
144         NSEnumerator    *enumerator;
145         NSString                *stringRequiringPolling;
146         BOOL                    shouldPoll = NO;
147         
148         enumerator = [stringsRequiringPolling objectEnumerator];
149         while ((stringRequiringPolling = [enumerator nextObject])) {
150                 if ([inString rangeOfString:stringRequiringPolling].location != NSNotFound) {
151                         shouldPoll = YES;
152                         break;
153                 }
154         }
155         
156         return shouldPoll;
160  * @brief Filter an attributed string immediately
162  * This does not perform delayed filters.
164  * @param attributedString NSAttributedString to filter
165  * @param type Type of the filter
166  * @param direction Direction of the filter
167  * @param filterContext A object, such as an AIListContact or an AIAccount, used as context by filters
168  * @result The filtered attributed string, which may be the same as attributedString
169  */
170 - (NSAttributedString *)filterAttributedString:(NSAttributedString *)attributedString
171                                                            usingFilterType:(AIFilterType)type
172                                                                          direction:(AIFilterDirection)direction
173                                                                            context:(id)filterContext
175         attributedString = [self _filterAttributedString:attributedString
176                                                                            contentFilter:contentFilter[type][direction]
177                                                                            filterContext:filterContext];
178         
179         return attributedString;
184  * @brief Perform the filtering of an attributedString on the specified content filter.
186  * @param attributedString NSAttributedString to filter
187  * @param inContentFilterArray Array of filters to use
188  * @param filtercontext Passed to each filter as context.
189  * @result The filtered NSAttributedString, which may be the same as attributedString
190  */
191 - (NSAttributedString *)_filterAttributedString:(NSAttributedString *)attributedString
192                                                                   contentFilter:(NSArray *)inContentFilterArray
193                                                                   filterContext:(id)filterContext
195         NSEnumerator            *enumerator = [inContentFilterArray objectEnumerator];
196         id<AIContentFilter>     filter;
197         
198         while ((filter = [enumerator nextObject])) {
199                 attributedString = [filter filterAttributedString:attributedString context:filterContext];
200         }
201         
202         return attributedString;
206  * @brief Begin delayed filtering of an attributedString
208  * @result YES if any delayed filtering began; NO if it did not
209  */
210 - (BOOL)_delayedFilterAttributedString:(NSAttributedString *)attributedString
211                                                  contentFilter:(NSArray *)inContentFilterArray
212                                                  filterContext:(id)filterContext
213                                  uniqueDelayedFilterID:(unsigned long long)uniqueID
215         NSEnumerator                            *enumerator = [inContentFilterArray objectEnumerator];
216         id<AIDelayedContentFilter>      filter;
217         BOOL                                            beganDelayedFiltering = NO;
218         
219         //Break as soon as we begin delayed filtering; we'll be back through here when that filtering is done
220         while ((filter = [enumerator nextObject]) && !beganDelayedFiltering) {
221                 beganDelayedFiltering = [filter delayedFilterAttributedString:attributedString 
222                                                                                                                           context:filterContext
223                                                                                                                          uniqueID:uniqueID];
224         }
225         
226         return beganDelayedFiltering;   
230  * @brief Filter an attributed string, notifying a target when complete
232  * This performs delayed filters, which means there may be a non-blocking delay before the filtered attributed string
233  * is returned.
235  * @param attributedString NSAttributedString to filter
236  * @param type Type of the filter
237  * @param direction Direction of the filter
238  * @param filterContext A object, such as an AIListContact or an AIAccount, used as context by filters
239  * @param target Target to notify when filtering is complete
240  * @param selector Selector to call on target.  It should take 2 arguments; the first will be the filtered attributedString; the second is the passed context.
241  * @param context Context passed back to target via selector when filtering is complete
242  * @result The filtered attributed string, which may be the same as attributedString
243  */
244 - (void)filterAttributedString:(NSAttributedString *)attributedString
245                            usingFilterType:(AIFilterType)type
246                                          direction:(AIFilterDirection)direction
247                                  filterContext:(id)filterContext
248                            notifyingTarget:(id)target
249                                           selector:(SEL)selector
250                                            context:(id)context
252         NSParameterAssert(type >= 0 && type < FILTER_TYPE_COUNT);
253         NSParameterAssert(direction >= 0 && direction < FILTER_DIRECTION_COUNT);
255         BOOL                            shouldDelay = NO;
256         NSInvocation            *invocation;
257         
258         //Set up the invocation
259         invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
260         [invocation setSelector:selector];
261         [invocation setTarget:target];
262         [invocation setArgument:&context atIndex:3]; //context, the second argument after the two hidden arguments of every NSInvocation
264         if (attributedString) {
265                 static unsigned long long       uniqueDelayedFilterID = 0;
266                 
267                 //Perform the main filters
268                 attributedString = [self _filterAttributedString:attributedString
269                                                                                    contentFilter:contentFilter[type][direction]
270                                                                                    filterContext:filterContext];
272                 //Now perform the delayed filters
273                 shouldDelay = [self _delayedFilterAttributedString:attributedString
274                                                                                          contentFilter:delayedContentFilter[type][direction]
275                                                                                          filterContext:filterContext
276                                                                          uniqueDelayedFilterID:uniqueDelayedFilterID];
278                 //If we should delay (a delayed filter is doing its thing), store what we need to finish later
279                 if (shouldDelay) {
280                         //NSInvocation does not retain its arguments by default; if we're caching the invocation, we must tell it to.
281                         [invocation retainArguments];
283                         //Track this so we can invoke with the filtered product later
284                         [delayedFilteringDict setObject:[NSDictionary dictionaryWithObjectsAndKeys:
285                                 invocation, @"Invocation",
286                                 delayedContentFilter[type][direction], @"Delayed Content Filter",
287                                 filterContext, @"Filter Context", nil]
288                                                                          forKey:[NSNumber numberWithUnsignedLongLong:uniqueDelayedFilterID]];
289                 }
291                 //Increment our delayed filter ID
292                 uniqueDelayedFilterID++;
293         }
294         
295         //If we didn't delay, invoke immediately
296         if (!shouldDelay) {
297                 //Put that attributed string into the invocation as the first argument after the two hidden arguments of every NSInvocation
298                 [invocation setArgument:&attributedString atIndex:2];
299                 
300                 //Send the filtered attributedString back via the invocation
301                 [invocation invoke];
302         }
306  * @brief A delayed filter finished filtering
308  * After this filter finishes, run it through the delayed filter system again
309  * to hit the next delayed string, if necessary.
311  * If no more delayed filtering is needed, look up the invocation and pass the
312  * now-finished string to the appropriate target.
313  */
314 - (void)delayedFilterDidFinish:(NSAttributedString *)attributedString uniqueID:(unsigned long long)uniqueID
316         NSNumber                *uniqueIDNumber;
317         NSDictionary    *infoDict;
318         BOOL                    shouldDelay;
319         
320         uniqueIDNumber = [NSNumber numberWithUnsignedLongLong:uniqueID];
321         infoDict = [delayedFilteringDict objectForKey:uniqueIDNumber];
322         
323         //Run through the delayed filters again, since a delayed filter would stop after the first hit
324         shouldDelay = [self _delayedFilterAttributedString:attributedString
325                                                                                  contentFilter:[infoDict objectForKey:@"Delayed Content Filter"]
326                                                                                  filterContext:[infoDict objectForKey:@"Filter Context"]
327                                                                  uniqueDelayedFilterID:uniqueID];
328         
329         //If we no longer need to delay, set up the invocation and invoke it
330         if (!shouldDelay) {
331                 NSInvocation    *invocation = [infoDict objectForKey:@"Invocation"];
333                 //Put that attributed string into the invocation as the first argument after the two hidden arguments of every NSInvocation
334                 [invocation setArgument:&attributedString atIndex:2];
336                 //Send the filtered attributedString back via the invocation
337                 [invocation invoke];
339                 //No further need for the infoDict from delayedFilteringDict
340                 [delayedFilteringDict removeObjectForKey:uniqueIDNumber];
341         }
344 #pragma mark Filter priority sort
345 int filterSort(id<AIContentFilter> filterA, id<AIContentFilter> filterB, void *context)
347         float filterPriorityA = [filterA filterPriority];
348         float filterPriorityB = [filterB filterPriority];
349         
350         if (filterPriorityA < filterPriorityB)
351                 return NSOrderedAscending;
352         else if (filterPriorityA > filterPriorityB)
353                 return NSOrderedDescending;
354         else
355                 return NSOrderedSame;
358 @end