Subversion Repositories Mobile Apps.GyroMouse

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 pmbaty 1
#import "DDFileLogger.h"
2
 
3
#import <unistd.h>
4
#import <sys/attr.h>
5
#import <sys/xattr.h>
6
#import <libkern/OSAtomic.h>
7
 
8
/**
9
 * Welcome to Cocoa Lumberjack!
10
 *
11
 * The project page has a wealth of documentation if you have any questions.
12
 * https://github.com/CocoaLumberjack/CocoaLumberjack
13
 *
14
 * If you're new to the project you may wish to read the "Getting Started" wiki.
15
 * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted
16
**/
17
 
18
#if ! __has_feature(objc_arc)
19
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
20
#endif
21
 
22
// We probably shouldn't be using DDLog() statements within the DDLog implementation.
23
// But we still want to leave our log statements for any future debugging,
24
// and to allow other developers to trace the implementation (which is a great learning tool).
25
//
26
// So we use primitive logging macros around NSLog.
27
// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
28
 
29
#define LOG_LEVEL 2
30
 
31
#define NSLogError(frmt, ...)    do{ if(LOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
32
#define NSLogWarn(frmt, ...)     do{ if(LOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
33
#define NSLogInfo(frmt, ...)     do{ if(LOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
34
#define NSLogDebug(frmt, ...)    do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
35
#define NSLogVerbose(frmt, ...)  do{ if(LOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
36
 
37
@interface DDLogFileManagerDefault (PrivateAPI)
38
 
39
- (void)deleteOldLogFiles;
40
- (NSString *)defaultLogsDirectory;
41
 
42
@end
43
 
44
@interface DDFileLogger (PrivateAPI)
45
 
46
- (void)rollLogFileNow;
47
- (void)maybeRollLogFileDueToAge;
48
- (void)maybeRollLogFileDueToSize;
49
 
50
@end
51
 
52
#if TARGET_OS_IPHONE
53
BOOL doesAppRunInBackground(void);
54
#endif
55
 
56
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
57
#pragma mark -
58
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
59
 
60
@implementation DDLogFileManagerDefault
61
 
62
@synthesize maximumNumberOfLogFiles;
63
 
64
- (id)init
65
{
66
    return [self initWithLogsDirectory:nil];
67
}
68
 
69
- (instancetype)initWithLogsDirectory:(NSString *)aLogsDirectory
70
{
71
    if ((self = [super init]))
72
    {
73
        maximumNumberOfLogFiles = DEFAULT_LOG_MAX_NUM_LOG_FILES;
74
 
75
        if (aLogsDirectory)
76
            _logsDirectory = [aLogsDirectory copy];
77
        else
78
            _logsDirectory = [[self defaultLogsDirectory] copy];
79
 
80
        NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
81
 
82
        [self addObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles)) options:kvoOptions context:nil];
83
 
84
        NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]);
85
        NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]);
86
    }
87
    return self;
88
}
89
 
90
- (void)dealloc
91
{
92
    // try-catch because the observer might be removed or never added. In this case, removeObserver throws and exception
93
    @try {
94
        [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles))];
95
    }
96
    @catch (NSException *exception) {
97
 
98
    }
99
}
100
 
101
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
102
#pragma mark Configuration
103
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
104
 
105
- (void)observeValueForKeyPath:(NSString *)keyPath
106
                      ofObject:(id)object
107
                        change:(NSDictionary *)change
108
                       context:(void *)context
109
{
110
    NSNumber *old = [change objectForKey:NSKeyValueChangeOldKey];
111
    NSNumber *new = [change objectForKey:NSKeyValueChangeNewKey];
112
 
113
    if ([old isEqual:new])
114
    {
115
        // No change in value - don't bother with any processing.
116
        return;
117
    }
118
 
119
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(maximumNumberOfLogFiles))])
120
    {
121
        NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles");
122
 
123
        dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool {
124
 
125
            [self deleteOldLogFiles];
126
        }});
127
    }
128
}
129
 
130
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
131
#pragma mark File Deleting
132
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
133
 
134
/**
135
 * Deletes archived log files that exceed the maximumNumberOfLogFiles configuration value.
136
**/
137
- (void)deleteOldLogFiles
138
{
139
    NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles");
140
 
141
    NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles;
142
    if (maxNumLogFiles == 0)
143
    {
144
        // Unlimited - don't delete any log files
145
        return;
146
    }
147
 
148
    NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
149
 
150
    // Do we consider the first file?
151
    // We are only supposed to be deleting archived files.
152
    // In most cases, the first file is likely the log file that is currently being written to.
153
    // So in most cases, we do not want to consider this file for deletion.
154
 
155
    NSUInteger count = [sortedLogFileInfos count];
156
    BOOL excludeFirstFile = NO;
157
 
158
    if (count > 0)
159
    {
160
        DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:0];
161
 
162
        if (!logFileInfo.isArchived)
163
        {
164
            excludeFirstFile = YES;
165
        }
166
    }
167
 
168
    NSArray *sortedArchivedLogFileInfos;
169
    if (excludeFirstFile)
170
    {
171
        count--;
172
        sortedArchivedLogFileInfos = [sortedLogFileInfos subarrayWithRange:NSMakeRange(1, count)];
173
    }
174
    else
175
    {
176
        sortedArchivedLogFileInfos = sortedLogFileInfos;
177
    }
178
 
179
    NSUInteger i;
180
    for (i = maxNumLogFiles; i < count; i++)
181
    {
182
        DDLogFileInfo *logFileInfo = [sortedArchivedLogFileInfos objectAtIndex:i];
183
 
184
        NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName);
185
 
186
        [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil];
187
    }
