Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosContainerNode.m @ 919cb043

History | View | Annotate | Download (25.8 kB)

1
//
2
//  PithosContainerNode.m
3
//  pithos-macos
4
//
5
// Copyright 2011-2012 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
15
//   2. Redistributions in binary form must reproduce the above
16
//      copyright notice, this list of conditions and the following
17
//      disclaimer in the documentation and/or other materials
18
//      provided with the distribution.
19
// 
20
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
// POSSIBILITY OF SUCH DAMAGE.
32
// 
33
// The views and conclusions contained in the software and
34
// documentation are those of the authors and should not be
35
// interpreted as representing official policies, either expressed
36
// or implied, of GRNET S.A.
37

    
38
#import "PithosContainerNode.h"
39
#import "PithosObjectNode.h"
40
#import "PithosSubdirNode.h"
41
#import "ASIPithos.h"
42
#import "ASIPithosContainerRequest.h"
43
#import "ASIPithosContainer.h"
44
#import "ASIPithosObject.h"
45
#import "ASIDownloadCache.h"
46
#import "PithosUtilities.h"
47
#import "PithosContainerNodeInfoController.h"
48
#import "PithosActivityFacility.h"
49

    
50
static NSImage *sharedIcon = nil;
51

    
52
@implementation PithosContainerNode
53
@synthesize pithos, pithosContainer, prefix;
54
@synthesize policyVersioning, policyQuota;
55

    
56
+ (void)initialize {
57
	if (self == [PithosContainerNode class])
58
        sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain];
59
}
60

    
61
#pragma mark -
62
#pragma mark Object Lifecycle
63

    
64
- (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
65
    if ((self = [super init])) {
66
        self.pithos = aPithos;
67
        self.pithosContainer = aPithosContainer;
68
        prefix = nil;
69
        self.icon = anIcon;
70
        childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated";
71
    }
72
    return self;
73
}
74

    
75
- (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer {
76
    return [self initWithPithos:aPithos pithosContainer:aPithosContainer icon:nil];
77
}
78

    
79
- (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
80
    ASIPithosContainer *container = [ASIPithosContainer container];
81
    container.name = aContainerName;
82
    return [self initWithPithos:aPithos pithosContainer:container icon:anIcon];
83
}
84

    
85
- (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName {
86
    return [self initWithPithos:aPithos containerName:aContainerName icon:nil];
87
}
88

    
89
- (void)dealloc {
90
    [containerRequest clearDelegatesAndCancel];
91
    [containerRequest release];
92
    [refreshMetadataContainerRequest clearDelegatesAndCancel];
93
    [refreshMetadataContainerRequest release];
94
    [applyMetadataContainerRequest clearDelegatesAndCancel];
95
    [applyMetadataContainerRequest release];
96
    [policyQuota release];
97
    [policyVersioning release];
98
    [childrenUpdatedNotificationName release];
99
    [prefix release];
100
    [objects release];
101
    [pithosContainer release];
102
    [pithos release];
103
    [super dealloc];
104
}
105

    
106
#pragma mark -
107
#pragma mark Properties
108

    
109
- (NSString *)url {
110
    if (url == nil)
111
        url = [[NSString alloc] initWithFormat:@"%@/%@%@", 
112
               (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
113
               pithosContainer.name, 
114
               (shared ? @"?shared" : @"")];
115
    return url;
116
}
117

    
118
- (NSArray *)children {
119
    @synchronized(self) {
120
        switch (freshness) {
121
            case PithosNodeStateFresh:
122
                break;
123
            case PithosNodeStateRefreshNeeded:
124
                freshness = PithosNodeStateRefreshing;
125
                containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
126
                                                                              containerName:pithosContainer.name 
127
                                                                                      limit:0 
128
                                                                                     marker:nil 
129
                                                                                     prefix:prefix 
130
                                                                                  delimiter:@"/" 
131
                                                                                       path:nil 
132
                                                                                       meta:nil 
133
                                                                                     shared:shared 
134
                                                                                      until:nil] retain];
135
                if (sharingAccount)
136
                    [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
137
                containerRequest.delegate = self;
138
                containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
139
                containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
140
                containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
141
                                             [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
142
                                             [NSNumber numberWithUnsignedInteger:10], @"retries", 
143
                                             NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", 
144
                                             NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", 
145
                                             nil];
146
                if (!forcedRefresh)
147
                    containerRequest.downloadCache = [ASIDownloadCache sharedCache];
148
                [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
149
                break;
150
            case PithosNodeStateRefreshing:
151
                break;
152
            case PithosNodeStateRefreshFinished:
153
                if (newChildren) {
154
                    [children release];
155
                    children = newChildren;
156
                    newChildren = nil;
157
                }
158
                freshness = PithosNodeStateFresh;
159
            default:
160
                break;
161
        }
162
        return children;
163
    }
164
}
165

    
166
- (NSString *)displayName {
167
    return [[pithosContainer.name copy] autorelease];
168
}
169

    
170
- (void)setDisplayName:(NSString *)aDisplayName {
171
}
172

    
173
- (NSImage *)icon {
174
    if (icon == nil) {
175
        if ([pithosContainer.name isEqualToString:@"pithos"])
176
            icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)] retain];
177
        else if ([pithosContainer.name isEqualToString:@"trash"])
178
            icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)] retain];
