1/******************************************************************************
2 * $Id: FileOutlineController.m 13340 2012-06-10 02:35:58Z livings124 $
3 *
4 * Copyright (c) 2008-2012 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "FileOutlineController.h"
26#import "Torrent.h"
27#import "FileOutlineView.h"
28#import "FilePriorityCell.h"
29#import "FileListNode.h"
30#import "NSApplicationAdditions.h"
31#import "NSMutableArrayAdditions.h"
32#import "NSStringAdditions.h"
33#import <Quartz/Quartz.h>
34
35#define ROW_SMALL_HEIGHT 18.0
36
37typedef enum
38{
39    FILE_CHECK_TAG,
40    FILE_UNCHECK_TAG
41} fileCheckMenuTag;
42
43typedef enum
44{
45    FILE_PRIORITY_HIGH_TAG,
46    FILE_PRIORITY_NORMAL_TAG,
47    FILE_PRIORITY_LOW_TAG
48} filePriorityMenuTag;
49
50@interface FileOutlineController (Private)
51
52- (NSMenu *) menu;
53
54- (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list atIndexes: (NSIndexSet *) range currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent;
55
56@end
57
58@implementation FileOutlineController
59
60- (void) awakeFromNib
61{
62    fFileList = [[NSMutableArray alloc] init];
63    
64    [fOutline setDoubleAction: @selector(revealFile:)];
65    [fOutline setTarget: self];
66    
67    //set table header tool tips
68    [[fOutline tableColumnWithIdentifier: @"Check"] setHeaderToolTip: NSLocalizedString(@"Download", "file table -> header tool tip")];
69    [[fOutline tableColumnWithIdentifier: @"Priority"] setHeaderToolTip: NSLocalizedString(@"Priority", "file table -> header tool tip")];
70    
71    [fOutline setMenu: [self menu]];
72    
73    [self setTorrent: nil];
74}
75
76- (void) dealloc
77{
78    [fFileList release];
79    [fFilterText release];
80    
81    [super dealloc];
82}
83
84- (FileOutlineView *) outlineView
85{
86    return fOutline;
87}
88
89- (void) setTorrent: (Torrent *) torrent
90{
91    fTorrent = torrent;
92    
93    [fFileList setArray: [fTorrent fileList]];
94    
95    [fFilterText release];
96    fFilterText = nil;
97    
98    [fOutline reloadData];
99    [fOutline deselectAll: nil]; //do this after reloading the data #4575
100}
101
102- (void) setFilterText: (NSString *) text
103{
104    NSArray * components = [text betterComponentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
105    if (!components || [components count] == 0)
106    {
107        text = nil;
108        components = nil;
109    }
110    
111    if ((!text && !fFilterText) || (text && fFilterText && [text isEqualToString: fFilterText]))
112        return;
113    
114    const BOOL onLion = [NSApp isOnLionOrBetter];
115    
116    if (onLion)
117        [fOutline beginUpdates];
118    
119    NSUInteger currentIndex = 0, totalCount = 0;
120    NSMutableArray * itemsToAdd = [NSMutableArray array];
121    NSMutableIndexSet * itemsToAddIndexes = [NSMutableIndexSet indexSet];
122    
123    NSMutableDictionary * removedIndexesForParents = nil; //ugly, but we can't modify the actual file nodes
124    
125    NSArray * tempList = !text ? [fTorrent fileList] : [fTorrent flatFileList];
126    for (FileListNode * item in tempList)
127    {
128        __block BOOL filter = NO;
129        if (components)
130        {
131            [components enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL * stop) {
132                if ([[item name] rangeOfString: (NSString *)obj options: (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location == NSNotFound)
133                {
134                    filter = YES;
135                    *stop = YES;
136                }
137            }];
138        }
139        
140        if (!filter)
141        {
142            FileListNode * parent = nil;
143            NSUInteger previousIndex = ![item isFolder] ? [self findFileNode: item inList: fFileList atIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(currentIndex, [fFileList count]-currentIndex)] currentParent: nil finalParent: &parent] : NSNotFound;
144            
145            if (previousIndex == NSNotFound)
146            {
147                [itemsToAdd addObject: item];
148                [itemsToAddIndexes addIndex: totalCount];
149            }
150            else
151            {
152                BOOL move = YES;
153                if (!parent)
154                {
155                    if (previousIndex != currentIndex)
156                        [fFileList moveObjectAtIndex: previousIndex toIndex: currentIndex];
157                    else
158                        move = NO;
159                }
160                else
161                {
162                    [fFileList insertObject: item atIndex: currentIndex];
163                    
164                    //figure out the index within the semi-edited table - UGLY
165                    if (!removedIndexesForParents)
166                        removedIndexesForParents = [NSMutableDictionary dictionary];
167                    
168                    NSMutableIndexSet * removedIndexes = [removedIndexesForParents objectForKey: parent];
169                    if (!removedIndexes)
170                    {
171                        removedIndexes = [NSMutableIndexSet indexSetWithIndex: previousIndex];
172                        [removedIndexesForParents setObject: removedIndexes forKey: parent];
173                    }
174                    else
175                    {
176                        [removedIndexes addIndex: previousIndex];
177                        previousIndex -= [removedIndexes countOfIndexesInRange: NSMakeRange(0, previousIndex)];
178                    }
179                }
180                
181                if (move && onLion)
182                    [fOutline moveItemAtIndex: previousIndex inParent: parent toIndex: currentIndex inParent: nil];
183                
184                ++currentIndex;
185            }
186            
187            ++totalCount;
188        }
189    }
190    
191    //remove trailing items - those are the unused
192    if (currentIndex  < [fFileList count])
193    {
194        const NSRange removeRange = NSMakeRange(currentIndex, [fFileList count]-currentIndex);
195        [fFileList removeObjectsInRange: removeRange];
196        if (onLion)
197            [fOutline removeItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: removeRange] inParent: nil withAnimation: NSTableViewAnimationSlideDown];
198    }
199    
200    //add new items
201    [fFileList insertObjects: itemsToAdd atIndexes: itemsToAddIndexes];
202    if (onLion)
203        [fOutline insertItemsAtIndexes: itemsToAddIndexes inParent: nil withAnimation: NSTableViewAnimationSlideUp];
204    
205    if (onLion)
206        [fOutline endUpdates];
207    else
208        [fOutline reloadData];
209    
210    [fFilterText release];
211    fFilterText = [text retain];
212}
213
214- (void) refresh
215{
216    [fTorrent updateFileStat];
217    
218    [fOutline setNeedsDisplay: YES];
219}
220
221- (void) outlineViewSelectionDidChange: (NSNotification *) notification
222{
223    if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible])
224        [[QLPreviewPanel sharedPreviewPanel] reloadData];
225}
226
227- (NSInteger) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item
228{
229    if (!item)
230        return fFileList ? [fFileList count] : 0;
231    else
232    {
233        FileListNode * node = (FileListNode *)item;
234        return [node isFolder] ? [[node children] count] : 0;
235    }
236}
237
238- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item 
239{
240    return [(FileListNode *)item isFolder];
241}
242
243- (id) outlineView: (NSOutlineView *) outlineView child: (NSInteger) index ofItem: (id) item
244{
245    return [(item ? [(FileListNode *)item children] : fFileList) objectAtIndex: index];
246}
247
248- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
249{
250    if ([[tableColumn identifier] isEqualToString: @"Check"])
251        return [NSNumber numberWithInteger: [fTorrent checkForFiles: [(FileListNode *)item indexes]]];
252    else
253        return item;
254}
255
256- (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn item: (id) item
257{
258    NSString * identifier = [tableColumn identifier];
259    if ([identifier isEqualToString: @"Check"])
260        [cell setEnabled: [fTorrent canChangeDownloadCheckForFiles: [(FileListNode *)item indexes]]];
261    else if ([identifier isEqualToString: @"Priority"])
262    {
263        [cell setRepresentedObject: item];
264        
265        NSInteger hoveredRow = [fOutline hoveredRow];
266        [(FilePriorityCell *)cell setHovered: hoveredRow != -1 && hoveredRow == [fOutline rowForItem: item]];
267    }
268    else;
269}
270
271- (void) outlineView: (NSOutlineView *) outlineView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
272{
273    NSString * identifier = [tableColumn identifier];
274    if ([identifier isEqualToString: @"Check"])
275    {
276        NSIndexSet * indexSet;
277        if ([NSEvent modifierFlags] & NSAlternateKeyMask)
278            indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
279        else
280            indexSet = [(FileListNode *)item indexes];
281        
282        [fTorrent setFileCheckState: [object intValue] != NSOffState ? NSOnState : NSOffState forIndexes: indexSet];
283        [fOutline setNeedsDisplay: YES];
284        
285        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
286    }
287}
288
289- (NSString *) outlineView: (NSOutlineView *) outlineView typeSelectStringForTableColumn: (NSTableColumn *) tableColumn item: (id) item
290{
291    return [(FileListNode *)item name];
292}
293
294- (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
295        tableColumn: (NSTableColumn *) tableColumn item: (id) item mouseLocation: (NSPoint) mouseLocation
296{
297    NSString * ident = [tableColumn identifier];
298    if ([ident isEqualToString: @"Name"])
299    {
300        NSString * path = [fTorrent fileLocation: item];
301        if (!path)
302            path = [[(FileListNode *)item path] stringByAppendingPathComponent: [(FileListNode *)item name]];
303        return path;
304    }
305    else if ([ident isEqualToString: @"Check"])
306    {
307        switch ([cell state])
308        {
309            case NSOffState:
310                return NSLocalizedString(@"Don't Download", "files tab -> tooltip");
311            case NSOnState:
312                return NSLocalizedString(@"Download", "files tab -> tooltip");
313            case NSMixedState:
314                return NSLocalizedString(@"Download Some", "files tab -> tooltip");
315        }
316    }
317    else if ([ident isEqualToString: @"Priority"])
318    {
319        NSSet * priorities = [fTorrent filePrioritiesForIndexes: [(FileListNode *)item indexes]];
320        switch ([priorities count])
321        {
322            case 0:
323                return NSLocalizedString(@"Priority Not Available", "files tab -> tooltip");
324            case 1:
325                switch ([[priorities anyObject] intValue])
326                {
327                    case TR_PRI_LOW:
328                        return NSLocalizedString(@"Low Priority", "files tab -> tooltip");
329                    case TR_PRI_HIGH:
330                        return NSLocalizedString(@"High Priority", "files tab -> tooltip");
331                    case TR_PRI_NORMAL:
332                        return NSLocalizedString(@"Normal Priority", "files tab -> tooltip");
333                }
334                break;
335            default:
336                return NSLocalizedString(@"Multiple Priorities", "files tab -> tooltip");
337        }
338    }
339    else;
340    
341    return nil;
342}
343
344- (CGFloat) outlineView: (NSOutlineView *) outlineView heightOfRowByItem: (id) item
345{
346    if ([(FileListNode *)item isFolder])
347        return ROW_SMALL_HEIGHT;
348    else
349        return [outlineView rowHeight];
350}
351
352- (void) setCheck: (id) sender
353{
354    NSInteger state = [sender tag] == FILE_UNCHECK_TAG ? NSOffState : NSOnState;
355    
356    NSIndexSet * indexSet = [fOutline selectedRowIndexes];
357    NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
358    for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
359        [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
360    
361    [fTorrent setFileCheckState: state forIndexes: itemIndexes];
362    [fOutline setNeedsDisplay: YES];
363}
364
365- (void) setOnlySelectedCheck: (id) sender
366{
367    NSIndexSet * indexSet = [fOutline selectedRowIndexes];
368    NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
369    for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
370        [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
371    
372    [fTorrent setFileCheckState: NSOnState forIndexes: itemIndexes];
373    
374    NSMutableIndexSet * remainingItemIndexes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
375    [remainingItemIndexes removeIndexes: itemIndexes];
376    [fTorrent setFileCheckState: NSOffState forIndexes: remainingItemIndexes];
377    
378    [fOutline setNeedsDisplay: YES];
379}
380
381- (void) checkAll
382{
383    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
384    [fTorrent setFileCheckState: NSOnState forIndexes: indexSet];
385    [fOutline setNeedsDisplay: YES];
386}
387
388- (void) uncheckAll
389{
390    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
391    [fTorrent setFileCheckState: NSOffState forIndexes: indexSet];
392    [fOutline setNeedsDisplay: YES];
393}
394
395- (void) setPriority: (id) sender
396{
397    tr_priority_t priority;
398    switch ([sender tag])
399    {
400        case FILE_PRIORITY_HIGH_TAG:
401            priority = TR_PRI_HIGH;
402            break;
403        case FILE_PRIORITY_NORMAL_TAG:
404            priority = TR_PRI_NORMAL;
405            break;
406        case FILE_PRIORITY_LOW_TAG:
407            priority = TR_PRI_LOW;
408    }
409    
410    NSIndexSet * indexSet = [fOutline selectedRowIndexes];
411    NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
412    for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
413        [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
414    
415    [fTorrent setFilePriority: priority forIndexes: itemIndexes];
416    [fOutline setNeedsDisplay: YES];
417}
418
419- (void) revealFile: (id) sender
420{
421    NSIndexSet * indexes = [fOutline selectedRowIndexes];
422    NSMutableArray * paths = [NSMutableArray arrayWithCapacity: [indexes count]];
423    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
424    {
425        NSString * path = [fTorrent fileLocation: [fOutline itemAtRow: i]];
426        if (path)
427            [paths addObject: [NSURL fileURLWithPath: path]];
428    }
429    
430    if ([paths count] > 0)
431        [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: paths];
432}
433
434#warning make real view controller (Leopard-only) so that Command-R will work
435- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
436{
437    if (!fTorrent)
438        return NO;
439    
440    SEL action = [menuItem action];
441    
442    if (action == @selector(revealFile:))
443    {
444        NSIndexSet * indexSet = [fOutline selectedRowIndexes];
445        for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
446            if ([fTorrent fileLocation: [fOutline itemAtRow: i]] != nil)
447                return YES;
448        return NO;
449    }
450    
451    if (action == @selector(setCheck:))
452    {
453        if ([fOutline numberOfSelectedRows] == 0)
454            return NO;
455        
456        NSIndexSet * indexSet = [fOutline selectedRowIndexes];
457        NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
458        for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
459            [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
460        
461        NSInteger state = ([menuItem tag] == FILE_CHECK_TAG) ? NSOnState : NSOffState;
462        return [fTorrent checkForFiles: itemIndexes] != state && [fTorrent canChangeDownloadCheckForFiles: itemIndexes];
463    }
464    
465    if (action == @selector(setOnlySelectedCheck:))
466    {
467        if ([fOutline numberOfSelectedRows] == 0)
468            return NO;
469        
470        NSIndexSet * indexSet = [fOutline selectedRowIndexes];
471        NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
472        for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
473            [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
474        
475        return [fTorrent canChangeDownloadCheckForFiles: itemIndexes];
476    }
477    
478    if (action == @selector(setPriority:))
479    {
480        if ([fOutline numberOfSelectedRows] == 0)
481        {
482            [menuItem setState: NSOffState];
483            return NO;
484        }
485        
486        //determine which priorities are checked
487        NSIndexSet * indexSet = [fOutline selectedRowIndexes];
488        tr_priority_t priority;
489        switch ([menuItem tag])
490        {
491            case FILE_PRIORITY_HIGH_TAG:
492                priority = TR_PRI_HIGH;
493                break;
494            case FILE_PRIORITY_NORMAL_TAG:
495                priority = TR_PRI_NORMAL;
496                break;
497            case FILE_PRIORITY_LOW_TAG:
498                priority = TR_PRI_LOW;
499                break;
500        }
501        
502        BOOL current = NO, canChange = NO;
503        for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
504        {
505            NSIndexSet * fileIndexSet = [[fOutline itemAtRow: i] indexes];
506            if (![fTorrent canChangeDownloadCheckForFiles: fileIndexSet])
507                continue;
508            
509            canChange = YES;
510            if ([fTorrent hasFilePriority: priority forIndexes: fileIndexSet])
511            {
512                current = YES;
513                break;
514            }
515        }
516        
517        [menuItem setState: current ? NSOnState : NSOffState];
518        return canChange;
519    }
520    
521    return YES;
522}
523
524@end
525
526@implementation FileOutlineController (Private)
527
528- (NSMenu *) menu
529{
530    NSMenu * menu = [[NSMenu alloc] initWithTitle: @"File Outline Menu"];
531    
532    //check and uncheck
533    NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Check Selected", "File Outline -> Menu")
534                            action: @selector(setCheck:) keyEquivalent: @""];
535    [item setTarget: self];
536    [item setTag: FILE_CHECK_TAG];
537    [menu addItem: item];
538    [item release];
539    
540    item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Uncheck Selected", "File Outline -> Menu")
541            action: @selector(setCheck:) keyEquivalent: @""];
542    [item setTarget: self];
543    [item setTag: FILE_UNCHECK_TAG];
544    [menu addItem: item];
545    [item release];
546    
547    //only check selected
548    item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Only Check Selected", "File Outline -> Menu")
549            action: @selector(setOnlySelectedCheck:) keyEquivalent: @""];
550    [item setTarget: self];
551    [menu addItem: item];
552    [item release];
553    
554    [menu addItem: [NSMenuItem separatorItem]];
555    
556    //priority
557    item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Priority", "File Outline -> Menu") action: NULL keyEquivalent: @""];
558    NSMenu * priorityMenu = [[NSMenu alloc] initWithTitle: @"File Priority Menu"];
559    [item setSubmenu: priorityMenu];
560    [menu addItem: item];
561    [item release];
562    
563    item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"High", "File Outline -> Priority Menu")
564            action: @selector(setPriority:) keyEquivalent: @""];
565    [item setTarget: self];
566    [item setTag: FILE_PRIORITY_HIGH_TAG];
567    [item setImage: [NSImage imageNamed: @"PriorityHighTemplate"]];
568    [priorityMenu addItem: item];
569    [item release];
570    
571    item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Normal", "File Outline -> Priority Menu")
572            action: @selector(setPriority:) keyEquivalent: @""];
573    [item setTarget: self];
574    [item setTag: FILE_PRIORITY_NORMAL_TAG];
575    [item setImage: [NSImage imageNamed: @"PriorityNormalTemplate"]];
576    [priorityMenu addItem: item];
577    [item release];
578    
579    item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Low", "File Outline -> Priority Menu")
580            action: @selector(setPriority:) keyEquivalent: @""];
581    [item setTarget: self];
582    [item setTag: FILE_PRIORITY_LOW_TAG];
583    [item setImage: [NSImage imageNamed: @"PriorityLowTemplate"]];
584    [priorityMenu addItem: item];
585    [item release];
586    
587    [priorityMenu release];
588    
589    [menu addItem: [NSMenuItem separatorItem]];
590    
591    //reveal in finder
592    item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Show in Finder", "File Outline -> Menu")
593            action: @selector(revealFile:) keyEquivalent: @""];
594    [item setTarget: self];
595    [menu addItem: item];
596    [item release];
597    
598    return [menu autorelease];
599}
600
601- (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list atIndexes: (NSIndexSet *) indexes currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent
602{
603    NSAssert(![node isFolder], @"Looking up folder node!");
604    
605    __block NSUInteger retIndex = NSNotFound;
606    
607    [list enumerateObjectsAtIndexes: indexes options: NSEnumerationConcurrent usingBlock: ^(id checkNode, NSUInteger index, BOOL * stop) {
608        if ([[checkNode indexes] containsIndex: [[node indexes] firstIndex]])
609        {
610            if (![checkNode isFolder])
611            {
612                NSAssert2([checkNode isEqualTo: node], @"Expected file nodes to be equal: %@ %@", checkNode, node);
613                
614                *parent = currentParent;
615                retIndex = index;
616            }
617            else
618            {
619                const NSUInteger subIndex = [self findFileNode: node inList: [checkNode children] atIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[checkNode children] count])] currentParent: checkNode finalParent: parent];
620                NSAssert(subIndex != NSNotFound, @"We didn't find an expected file node.");
621                retIndex = subIndex;
622            }
623            
624            *stop = YES;
625        }
626    }];
627    
628    return retIndex;
629}
630
631@end
632