root/Trunk/AILogSizeSort.m

Revision 28, 9.4 KB (checked in by jon, 13 years ago)

Modified the sort function to not crash when sorting groups.

  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Adium is the legal property of its developers, whose names are listed in the copyright file included
5 * with this source distribution.
6 *
7 * This plugin is copyright (c) 2008 Jon Chambers.  The plugin's official site is:
8 * http://projects.eatthepath.com/sort-by-log-size-plugin/
9 *
10 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License as published by the Free Software Foundation; either version 2 of the License,
12 * or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
15 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
16 * Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along with this program; if not,
19 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 */
21
22#import <Adium/AIPlugin.h>
23#import <Adium/AISharedAdium.h>
24
25#import <AIUtilities/AIStringUtilities.h>
26
27#import <Adium/AIAdiumProtocol.h>
28#import <Adium/AIContentControllerProtocol.h>
29#import <Adium/AIContactControllerProtocol.h>
30#import <Adium/AIListObject.h>
31#import <Adium/AIMetaContact.h>
32
33#import <Adium/AIChat.h>
34#import <Adium/AIContentObject.h>
35#import <Adium/AIContentMessage.h>
36
37#import "AILogSizeSort.h"
38#import "AILoggerPlugin.h"
39
40@implementation AILogSizeSort
41
42#pragma mark AISortController obligations
43
44/*!
45 * @brief Did become active first time
46 *
47 * Called only once; gives the sort controller an opportunity to set defaults and load preferences lazily.
48 */
49- (void)didBecomeActiveFirstTime
50{
51        logSizeCache = [[NSMutableDictionary alloc] init];
52       
53        // Listen for content addition notifications
54        [[NSNotificationCenter defaultCenter] addObserver:self
55                                                                                         selector:@selector(contentObjectAdded:)
56                                                                                                 name:Content_ContentObjectAdded
57                                                                                           object:nil];
58}
59
60/*!
61 * @brief Non-localized identifier
62 */
63- (NSString *)identifier{
64    return @"Log size";
65}
66
67/*!
68 * @brief Localized display name
69 */
70- (NSString *)displayName{
71    return AILocalizedString(@"Sort Contacts by Log Size", nil);
72}
73
74/*!
75 * @brief Properties which, when changed, should trigger a resort
76 */
77- (NSSet *)statusKeysRequiringResort{
78        return nil;
79}
80
81/*!
82 * @brief Attribute keys which, when changed, should trigger a resort
83 */
84- (NSSet *)attributeKeysRequiringResort{
85        return nil;
86}
87
88#pragma mark Configuration
89
90/*!
91 * @brief Window title when configuring the sort
92 *
93 * Subclasses should provide a title for configuring the sort only if configuration is possible.
94 * @result Localized title. If nil, the menu item will be disabled.
95 */
96- (NSString *)configureSortWindowTitle{
97        return nil;
98}
99
100/*!
101 * @brief Nib name for configuration
102 */
103- (NSString *)configureNibName{
104        return nil;
105}
106
107/*!
108 * @brief View did load
109 */
110- (void)viewDidLoad{
111}
112
113/*!
114 * @brief Preference changed
115 *
116 * Sort controllers should live update as preferences change.
117 */
118- (IBAction)changePreference:(id)sender
119{
120}
121
122/*!
123 * @brief Allow users to manually sort groups
124 */
125-(BOOL)canSortManually
126{
127        return YES;
128}
129
130#pragma mark Cache operations
131
132/*!
133 * @brief Creates and populates a new cache entry for a contact if needed
134 */
135-(void)createCacheEntryIfNil:(AIListContact *)listContact
136{
137        // Check for a match on the account name; create a new sub-dictionary if needed
138        if([logSizeCache valueForKey:[[listContact account] explicitFormattedUID]] == nil)
139        {
140                [logSizeCache setValue:[[NSMutableDictionary alloc] init] forKey:[[listContact account] explicitFormattedUID]];
141        }
142       
143        NSMutableDictionary *accountDictionary = [logSizeCache valueForKey:[[listContact account] explicitFormattedUID]];
144       
145        // If we don't already have a valid log size cached for this contact, create one
146        if([accountDictionary valueForKey:[listContact UID]] == nil)
147        {
148                [accountDictionary setValue:[NSNumber numberWithUnsignedLongLong:[AILogSizeSort getContactLogSize:listContact]] forKey: [listContact UID]];
149        }
150}
151
152/*!
153 * @brief Invalidates a cached log size for a list contact
154 */
155-(void)removeCacheEntry:(AIListContact *)listContact
156{
157        if([listContact isMemberOfClass:[AIMetaContact class]])
158        {
159                // Recurse!  Invalidate each sub-contact's cache entry.
160                id contact;
161               
162                NSEnumerator *contactEnumerator = [[(AIMetaContact *)listContact uniqueContainedObjects] objectEnumerator];
163               
164                while(contact = [contactEnumerator nextObject])
165                {
166                        [self removeCacheEntry:contact];
167                }
168        }
169        else
170        {
171                // Bail out if we don't know about the group this contact is in (there's nothing for us to do
172                // anyway).
173                if([logSizeCache valueForKey:[[listContact account] explicitFormattedUID]] == nil) { return; }
174               
175                // Remove the cache entry for the dirty account.
176                [(NSMutableDictionary *)[logSizeCache valueForKey:[[listContact account] explicitFormattedUID]] setValue:nil forKey:[listContact UID]];
177        }
178}
179
180#pragma mark Event handlers
181
182/*!
183 * @brief Handles content send/receive events
184 *
185 * Handles content send/receive events.  For one-on-one chats, the cached log size for a contact is
186 * invalidated (forcing a recalculation on the next sorting cycle).
187 */
188-(void)contentObjectAdded:(NSNotification *)notification
189{
190        AIChat *chat = [notification object];
191       
192        if(![chat isGroupChat])
193        {
194                [self removeCacheEntry:[chat listObject]];
195        }
196}
197
198#pragma mark Log operations
199
200/*!
201 * @brief Returns the cached log size for a list contact
202 */
203-(unsigned long long)getCachedLogSize:(AIListContact *)listContact
204{
205        [self createCacheEntryIfNil:listContact];
206       
207        NSMutableDictionary *accountDictionary = [logSizeCache valueForKey:[[listContact account] explicitFormattedUID]];
208        return [[accountDictionary valueForKey:[listContact UID]] unsignedLongLongValue];
209}
210
211/*!
212 * @brief Returns the total aggregate log size for a contact
213 *
214 * Returns the total aggregate log size for a contact.  For meta-contacts, the
215 * total log file size of all sub-contacts is returned.  If no log exists or if
216 * something else goes wrong, 0 is returned.
217 *
218 * @param listContact an AIListContact for which to retrieve a total log file size
219 * @return the total log file size in bytes or 0 if an error occurred
220 */
221+(unsigned long long)getContactLogSize:(AIListContact *)listContact
222{
223        NSFileManager *fileManager = [NSFileManager defaultManager];
224       
225        if([listContact isMemberOfClass:[AIMetaContact class]])
226        {
227                // Recurse through all sub-contacts
228                id contact;
229                unsigned long long size = 0;
230               
231                NSEnumerator *contactEnumerator = [[(AIMetaContact *)listContact uniqueContainedObjects] objectEnumerator];
232
233                while(contact = [contactEnumerator nextObject])
234                {
235                        size += [AILogSizeSort getContactLogSize:contact];
236                }
237               
238                return size;
239        }
240        else
241        {
242                // Find the path to the directory containing the log files for this contact
243                NSString *path = [[AILoggerPlugin logBasePath] stringByAppendingPathComponent:[AILoggerPlugin relativePathForLogWithObject:[listContact UID] onAccount: [listContact account]]];
244               
245                // Grab an enumerator for all log files for this contact
246                NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
247                NSString *file;
248               
249                unsigned long long size = 0;
250               
251                while(file = [dirEnum nextObject])
252                {
253                        NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:[path stringByAppendingPathComponent:file] traverseLink:YES];
254                       
255                        if (fileAttributes != nil)
256                        {
257                                NSNumber *fileSize;
258                                if(fileSize = [fileAttributes objectForKey:NSFileSize])
259                                {
260                                        size += [fileSize unsignedLongLongValue];
261                                }
262                        }
263                }
264               
265                return size;
266        }
267}
268
269#pragma mark Sorting
270
271/*!
272 * @brief Sort by log size
273 */
274NSComparisonResult logSizeSort(id objectA, id objectB, BOOL groups, id<AIContainingObject> container)
275{
276        // Borrowed from AISortController.m
277        BOOL objectAIsGroup = [objectA isKindOfClass:[AIListGroup class]];
278        BOOL objectBIsGroup = [objectB isKindOfClass:[AIListGroup class]];
279       
280        if(groups || (objectAIsGroup && objectBIsGroup))
281        {
282                // Keep groups in manual order (borrowed from ESStatusSort)
283                if ([container orderIndexForObject:objectA] > [container orderIndexForObject:objectB])
284                {
285                        return NSOrderedDescending;
286                }
287                else
288                {
289                        return NSOrderedAscending;
290                }
291        }
292       
293        // Catch cases where only one of the objects is a group
294        if(objectAIsGroup && !objectBIsGroup)
295        {
296                return NSOrderedAscending;
297        }
298        else if(!objectAIsGroup && objectBIsGroup)
299        {
300                return NSOrderedDescending;
301        }
302       
303        // Get a reference to one and only AILogSizeSort instance.  If this sorting method is being
304        // called, it should always be the case that AILogSizeSort is the active sort controller.
305        AISortController *sortController = [AISortController activeSortController];
306       
307        unsigned long long sizeA = 0;
308        unsigned long long sizeB = 0;
309       
310        // Attempt to use cached log sizes if possible, but fall back to dumber methods if something
311        // unforeseen happens.
312        if([sortController isMemberOfClass:[AILogSizeSort class]])
313        {
314                sizeA = [(AILogSizeSort *)sortController getCachedLogSize:objectA];
315                sizeB = [(AILogSizeSort *)sortController getCachedLogSize:objectB];
316        }
317        else
318        {
319                sizeA = [AILogSizeSort getContactLogSize:objectA];
320                sizeB = [AILogSizeSort getContactLogSize:objectB];
321        }
322
323        if(sizeB == sizeA)
324        {
325                // Fall back to basic alphabetical sorting in the event of a tie.
326                return [[objectA displayName] caseInsensitiveCompare:[objectB displayName]];
327        }
328        else if(sizeA > sizeB)
329        {
330                // There's a clear winner; run with it.
331                return NSOrderedAscending;
332        }
333        else
334        {
335                return NSOrderedDescending;
336        }
337}
338
339/*!
340 * @brief Sort function
341 */
342- (sortfunc)sortFunction{
343        return &logSizeSort;
344}
345@end
Note: See TracBrowser for help on using the browser.