root/Trunk/AILogSizeSort.m @ 26

Revision 26, 9.0 KB (checked in by jon, 13 years ago)

Various updates to accommodate API changes in Adium 1.4. Now builds and runs with Adium 1.4, but no longer with 1.3.

  • 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        if(groups)
277        {
278                // Keep groups in manual order (borrowed from ESStatusSort)
279                if ([container orderIndexForObject:objectA] > [container orderIndexForObject:objectB])
280                {
281                        return NSOrderedDescending;
282                }
283                else
284                {
285                        return NSOrderedAscending;
286                }
287        }
288       
289        // Get a reference to one and only AILogSizeSort instance.  If this sorting method is being
290        // called, it should always be the case that AILogSizeSort is the active sort controller.
291        AISortController *sortController = [AISortController activeSortController];
292       
293        unsigned long long sizeA = 0;
294        unsigned long long sizeB = 0;
295       
296        // Attempt to use cached log sizes if possible, but fall back to dumber methods if something
297        // unforeseen happens.
298        if([sortController isMemberOfClass:[AILogSizeSort class]])
299        {
300                sizeA = [(AILogSizeSort *)sortController getCachedLogSize:objectA];
301                sizeB = [(AILogSizeSort *)sortController getCachedLogSize:objectB];
302        }
303        else
304        {
305                sizeA = [AILogSizeSort getContactLogSize:objectA];
306                sizeB = [AILogSizeSort getContactLogSize:objectB];
307        }
308
309        if(sizeB == sizeA)
310        {
311                // Fall back to basic alphabetical sorting in the event of a tie.
312                return [[objectA displayName] caseInsensitiveCompare:[objectB displayName]];
313        }
314        else if(sizeA > sizeB)
315        {
316                // There's a clear winner; run with it.
317                return NSOrderedAscending;
318        }
319        else
320        {
321                return NSOrderedDescending;
322        }
323}
324
325/*!
326 * @brief Sort function
327 */
328- (sortfunc)sortFunction{
329        return &logSizeSort;
330}
331@end
Note: See TracBrowser for help on using the browser.