179
        else
180
            icon = [sharedIcon retain];
181
    }
182
    return icon;
183
}
184

    
185
- (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
186
    if (![pithosContainer isEqualTo:aPithosContainer]) {
187
        [pithosContainer release];
188
        pithosContainer = [aPithosContainer retain];
189
    }
190
    if (pithosContainer.policy) {
191
        self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
192
        self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
193
    } else {
194
        self.policyVersioning = @"manual";
195
        self.policyQuota = [NSNumber numberWithLongLong:0];
196
    }
197
}
198

    
199
#pragma mark -
200
#pragma mark ASIHTTPRequestDelegate
201

    
202
- (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
203
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
204
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
205
    if (retries > 0) {
206
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
207
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
208
        [containerRequest release];
209
        containerRequest = newContainerRequest;
210
        [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
211
    } else {
212
        NSString *message;
213
        NSError *error = [containerRequest error];
214
        if (error)
215
            message = [NSString stringWithFormat:@"Container listing failed: %@", error];
216
        else
217
            message = [NSString stringWithFormat:@"Container listing failed: (%d) %@", 
218
                       containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
219
        dispatch_async(dispatch_get_main_queue(), ^{
220
            [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
221
        });
222
        [newChildren release];
223
        newChildren = nil;
224
        [containerRequest release];
225
        containerRequest = nil;
226
        [objects release];
227
        objects = nil;
228
        forcedRefresh = NO;
229
        @synchronized(self) {
230
            freshness = PithosNodeStateRefreshNeeded;
231
        }
232
    }
233
    [pool drain];
234
}
235

    
236
- (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
237
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
238
    NSLog(@"List container finished: %@", [containerRequest url]);
239
    NSLog(@"Cached: %d", [containerRequest didUseCachedResponse]);
240
    if (containerRequest.responseStatusCode == 200) {
241
        if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
242
            pithosContainer.blockHash = [containerRequest blockHash];
243
            pithosContainer.blockSize = [containerRequest blockSize];
244
        }
245
    
246
        NSArray *someObjects = [containerRequest objects];
247
        if (objects == nil) {
248
            objects = [[NSMutableArray alloc] initWithArray:someObjects];
249
        } else {
250
            [objects addObjectsFromArray:someObjects];
251
        }
252
        if ([someObjects count] < 10000) {
253
            if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
254
                // Save new children
255
                NSLog(@"using newChildren");
256
                newChildren = [[NSMutableArray alloc] init];
257
                NSArray *objectNames = [objects valueForKey:@"name"];
258
                NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
259
                BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
260
                for (ASIPithosObject *object in objects) {
261
                    if (!isSubdirNode || 
262
                        ([object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]] &&
263
                         ([object.name length] > [((PithosSubdirNode *)self).prefix length] + 1))) {
264
                        // The check above removes false objects due to trailing slash or same prefix
265
                        if (object.subdir) {
266
                            NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
267
                            if ((sameNameObjectIndex == NSNotFound) || 
268
                                ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) {
269
                                PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos 
270
                                                                                   pithosContainer:pithosContainer 
271
                                                                                      pithosObject:object] autorelease];
272
                                node.parent = self;
273
                                node.shared = shared;
274
                                node.sharingAccount = sharingAccount;
275
                                if (children) {
276
                                    NSUInteger oldIndex = [children indexOfObject:node];
277
                                    if (oldIndex != NSNotFound) {
278
                                        // Use the same pointer value, if possible
279
                                        node = [children objectAtIndex:oldIndex];
280
                                        node.pithosContainer = pithosContainer;
281
                                        node.pithosObject = object;
282
                                        [keptNodes addIndex:oldIndex];
283
                                    }
284
                                }
285
                                if (sharingAccount)
286
                                    node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
287
                                [newChildren addObject:node];
288
                            }
289
                        } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
290
                            PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos 
291
                                                                               pithosContainer:pithosContainer 
292
                                                                                  pithosObject:object] autorelease];
293
                            node.parent = self;
294
                            node.shared = shared;
295
                            node.sharingAccount = sharingAccount;
296
                            if (children) {
297
                                NSUInteger oldIndex = [children indexOfObject:node];
298
                                if (oldIndex != NSNotFound) {
299
                                    // Use the same pointer value, if possible
300
                                    node = [children objectAtIndex:oldIndex];
301
                                    node.pithosContainer = pithosContainer;
302
                                    node.pithosObject = object;
303
                                    [keptNodes addIndex:oldIndex];
304
                                }
305
                            }
306
                            [newChildren addObject:node];
307
                        } else {
308
                            PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithos:pithos 
309
                                                                               pithosContainer:pithosContainer 
310
                                                                                  pithosObject:object] autorelease];