188
}
189
 
190
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
191
#pragma mark Log Files
192
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
193
 
194
/**
195
 * Returns the path to the default logs directory.
196
 * If the logs directory doesn't exist, this method automatically creates it.
197
**/
198
- (NSString *)defaultLogsDirectory
199
{
200
#if TARGET_OS_IPHONE
201
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
202
    NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
203
    NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
204
 
205
#else
206
    NSString *appName = [[NSProcessInfo processInfo] processName];
207
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
208
    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
209
    NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName];
210
 
211
#endif
212
 
213
    return logsDirectory;
214
}
215
 
216
- (NSString *)logsDirectory
217
{
218
    // We could do this check once, during initalization, and not bother again.
219
    // But this way the code continues to work if the directory gets deleted while the code is running.
220
 
221
    if (![[NSFileManager defaultManager] fileExistsAtPath:_logsDirectory])
222
    {
223
        NSError *err = nil;
224
        if (![[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory
225
                                       withIntermediateDirectories:YES attributes:nil error:&err])
226
        {
227
            NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err);
228
        }
229
    }
230
 
231
    return _logsDirectory;
232
}
233
 
234
/**
235
 * A log file has a name like "<app name> <date> <time>.log".
236
 * Example: MobileSafari 2013-12-03 17-14.log
237
**/
238
- (BOOL)isLogFile:(NSString *)fileName
239
{
240
    NSString *appName = [self applicationName];
241
 
242
    BOOL hasProperPrefix = [fileName hasPrefix:appName];
243
    BOOL hasProperSuffix = [fileName hasSuffix:@".log"];
244
    BOOL hasProperDate = NO;
245
 
246
    if (hasProperPrefix && hasProperSuffix)
247
    {
248
        NSUInteger lengthOfMiddle = fileName.length - appName.length - @".log".length;
249
 
250
        // Date string should have at least 16 characters - " 2013-12-03 17-14"
251
        if (lengthOfMiddle >= 17)
252
        {
253
            NSRange range = NSMakeRange(appName.length, lengthOfMiddle);
254
 
255
            NSString *middle = [fileName substringWithRange:range];
256
            NSArray *components = [middle componentsSeparatedByString:@" "];
257
 
258
            // When creating logfile if there is existing file with the same name, we append attemp number at the end.
259
            // Thats why here we can have three or four components. For details see generateLogFileNameWithAttempt: method.
260
            //
261
            // Components:
262
            //     "", "2013-12-03", "17-14"
263
            // or
264
            //     "", "2013-12-03", "17-14", "1"
265
            if (components.count == 3 || components.count == 4)
266
            {
267
                NSString *dateString = [NSString stringWithFormat:@"%@ %@", components[1], components[2]];
268
                NSDateFormatter *dateFormatter = [self logFileDateFormatter];
269
 
270
                NSDate *date = [dateFormatter dateFromString:dateString];
271
 
272
                if (date)
273
                {
274
                    hasProperDate = YES;
275
                }
276
            }
277
        }
278
    }
279
 
280
    return (hasProperPrefix && hasProperDate && hasProperSuffix);
281
}
282
 
283
- (NSDateFormatter *)logFileDateFormatter
284
{
285
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
286
    [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd' 'HH'-'mm'"];
287
    [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
288
 
289
    return dateFormatter;
290
}
291
 
292
/**
293
 * Returns an array of NSString objects,
294
 * each of which is the filePath to an existing log file on disk.
295
**/
296
- (NSArray *)unsortedLogFilePaths
297
{
298
    NSString *logsDirectory = [self logsDirectory];
299
    NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:nil];
300
 
301
    NSMutableArray *unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]];
302
 
303
    for (NSString *fileName in fileNames)
304
    {
305
        // Filter out any files that aren't log files. (Just for extra safety)
306
 
307
        if ([self isLogFile:fileName])
308
        {
309
            NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
310
 
311
            [unsortedLogFilePaths addObject:filePath];
312
        }
313
    }
314
 
315
    return unsortedLogFilePaths;
316
}
317
 
318
/**
319
 * Returns an array of NSString objects,
320
 * each of which is the fileName of an existing log file on disk.
321
**/
322
- (NSArray *)unsortedLogFileNames
323
{
324
    NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
325
 
326
    NSMutableArray *unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
327
 
328
    for (NSString *filePath in unsortedLogFilePaths)
329
    {
330
        [unsortedLogFileNames addObject:[filePath lastPathComponent]];
331
    }
332
 
333
    return unsortedLogFileNames;
334
}
335
 
336
/**
337
 * Returns an array of DDLogFileInfo objects,
338
 * each representing an existing log file on disk,
339
 * and containing important information about the log file such as it's modification date and size.
340
**/
341
- (NSArray *)unsortedLogFileInfos
342
{
343
    NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
344
 
345
    NSMutableArray *unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
346
 
347
    for (NSString *filePath in unsortedLogFilePaths)
348
    {
349
        DDLogFileInfo *logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath];
350
 
351
        [unsortedLogFileInfos addObject:logFileInfo];
352
    }
353
 
354
    return unsortedLogFileInfos;
355
}
356
 
357
/**
358
 * Just like the unsortedLogFilePaths method, but sorts the array.
359
 * The items in the array are sorted by creation date.
360
 * The first item in the array will be the most recently created log file.
361
**/
362
- (NSArray *)sortedLogFilePaths
363
{
364
    NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
365
 
366
    NSMutableArray *sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
367
 
368
    for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
369
    {
370
        [sortedLogFilePaths addObject:[logFileInfo filePath]];
371
    }
372
 
373
    return sortedLogFilePaths;
374
}
375
 
