appjam 0.1.8.6 → 0.1.8.7

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