311
                            node.parent = self;
312
                            node.shared = shared;
313
                            node.sharingAccount = sharingAccount;
314
                            if (children) {
315
                                NSUInteger oldIndex = [children indexOfObject:node];
316
                                if (oldIndex != NSNotFound) {
317
                                    // Use the same pointer value, if possible
318
                                    node = [children objectAtIndex:oldIndex];
319
                                    node.pithosContainer = pithosContainer;
320
                                    node.pithosObject = object;
321
                                    [keptNodes addIndex:oldIndex];
322
                                }
323
                            }
324
                            [newChildren addObject:node];                                
325
                        }
326
                    }
327
                }
328
                [[children objectsAtIndexes:
329
                  [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
330
                    if ([keptNodes containsIndex:idx])
331
                        return NO;
332
                    return YES;
333
                }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
334
            }
335
            // Else cache was used and all results were fetched during this request, so existing children can be reused
336
            [containerRequest release];
337
            containerRequest = nil;
338
            [objects release];
339
            objects = nil;
340
            forcedRefresh = NO;
341
            @synchronized(self) {
342
                freshness = PithosNodeStateRefreshFinished;
343
            }
344
            // Notify observers that children are updated
345
            [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
346
        } else {
347
            [containerRequest release];
348
            // Do an additional request to fetch more objects
349
            containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
350
                                                                          containerName:pithosContainer.name 
351
                                                                                  limit:0 
352
                                                                                 marker:[[someObjects lastObject] name] 
353
                                                                                 prefix:prefix 
354
                                                                              delimiter:@"/" 
355
                                                                                   path:nil 
356
                                                                                   meta:nil 
357
                                                                                 shared:shared 
358
                                                                                  until:nil] retain];
359
            if (sharingAccount)