376
/**
377
 * Just like the unsortedLogFileNames method, but sorts the array.
378
 * The items in the array are sorted by creation date.
379
 * The first item in the array will be the most recently created log file.
380
**/
381
- (NSArray *)sortedLogFileNames
382
{
383
    NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
384
 
385
    NSMutableArray *sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
386
 
387
    for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
388
    {
389
        [sortedLogFileNames addObject:[logFileInfo fileName]];
390
    }
391
 
392
    return sortedLogFileNames;
393
}
394
 
395
/**
396
 * Just like the unsortedLogFileInfos method, but sorts the array.
397
 * The items in the array are sorted by creation date.
398
 * The first item in the array will be the most recently created log file.
399
**/
400
- (NSArray *)sortedLogFileInfos
401
{
402
    return [[self unsortedLogFileInfos] sortedArrayUsingSelector:@selector(reverseCompareByCreationDate:)];
403
}
404
 
405
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
406
#pragma mark Creation
407
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
408
 
409
/**
410
 * Generates log file name with format "<app name> <date> <time>.log"
411
 * Example: MobileSafari 2013-12-03 17-14.log
412
**/
413
- (NSString *)generateLogFileNameWithAttempt:(NSUInteger)attempt
414
{
415
    NSString *appName = [self applicationName];
416
 
417
    NSDateFormatter *dateFormatter = [self logFileDateFormatter];
418
    NSString *formattedDate = [dateFormatter stringFromDate:[NSDate date]];
419
 
420
    if (attempt > 1)
421
    {
422
        return [NSString stringWithFormat:@"%@ %@ %lu.log", appName, formattedDate, (unsigned long)attempt];
423
    }
424
    else
425
    {
426
        return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate];
427
    }
428
}
429
 
430
/**
431
 * Generates a new unique log file path, and creates the corresponding log file.
432
**/
433
- (NSString *)createNewLogFile
434
{
435
    // Generate a random log file name, and create the file (if there isn't a collision)
436
 
437
    NSString *logsDirectory = [self logsDirectory];
438
    NSUInteger attempt = 1;
439
    do
440
    {
441
        NSString *fileName = [self generateLogFileNameWithAttempt:attempt];
442
 
443
        NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
444
 
445
        if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
446
        {
447
            NSLogVerbose(@"DDLogFileManagerDefault: Creating new log file: %@", fileName);
448
 
449
            NSDictionary *attributes = nil;
450
 
451
        #if TARGET_OS_IPHONE
452
             // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
453
             //
454
             // But in case if app is able to launch from background we need to have an ability to open log file any time we
455
             // want (even if device is locked). Thats why that attribute have to be changed to
456
             // NSFileProtectionCompleteUntilFirstUserAuthentication.
457
 
458
            NSString *key = doesAppRunInBackground() ?
459
                NSFileProtectionCompleteUntilFirstUserAuthentication : NSFileProtectionCompleteUnlessOpen;
460
 
461
            attributes = @{ NSFileProtectionKey : key };
462
        #endif
463
 
464
            [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:attributes];
465
 
466
            // Since we just created a new log file, we may need to delete some old log files
467
            [self deleteOldLogFiles];
468
 
469
            return filePath;
470
        } else {
471
            attempt++;
472
        }
473
 
474
    } while(YES);
475
}
476
 
477
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
478
#pragma mark Utility
479
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
480
 
481
- (NSString *)applicationName
482
{
483
    static NSString *_appName;
484
    static dispatch_once_t onceToken;
485
 
486
    dispatch_once(&onceToken, ^{
487
    #if TARGET_OS_IPHONE
488
        _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
489
 
490
        if (! _appName)
491
        {
492
            _appName = [[NSProcessInfo processInfo] processName];
493
        }
494
    #else
495
        _appName = [[NSProcessInfo processInfo] processName];
496
    #endif
497
 
498
        if (! _appName)
499
        {
500
            _appName = @"";
501
        }
502
    });
503
 
504
    return _appName;
505
}
506
 
507
@end
508
 
509
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
510
#pragma mark -
511
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
512
 
513
@implementation DDLogFileFormatterDefault
514
 
515
- (id)init
516
{
517
    return [self initWithDateFormatter:nil];
518
}
519
 
