motion-logger 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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