360
                [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
361
            containerRequest.delegate = self;
362
            containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
363
            containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
364
            containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
365
                                         [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
366
                                         [NSNumber numberWithUnsignedInteger:10], @"retries", 
367
                                         NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", 
368
                                         NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", 
369
                                         nil];
370
            if (!forcedRefresh)
371
            containerRequest.downloadCache = [ASIDownloadCache sharedCache];
372
            [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
373
        }
374
    } else if (containerRequest.responseStatusCode == 304) {
375
        // Container is not modified, so existing children can be reused
376
        [containerRequest release];
377
        containerRequest = nil;
378
        [objects release];
379
        objects = nil;
380
        forcedRefresh = NO;
381
        @synchronized(self) {
382
            freshness = PithosNodeStateRefreshFinished;
383
        }
384
        // Notify observers that children are updated
385
        [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
386
    } else {
387
        [self containerRequestFailed:containerRequest];
388
    }
389
    [pool drain];
390
}
391

    
392
- (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
393
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
394
    NSLog(@"URL: %@", [request url]);
395
    NSLog(@"cached: %d", [request didUseCachedResponse]);
396
    
397
    if ([request isEqualTo:applyMetadataContainerRequest]) {
398
        @synchronized(self) {
399
            [applyMetadataContainerRequest release];
400
            applyMetadataContainerRequest = nil;
401
        }
402
        [self refreshInfo];
403
    } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
404
        [[pithosNodeInfoController window] makeFirstResponder:nil];
405
        self.pithosContainer = [refreshMetadataContainerRequest container];
406
        @synchronized(self) {
407
            [refreshMetadataContainerRequest release];
408
            refreshMetadataContainerRequest = nil;
409
        }
410
    }
411
    [pool drain];
412
}
413

    
414
- (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
415
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
416
    if ([request isEqualTo:applyMetadataContainerRequest]) {
417
        [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
418
        @synchronized(self) {
419
            [applyMetadataContainerRequest release];
420
            applyMetadataContainerRequest = nil;
421
        }
422
    } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
423
        [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
424
        @synchronized(self) {
425
            [refreshMetadataContainerRequest release];
426
            refreshMetadataContainerRequest = nil;
427
        }
428
    }
429
    [pool drain];
430
}
431

    
432
#pragma mark -
433
#pragma mark Info
434

    
435
- (void)applyInfo {
436
    @synchronized(self) {
437
        if (applyMetadataContainerRequest == nil) {
438
            [[pithosNodeInfoController window] makeFirstResponder:nil];
439
            applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithos 
440
                                                                                                   containerName:pithosContainer.name 
441
                                                                                                          policy:[NSDictionary dictionaryWithObjectsAndKeys:
442
                                                                                                                  policyVersioning, @"versioning", 
443
                                                                                                                  [policyQuota stringValue], @"quota", 
444
                                                                                                                  nil] 
445
                                                                                                        metadata:pithosContainer.metadata 
446
                                                                                                          update:NO] retain];
447
            applyMetadataContainerRequest.delegate = self;
448
            applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
449
            applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
450
            applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
451
                                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
452
                                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
453
                                                      NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
454
                                                      NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
455
                                                      nil];
456
            [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
457
        }
458
    }
459
}
460

    
461
- (void)refreshInfo {
462
    @synchronized(self) {
463
        if (refreshMetadataContainerRequest == nil) {
464
            refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
465
                                                                                               containerName:pithosContainer.name] retain];
466
            refreshMetadataContainerRequest.delegate = self;
467
            refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
468
            refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
469
            refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
470
                                                        [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
471
                                                        [NSNumber numberWithUnsignedInteger:10], @"retries", 
472
                                                        NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
473
                                                        NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
474
                                                        nil];
475
            refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
476
            [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
477
        }
478
    }
479
}
480

    
481
#pragma mark -
482
#pragma mark Actions
483

    
484
- (void)showPithosNodeInfo:(id)sender {
485
    if (!pithosNodeInfoController)
486
        pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
487
    [pithosNodeInfoController showWindow:sender];
488
    [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
489
    [NSApp activateIgnoringOtherApps:YES];
490
}
491

    
492
@end