520
- (instancetype)initWithDateFormatter:(NSDateFormatter *)aDateFormatter
521
{
522
    if ((self = [super init]))
523
    {
524
        if (aDateFormatter)
525
        {
526
            dateFormatter = aDateFormatter;
527
        }
528
        else
529
        {
530
            dateFormatter = [[NSDateFormatter alloc] init];
531
            [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
532
            [dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
533
        }
534
    }
535
    return self;
536
}
537
 
538
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage
539
{
540
    NSString *dateAndTime = [dateFormatter stringFromDate:(logMessage->timestamp)];
541
 
542
    return [NSString stringWithFormat:@"%@  %@", dateAndTime, logMessage->logMsg];
543
}
544
 
545
@end
546
 
547
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
548
#pragma mark -
549
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
550
 
551
@implementation DDFileLogger
552
 
553
- (id)init
554
{
555
    DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];
556
 
557
    return [self initWithLogFileManager:defaultLogFileManager];
558
}
559
 
560
- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager
561
{
562
    if ((self = [super init]))
563
    {
564
        maximumFileSize = DEFAULT_LOG_MAX_FILE_SIZE;
565
        rollingFrequency = DEFAULT_LOG_ROLLING_FREQUENCY;
566
 
567
        logFileManager = aLogFileManager;
568
 
569
        formatter = [[DDLogFileFormatterDefault alloc] init];
570
    }
571
    return self;
572
}
573
 
574
- (void)dealloc
575
{
576
    [currentLogFileHandle synchronizeFile];
577
    [currentLogFileHandle closeFile];
578
 
579
    if (currentLogFileVnode) {
580
        dispatch_source_cancel(currentLogFileVnode);
581
        currentLogFileVnode = NULL;
582
    }
583
 
584
    if (rollingTimer)
585
    {
586
        dispatch_source_cancel(rollingTimer);
587
        rollingTimer = NULL;
588
    }
589
}
590
 
591
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
592
#pragma mark Properties
593
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
594
 
595
@synthesize logFileManager;
596
 
597
- (unsigned long long)maximumFileSize
598
{
599
    __block unsigned long long result;
600
 
601
    dispatch_block_t block = ^{
602
        result = maximumFileSize;
603
    };
604
 
605
    // The design of this method is taken from the DDAbstractLogger implementation.
606
    // For extensive documentation please refer to the DDAbstractLogger implementation.
607
 
608
    // Note: The internal implementation MUST access the maximumFileSize variable directly,
609
    // This method is designed explicitly for external access.
610
    //
611
    // Using "self." syntax to go through this method will cause immediate deadlock.
612
    // This is the intended result. Fix it by accessing the ivar directly.
613
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
614
 
615
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
616
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
617
 
618
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
619
 
620
    dispatch_sync(globalLoggingQueue, ^{
621
        dispatch_sync(loggerQueue, block);
622
    });
623
 
624
    return result;
625
}
626
 
627
- (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize
628
{
629
    dispatch_block_t block = ^{ @autoreleasepool {
630
 
631
        maximumFileSize = newMaximumFileSize;
632
        [self maybeRollLogFileDueToSize];
633
 
634
    }};
635
 
636
    // The design of this method is taken from the DDAbstractLogger implementation.
637
    // For extensive documentation please refer to the DDAbstractLogger implementation.
638
 
639
    // Note: The internal implementation MUST access the maximumFileSize variable directly,
640
    // This method is designed explicitly for external access.
641
    //
642
    // Using "self." syntax to go through this method will cause immediate deadlock.
643
    // This is the intended result. Fix it by accessing the ivar directly.
644
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
645
 
646
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
647
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
648
 
649
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
650
 
651
    dispatch_async(globalLoggingQueue, ^{
652
        dispatch_async(loggerQueue, block);
653
    });
654
}
655
 
656
- (NSTimeInterval)rollingFrequency
657
{
658
    __block NSTimeInterval result;
659
 
660
    dispatch_block_t block = ^{
661
        result = rollingFrequency;
662
    };
663
 
664
    // The design of this method is taken from the DDAbstractLogger implementation.
665
    // For extensive documentation please refer to the DDAbstractLogger implementation.
666
 
667
    // Note: The internal implementation should access the rollingFrequency variable directly,
668
    // This method is designed explicitly for external access.
669
    //
670
    // Using "self." syntax to go through this method will cause immediate deadlock.
671
    // This is the intended result. Fix it by accessing the ivar directly.
672
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
673
 
674
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
675
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
676
 
677
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
678
 
679
    dispatch_sync(globalLoggingQueue, ^{
680
        dispatch_sync(loggerQueue, block);
681
    });
682
 
683
    return result;
684
}
685
 
686
- (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency
687
{
688
    dispatch_block_t block = ^{ @autoreleasepool {
689
 
690
        rollingFrequency = newRollingFrequency;
691
        [self maybeRollLogFileDueToAge];
692
    }};
693
 
694
    // The design of this method is taken from the DDAbstractLogger implementation.
695
    // For extensive documentation please refer to the DDAbstractLogger implementation.
696
 
697
    // Note: The internal implementation should access the rollingFrequency variable directly,
698
    // This method is designed explicitly for external access.
699
    //
700
    // Using "self." syntax to go through this method will cause immediate deadlock.
701
    // This is the intended result. Fix it by accessing the ivar directly.
702
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
703
 
704
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
705
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
706
 
707
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
708
 
709
    dispatch_async(globalLoggingQueue, ^{
710
        dispatch_async(loggerQueue, block);
711
    });
712
}
713
 
714
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
715
#pragma mark File Rolling
716
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
717
 
718
- (void)scheduleTimerToRollLogFileDueToAge
719
{
720
    if (rollingTimer)
721
    {
722
        dispatch_source_cancel(rollingTimer);
723
        rollingTimer = NULL;
724
    }
725
 
726
    if (currentLogFileInfo == nil || rollingFrequency <= 0.0)
727
    {
728
        return;
729
    }
730
 
731
    NSDate *logFileCreationDate = [currentLogFileInfo creationDate];
732
 
733
    NSTimeInterval ti = [logFileCreationDate timeIntervalSinceReferenceDate];
734
    ti += rollingFrequency;
735
 
736
    NSDate *logFileRollingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ti];
737
 
738
    NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
739
 
740
    NSLogVerbose(@"DDFileLogger: logFileCreationDate: %@", logFileCreationDate);
741
    NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
742
 
743
    rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue);
744
 
745
    dispatch_source_set_event_handler(rollingTimer, ^{ @autoreleasepool {
746
 
747
        [self maybeRollLogFileDueToAge];
748
 
749
    }});
750
 
751
    #if !OS_OBJECT_USE_OBJC
752
    dispatch_source_t theRollingTimer = rollingTimer;
753
    dispatch_source_set_cancel_handler(rollingTimer, ^{
754
        dispatch_release(theRollingTimer);
755
    });
756
    #endif
757
 
758
    uint64_t delay = (uint64_t)([logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC);
759
    dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay);
760
 
761
    dispatch_source_set_timer(rollingTimer, fireTime, DISPATCH_TIME_FOREVER, 1.0);
762
    dispatch_resume(rollingTimer);
763
}
764
 
765
 
766
- (void)rollLogFile
767
{
768
    [self rollLogFileWithCompletionBlock:nil];
769
}
770
 
771
- (void)rollLogFileWithCompletionBlock:(void (^)())completionBlock
772
{
773
    // This method is public.
774
    // We need to execute the rolling on our logging thread/queue.
775
 
776
    dispatch_block_t block = ^{ @autoreleasepool {
777
 
778
        [self rollLogFileNow];
779
 
780
        if (completionBlock)
781
        {
782
            dispatch_async(dispatch_get_main_queue(), ^{
783
                completionBlock();
784
            });
785
        }
786
    }};
787
 
788
    // The design of this method is taken from the DDAbstractLogger implementation.
789
    // For extensive documentation please refer to the DDAbstractLogger implementation.
790
 
791
    if ([self isOnInternalLoggerQueue])
792
    {
793
        block();
794
    }
795
    else
796
    {
797
        dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
798
        NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
799
 
800
        dispatch_async(globalLoggingQueue, ^{
801
            dispatch_async(loggerQueue, block);
802
        });
803
    }
804
}
805
 
806
- (void)rollLogFileNow
807
{
808
    NSLogVerbose(@"DDFileLogger: rollLogFileNow");
809
 
810
 
811
    if (currentLogFileHandle == nil) return;
812
 
813
    [currentLogFileHandle synchronizeFile];
814
    [currentLogFileHandle closeFile];
815
    currentLogFileHandle = nil;
816
 
817
    currentLogFileInfo.isArchived = YES;
818
 
819
    if ([logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)])
820
    {
821
        [logFileManager didRollAndArchiveLogFile:(currentLogFileInfo.filePath)];
822
    }
823
 
824
    currentLogFileInfo = nil;
825
 
826
    if (currentLogFileVnode) {
827
        dispatch_source_cancel(currentLogFileVnode);
828
        currentLogFileVnode = NULL;
829
    }
830
 
831
    if (rollingTimer)
832
    {
833
        dispatch_source_cancel(rollingTimer);
834
        rollingTimer = NULL;
835
    }
836
}
837
 
