motion-logger 0.1.0

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