Subversion Repositories Mobile Apps.GyroMouse

Rev

Blame | Last modification | View Log | Download | RSS feed

  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.  
  1517.