838
- (void)maybeRollLogFileDueToAge
839
{
840
    if (rollingFrequency > 0.0 && currentLogFileInfo.age >= rollingFrequency)
841
    {
842
        NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
843
 
844
        [self rollLogFileNow];
845
    }
846
    else
847
    {
848
        [self scheduleTimerToRollLogFileDueToAge];
849
    }
850
}
851
 
852
- (void)maybeRollLogFileDueToSize
853
{
854
    // This method is called from logMessage.
855
    // Keep it FAST.
856
 
857
    // Note: Use direct access to maximumFileSize variable.
858
    // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons).
859
 
860
    if (maximumFileSize > 0)
861
    {
862
        unsigned long long fileSize = [currentLogFileHandle offsetInFile];
863
 
864
        if (fileSize >= maximumFileSize)
865
        {
866
            NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize);
867
 
868
            [self rollLogFileNow];
869
        }
870
    }
871
}
872
 
873
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
874
#pragma mark File Logging
875
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
876
 
877
/**
878
 * Returns the log file that should be used.
879
 * If there is an existing log file that is suitable,
880
 * within the constraints of maximumFileSize and rollingFrequency, then it is returned.
881
 *
882
 * Otherwise a new file is created and returned.
883
**/
884
- (DDLogFileInfo *)currentLogFileInfo
885
{
886
    if (currentLogFileInfo == nil)
887
    {
888
        NSArray *sortedLogFileInfos = [logFileManager sortedLogFileInfos];
889
 
890
        if ([sortedLogFileInfos count] > 0)
891
        {
892
            DDLogFileInfo *mostRecentLogFileInfo = [sortedLogFileInfos objectAtIndex:0];
893
 
894
            BOOL useExistingLogFile = YES;
895
            BOOL shouldArchiveMostRecent = NO;
896
 
897
            if (mostRecentLogFileInfo.isArchived)
898
            {
899
                useExistingLogFile = NO;
900
                shouldArchiveMostRecent = NO;
901
            }
902
            else if (maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= maximumFileSize)
903
            {
904
                useExistingLogFile = NO;
905
                shouldArchiveMostRecent = YES;
906
            }
907
            else if (rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= rollingFrequency)
908
            {
909
                useExistingLogFile = NO;
910
                shouldArchiveMostRecent = YES;
911
            }
912
 
913
 
914
        #if TARGET_OS_IPHONE
915
            // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
916
            //
917
            // But in case if app is able to launch from background we need to have an ability to open log file any time we
918
            // want (even if device is locked). Thats why that attribute have to be changed to
919
            // NSFileProtectionCompleteUntilFirstUserAuthentication.
920
            //
921
            // If previous log was created when app wasn't running in background, but now it is - we archive it and create
922
            // a new one.
923
 
924
            if (useExistingLogFile && doesAppRunInBackground()) {
925
                NSString *key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey];
926
 
927
                if (! [key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) {
928
                    useExistingLogFile = NO;
929
                    shouldArchiveMostRecent = YES;
930
                }
931
            }
932
        #endif
933
 
934
            if (useExistingLogFile)
935
            {
936
                NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", mostRecentLogFileInfo.fileName);
937
 
938
                currentLogFileInfo = mostRecentLogFileInfo;
939
            }
940
            else
941
            {
942
                if (shouldArchiveMostRecent)
943
                {
944
                    mostRecentLogFileInfo.isArchived = YES;
945
 
946
                    if ([logFileManager respondsToSelector:@selector(didArchiveLogFile:)])
947
                    {
948
                        [logFileManager didArchiveLogFile:(mostRecentLogFileInfo.filePath)];
949
                    }
950
                }
951
            }
952
        }
953
 
954
        if (currentLogFileInfo == nil)
955
        {
956
            NSString *currentLogFilePath = [logFileManager createNewLogFile];
957
 
958
            currentLogFileInfo = [[DDLogFileInfo alloc] initWithFilePath:currentLogFilePath];
959
        }
960
    }
961
 
962
    return currentLogFileInfo;
963
}
964
 
965
- (NSFileHandle *)currentLogFileHandle
966
{
967
    if (currentLogFileHandle == nil)
968
    {
969
        NSString *logFilePath = [[self currentLogFileInfo] filePath];
970
 
971
        currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
972
        [currentLogFileHandle seekToEndOfFile];
973
 
974
        if (currentLogFileHandle)
975
        {
976
            [self scheduleTimerToRollLogFileDueToAge];
977
 
978
            // Here we are monitoring the log file. In case if it would be deleted ormoved
979
            // somewhere we want to roll it and use a new one.
980
            currentLogFileVnode = dispatch_source_create(
981
                DISPATCH_SOURCE_TYPE_VNODE,
982
                [currentLogFileHandle fileDescriptor],
983
                DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME,
984
                loggerQueue
985
            );
986
 
987
            dispatch_source_set_event_handler(currentLogFileVnode, ^{ @autoreleasepool {
988
                NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one");
989
                [self rollLogFileNow];
990
            }});
991
 
992
            #if !OS_OBJECT_USE_OBJC
993
            dispatch_source_t vnode = currentLogFileVnode;
994
            dispatch_source_set_cancel_handler(currentLogFileVnode, ^{
995
                dispatch_release(vnode);
996
            });
997
            #endif
998
 
999
            dispatch_resume(currentLogFileVnode);
1000
        }
1001
    }
1002
 
1003
    return currentLogFileHandle;
1004
}
1005
 
1006
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1007
#pragma mark DDLogger Protocol
1008
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1009
 
1010
static int exception_count = 0;
1011
- (void)logMessage:(DDLogMessage *)logMessage
1012
{
1013
    NSString *logMsg = logMessage->logMsg;
1014
 
1015
    if (formatter)
1016
    {
1017
        logMsg = [formatter formatLogMessage:logMessage];
1018
    }
1019
 
1020
    if (logMsg)
1021
    {
1022
        if (![logMsg hasSuffix:@"\n"])
1023
        {
1024
            logMsg = [logMsg stringByAppendingString:@"\n"];
1025
        }
1026
 
1027
        NSData *logData = [logMsg dataUsingEncoding:NSUTF8StringEncoding];
1028
 
1029
        @try {
1030
            [[self currentLogFileHandle] writeData:logData];
1031
 
1032
            [self maybeRollLogFileDueToSize];
1033
        }
1034
        @catch (NSException *exception) {
1035
            exception_count++;
1036
            if (exception_count <= 10) {
1037
                NSLogError(@"DDFileLogger.logMessage: %@", exception);
1038
                if (exception_count == 10)
1039
                    NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them.");
1040
            }
1041
        }
1042
    }
1043
}
1044
 
1045
- (void)willRemoveLogger
1046
{
1047
    // If you override me be sure to invoke [super willRemoveLogger];
1048
 
1049
    [self rollLogFileNow];
1050
}
1051
 
1052
- (NSString *)loggerName
1053
{
1054
    return @"cocoa.lumberjack.fileLogger";
1055
}
1056
 
1057
@end
1058
 
1059
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1060
#pragma mark -
1061
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1062
 
1063
#if TARGET_IPHONE_SIMULATOR
1064
  #define XATTR_ARCHIVED_NAME  @"archived"
1065
#else
1066
  #define XATTR_ARCHIVED_NAME  @"lumberjack.log.archived"
1067
#endif
1068
 
1069
@implementation DDLogFileInfo
1070
 
1071
@synthesize filePath;
1072
 
1073
@dynamic fileName;
1074
@dynamic fileAttributes;
1075
@dynamic creationDate;
1076
@dynamic modificationDate;
1077
@dynamic fileSize;
1078
@dynamic age;
1079
 
1080
@dynamic isArchived;
1081
 
1082
 
1083
#pragma mark Lifecycle
1084
 
1085
+ (instancetype)logFileWithPath:(NSString *)aFilePath
1086
{
1087
    return [[self alloc] initWithFilePath:aFilePath];
1088
}
1089
 
1090
- (instancetype)initWithFilePath:(NSString *)aFilePath
1091
{
1092
    if ((self = [super init]))
1093
    {
1094
        filePath = [aFilePath copy];
1095
    }
1096
    return self;
1097
}
1098
 
1099
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1100
#pragma mark Standard Info
1101
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1102
 
1103
- (NSDictionary *)fileAttributes
1104
{
1105
    if (fileAttributes == nil)
1106
    {
1107
        fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1108
    }
1109
    return fileAttributes;
1110
}
1111
 
1112
- (NSString *)fileName
1113
{
1114
    if (fileName == nil)
1115
    {
1116
        fileName = [filePath lastPathComponent];
1117
    }
1118
    return fileName;
1119
}
1120
 
1121
- (NSDate *)modificationDate
1122
{
1123
    if (modificationDate == nil)
1124
    {
1125
        modificationDate = [[self fileAttributes] objectForKey:NSFileModificationDate];
1126
    }
1127
 
1128
    return modificationDate;
1129
}
1130
 
1131
- (NSDate *)creationDate
1132
{
1133
    if (creationDate == nil)
1134
    {
1135
        creationDate = [[self fileAttributes] objectForKey:NSFileCreationDate];
1136
    }
1137
    return creationDate;
1138
}
1139
 
1140
- (unsigned long long)fileSize
1141
{
1142
    if (fileSize == 0)
1143
    {
1144
        fileSize = [[[self fileAttributes] objectForKey:NSFileSize] unsignedLongLongValue];
1145
    }
1146
 
1147
    return fileSize;
1148
}
1149
 
1150
- (NSTimeInterval)age
1151
{
1152
    return [[self creationDate] timeIntervalSinceNow] * -1.0;
1153
}
1154
 
1155
- (NSString *)description
1156
{
1157
    return [@{@"filePath": self.filePath ?: @"",
1158
              @"fileName": self.fileName ?: @"",
1159
              @"fileAttributes": self.fileAttributes ?: @"",
1160
              @"creationDate": self.creationDate ?: @"",
1161
              @"modificationDate": self.modificationDate ?: @"",
1162
              @"fileSize": @(self.fileSize),
1163
              @"age": @(self.age),
1164
              @"isArchived": @(self.isArchived)} description];
1165
}
1166
 
1167
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1168
#pragma mark Archiving
1169
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1170
 
1171
- (BOOL)isArchived
1172
{
1173
 
1174
#if TARGET_IPHONE_SIMULATOR
1175
 
1176
    // Extended attributes don't work properly on the simulator.
1177
    // So we have to use a less attractive alternative.
1178
    // See full explanation in the header file.
1179
 
1180
    return [self hasExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
1181
 
1182
#else
1183
 
1184
    return [self hasExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
1185
 
1186
#endif
1187
}
1188
 
1189
- (void)setIsArchived:(BOOL)flag
1190
{
1191
 
1192
#if TARGET_IPHONE_SIMULATOR
1193
 
1194
    // Extended attributes don't work properly on the simulator.
1195
    // So we have to use a less attractive alternative.
1196
    // See full explanation in the header file.
1197
 
1198
    if (flag)
1199
        [self addExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
1200
    else
1201
        [self removeExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
1202
 
1203
#else
1204
 
1205
    if (flag)
1206
        [self addExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
1207
    else
1208
        [self removeExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
1209
 
1210
#endif
1211
}
1212
 
1213
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1214
#pragma mark Changes
1215
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1216
 
1217
- (void)reset
1218
{
1219
    fileName = nil;
1220
    fileAttributes = nil;
1221
    creationDate = nil;
1222
    modificationDate = nil;
1223
}
1224
 
1225
- (void)renameFile:(NSString *)newFileName
1226
{
1227
    // This method is only used on the iPhone simulator, where normal extended attributes are broken.
1228
    // See full explanation in the header file.
1229
 
1230
    if (![newFileName isEqualToString:[self fileName]])
1231
    {
1232
        NSString *fileDir = [filePath stringByDeletingLastPathComponent];
1233
 
1234
        NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
1235
 
1236
        NSLogVerbose(@"DDLogFileInfo: Renaming file: '%@' -> '%@'", self.fileName, newFileName);
1237
 
1238
        NSError *error = nil;
1239
        if (![[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newFilePath error:&error])
1240
        {
1241
            NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
1242
        }
1243
 
1244
        filePath = newFilePath;
1245
        [self reset];
1246
    }
1247
}
1248
 
1249
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1250
#pragma mark Attribute Management
1251
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1252
 
1253
#if TARGET_IPHONE_SIMULATOR
1254
 
1255
// Extended attributes don't work properly on the simulator.
1256
// So we have to use a less attractive alternative.
1257
// See full explanation in the header file.
1258
 
1259
- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName
1260
{
1261
    // This method is only used on the iPhone simulator, where normal extended attributes are broken.
1262
    // See full explanation in the header file.
1263
 
1264
    // Split the file name into components.
1265
    //
1266
    // log-ABC123.archived.uploaded.txt
1267
    //
1268
    // 0. log-ABC123
1269
    // 1. archived
1270
    // 2. uploaded
1271
    // 3. txt
1272
    //
1273
    // So we want to search for the attrName in the components (ignoring the first and last array indexes).
1274
 
1275
    NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
1276
 
1277
    // Watch out for file names without an extension
1278
 
1279
    NSUInteger count = [components count];
1280
    NSUInteger max = (count >= 2) ? count-1 : count;
1281
 
1282
    NSUInteger i;
1283
    for (i = 1; i < max; i++)
1284
    {
1285
        NSString *attr = [components objectAtIndex:i];
1286
 
1287
        if ([attrName isEqualToString:attr])
1288
        {
1289
            return YES;
1290
        }
1291
    }
1292
 
1293
    return NO;
1294
}
1295
 
1296
- (void)addExtensionAttributeWithName:(NSString *)attrName
1297
{
1298
    // This method is only used on the iPhone simulator, where normal extended attributes are broken.
1299
    // See full explanation in the header file.
1300
 
1301
    if ([attrName length] == 0) return;
1302
 
1303
    // Example:
1304
    // attrName = "archived"
1305
    //
1306
    // "log-ABC123.txt" -> "log-ABC123.archived.txt"
1307
 
1308
    NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
1309
 
1310
    NSUInteger count = [components count];
1311
 
1312
    NSUInteger estimatedNewLength = [[self fileName] length] + [attrName length] + 1;
1313
    NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
1314
 
1315
    if (count > 0)
1316
    {
1317
        [newFileName appendString:[components objectAtIndex:0]];
1318
    }
1319
 
1320
    NSString *lastExt = @"";
1321
 
1322
    NSUInteger i;
1323
    for (i = 1; i < count; i++)
1324
    {
1325
        NSString *attr = [components objectAtIndex:i];
1326
        if ([attr length] == 0)
1327
        {
1328
            continue;
1329
        }
1330
 
1331
        if ([attrName isEqualToString:attr])
1332
        {
1333
            // Extension attribute already exists in file name
1334
            return;
1335
        }
1336
 
1337
        if ([lastExt length] > 0)
1338
        {
1339
            [newFileName appendFormat:@".%@", lastExt];
1340
        }
1341
 
1342
        lastExt = attr;
1343
    }
1344
 
1345
    [newFileName appendFormat:@".%@", attrName];
1346
 
1347
    if ([lastExt length] > 0)
1348
    {
1349
        [newFileName appendFormat:@".%@", lastExt];
1350
    }
1351
 
1352
    [self renameFile:newFileName];
1353
}
1354
 
1355
- (void)removeExtensionAttributeWithName:(NSString *)attrName
1356
{
1357
    // This method is only used on the iPhone simulator, where normal extended attributes are broken.
1358
    // See full explanation in the header file.
1359
 
1360
    if ([attrName length] == 0) return;
1361
 
1362
    // Example:
1363
    // attrName = "archived"
1364
    //
1365
    // "log-ABC123.archived.txt" -> "log-ABC123.txt"
1366
 
1367
    NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
1368
 
1369
    NSUInteger count = [components count];
1370
 
1371
    NSUInteger estimatedNewLength = [[self fileName] length];
1372
    NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
1373
 
1374
    if (count > 0)
1375
    {
1376
        [newFileName appendString:[components objectAtIndex:0]];
1377
    }
1378
 
1379
    BOOL found = NO;
1380
 
1381
    NSUInteger i;
1382
    for (i = 1; i < count; i++)
1383
    {
1384
        NSString *attr = [components objectAtIndex:i];
1385
 
1386
        if ([attrName isEqualToString:attr])
1387
        {
1388
            found = YES;
1389
        }
1390
        else
1391
        {
1392
            [newFileName appendFormat:@".%@", attr];
1393
        }
1394
    }
1395
 
1396
    if (found)
1397
    {
1398
        [self renameFile:newFileName];
1399
    }
1400
}
1401
 
1402
#else
1403
 
1404
- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName
1405
{
1406
    const char *path = [filePath UTF8String];
1407
    const char *name = [attrName UTF8String];
1408
 
1409
    ssize_t result = getxattr(path, name, NULL, 0, 0, 0);
1410
 
1411
    return (result >= 0);
1412
}
1413
 
1414
- (void)addExtendedAttributeWithName:(NSString *)attrName
1415
{
1416
    const char *path = [filePath UTF8String];
1417
    const char *name = [attrName UTF8String];
1418
 
1419
    int result = setxattr(path, name, NULL, 0, 0, 0);
1420
 
1421
    if (result < 0)
1422
    {
1423
        NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %i", attrName, self.fileName, result);
1424
    }
1425
}
1426
 
1427
- (void)removeExtendedAttributeWithName:(NSString *)attrName
1428
{
1429
    const char *path = [filePath UTF8String];
1430
    const char *name = [attrName UTF8String];
1431
 
1432
    int result = removexattr(path, name, 0);
1433
 
1434
    if (result < 0 && errno != ENOATTR)
1435
    {
1436
        NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %i", attrName, self.fileName, result);
1437
    }
1438
}
1439
 
1440
#endif
1441
 
1442
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1443
#pragma mark Comparisons
1444
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1445
 
1446
- (BOOL)isEqual:(id)object
1447
{
1448
    if ([object isKindOfClass:[self class]])
1449
    {
1450
        DDLogFileInfo *another = (DDLogFileInfo *)object;
1451
 
1452
        return [filePath isEqualToString:[another filePath]];
1453
    }
1454
 
1455
    return NO;
1456
}
1457
 
1458
- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another
1459
{
1460
    NSDate *us = [self creationDate];
1461
    NSDate *them = [another creationDate];
1462
 
1463
    NSComparisonResult result = [us compare:them];
1464
 
1465
    if (result == NSOrderedAscending)
1466
        return NSOrderedDescending;
1467
 
1468
    if (result == NSOrderedDescending)
1469
        return NSOrderedAscending;
1470
 
1471
    return NSOrderedSame;
1472
}
1473
 
1474
- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another
1475
{
1476
    NSDate *us = [self modificationDate];
1477
    NSDate *them = [another modificationDate];
1478
 
1479
    NSComparisonResult result = [us compare:them];
1480
 
1481
    if (result == NSOrderedAscending)
1482
        return NSOrderedDescending;
1483
 
1484
    if (result == NSOrderedDescending)
1485
        return NSOrderedAscending;
1486
 
1487
    return NSOrderedSame;
1488
}
1489
 
1490
@end
1491
 
1492
#if TARGET_OS_IPHONE
1493
/**
1494
 * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
1495
 *
1496
 * But in case if app is able to launch from background we need to have an ability to open log file any time we
1497
 * want (even if device is locked). Thats why that attribute have to be changed to
1498
 * NSFileProtectionCompleteUntilFirstUserAuthentication.
1499
 */
1500
BOOL doesAppRunInBackground()
1501
{
1502
    BOOL answer = NO;
1503
 
1504
    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
1505
 
1506
    for (NSString *mode in backgroundModes) {
1507
        if (mode.length > 0) {
1508
            answer = YES;
1509
            break;
1510
        }
1511
    }
1512
 
1513
    return answer;
1514
}
1515
#endif
1516