402 lines
12 KiB
Matlab
402 lines
12 KiB
Matlab
|
|
//
|
||
|
|
// DDLogFileInfo.m
|
||
|
|
// NVLogManagerDemo
|
||
|
|
//
|
||
|
|
// Created by cxb on 2023/5/10.
|
||
|
|
// Copyright © 2023 com.zhouxi. All rights reserved.
|
||
|
|
//
|
||
|
|
|
||
|
|
#import "DDLogFileInfo.h"
|
||
|
|
static NSString * const kDDXAttrArchivedName = @"guru.log.archived";
|
||
|
|
|
||
|
|
@interface DDLogFileInfo () {
|
||
|
|
__strong NSString *_filePath;
|
||
|
|
__strong NSString *_fileName;
|
||
|
|
|
||
|
|
__strong NSDictionary *_fileAttributes;
|
||
|
|
|
||
|
|
__strong NSDate *_creationDate;
|
||
|
|
__strong NSDate *_modificationDate;
|
||
|
|
|
||
|
|
unsigned long long _fileSize;
|
||
|
|
}
|
||
|
|
|
||
|
|
#if TARGET_IPHONE_SIMULATOR
|
||
|
|
|
||
|
|
// Old implementation of extended attributes on the simulator.
|
||
|
|
|
||
|
|
- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName;
|
||
|
|
- (void)_removeExtensionAttributeWithName:(NSString *)attrName;
|
||
|
|
|
||
|
|
#endif
|
||
|
|
|
||
|
|
@end
|
||
|
|
@implementation DDLogFileInfo
|
||
|
|
|
||
|
|
@synthesize filePath;
|
||
|
|
|
||
|
|
@dynamic fileName;
|
||
|
|
@dynamic fileAttributes;
|
||
|
|
@dynamic creationDate;
|
||
|
|
@dynamic modificationDate;
|
||
|
|
@dynamic fileSize;
|
||
|
|
@dynamic age;
|
||
|
|
|
||
|
|
@dynamic isArchived;
|
||
|
|
|
||
|
|
#pragma mark Lifecycle
|
||
|
|
|
||
|
|
+ (instancetype)logFileWithPath:(NSString *)aFilePath {
|
||
|
|
if (!aFilePath) return nil;
|
||
|
|
return [[self alloc] initWithFilePath:aFilePath];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (instancetype)initWithFilePath:(NSString *)aFilePath {
|
||
|
|
NSParameterAssert(aFilePath);
|
||
|
|
if ((self = [super init])) {
|
||
|
|
filePath = [aFilePath copy];
|
||
|
|
}
|
||
|
|
|
||
|
|
return self;
|
||
|
|
}
|
||
|
|
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
#pragma mark Standard Info
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
|
||
|
|
- (NSDictionary *)fileAttributes {
|
||
|
|
if (_fileAttributes == nil && filePath != nil) {
|
||
|
|
__autoreleasing NSError *error = nil;
|
||
|
|
_fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
|
||
|
|
if (!_fileAttributes) {
|
||
|
|
NSLogError(@"DDLogFileInfo: Failed to read file attributes: %@", error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return _fileAttributes ?: @{};
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSString *)fileName {
|
||
|
|
if (_fileName == nil) {
|
||
|
|
_fileName = [filePath lastPathComponent];
|
||
|
|
}
|
||
|
|
|
||
|
|
return _fileName;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSDate *)modificationDate {
|
||
|
|
if (_modificationDate == nil) {
|
||
|
|
_modificationDate = self.fileAttributes[NSFileModificationDate];
|
||
|
|
}
|
||
|
|
|
||
|
|
return _modificationDate;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSDate *)creationDate {
|
||
|
|
if (_creationDate == nil) {
|
||
|
|
_creationDate = self.fileAttributes[NSFileCreationDate];
|
||
|
|
}
|
||
|
|
|
||
|
|
return _creationDate;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (unsigned long long)fileSize {
|
||
|
|
if (_fileSize == 0) {
|
||
|
|
_fileSize = [self.fileAttributes[NSFileSize] unsignedLongLongValue];
|
||
|
|
}
|
||
|
|
|
||
|
|
return _fileSize;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSTimeInterval)age {
|
||
|
|
return -[[self creationDate] timeIntervalSinceNow];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (BOOL)isSymlink {
|
||
|
|
return self.fileAttributes[NSFileType] == NSFileTypeSymbolicLink;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSString *)description {
|
||
|
|
return [@{ @"filePath": self.filePath ? : @"",
|
||
|
|
@"fileName": self.fileName ? : @"",
|
||
|
|
@"fileAttributes": self.fileAttributes ? : @"",
|
||
|
|
@"creationDate": self.creationDate ? : @"",
|
||
|
|
@"modificationDate": self.modificationDate ? : @"",
|
||
|
|
@"fileSize": @(self.fileSize),
|
||
|
|
@"age": @(self.age),
|
||
|
|
@"isArchived": @(self.isArchived) } description];
|
||
|
|
}
|
||
|
|
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
#pragma mark Archiving
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
|
||
|
|
- (BOOL)isArchived {
|
||
|
|
return [self hasExtendedAttributeWithName:kDDXAttrArchivedName];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)setIsArchived:(BOOL)flag {
|
||
|
|
if (flag) {
|
||
|
|
[self addExtendedAttributeWithName:kDDXAttrArchivedName];
|
||
|
|
} else {
|
||
|
|
[self removeExtendedAttributeWithName:kDDXAttrArchivedName];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
#pragma mark Changes
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
|
||
|
|
- (void)reset {
|
||
|
|
_fileName = nil;
|
||
|
|
_fileAttributes = nil;
|
||
|
|
_creationDate = nil;
|
||
|
|
_modificationDate = nil;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)renameFile:(NSString *)newFileName {
|
||
|
|
// This method is only used on the iPhone simulator, where normal extended attributes are broken.
|
||
|
|
// See full explanation in the header file.
|
||
|
|
|
||
|
|
if (![newFileName isEqualToString:[self fileName]]) {
|
||
|
|
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||
|
|
NSString *fileDir = [filePath stringByDeletingLastPathComponent];
|
||
|
|
NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
|
||
|
|
|
||
|
|
// We only want to assert when we're not using the simulator, as we're "archiving" a log file with this method in the sim
|
||
|
|
// (in which case the file might not exist anymore and neither does it parent folder).
|
||
|
|
#if defined(DEBUG) && (!defined(TARGET_IPHONE_SIMULATOR) || !TARGET_IPHONE_SIMULATOR)
|
||
|
|
BOOL directory = NO;
|
||
|
|
[fileManager fileExistsAtPath:fileDir isDirectory:&directory];
|
||
|
|
NSAssert(directory, @"Containing directory must exist.");
|
||
|
|
#endif
|
||
|
|
|
||
|
|
__autoreleasing NSError *error = nil;
|
||
|
|
BOOL success = [fileManager removeItemAtPath:newFilePath error:&error];
|
||
|
|
if (!success && error.code != NSFileNoSuchFileError) {
|
||
|
|
NSLogError(@"DDLogFileInfo: Error deleting archive (%@): %@", self.fileName, error);
|
||
|
|
}
|
||
|
|
|
||
|
|
success = [fileManager moveItemAtPath:filePath toPath:newFilePath error:&error];
|
||
|
|
|
||
|
|
// When a log file is deleted, moved or renamed on the simulator, we attempt to rename it as a
|
||
|
|
// result of "archiving" it, but since the file doesn't exist anymore, needless error logs are printed
|
||
|
|
// We therefore ignore this error, and assert that the directory we are copying into exists (which
|
||
|
|
// is the only other case where this error code can come up).
|
||
|
|
#if TARGET_IPHONE_SIMULATOR
|
||
|
|
if (!success && error.code != NSFileNoSuchFileError)
|
||
|
|
#else
|
||
|
|
if (!success)
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
|
||
|
|
}
|
||
|
|
|
||
|
|
filePath = newFilePath;
|
||
|
|
[self reset];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
#pragma mark Attribute Management
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
|
||
|
|
#if TARGET_IPHONE_SIMULATOR
|
||
|
|
|
||
|
|
// Old implementation of extended attributes on the simulator.
|
||
|
|
|
||
|
|
// Extended attributes were not working properly on the simulator
|
||
|
|
// due to misuse of setxattr() function.
|
||
|
|
// Now that this is fixed in the new implementation, we want to keep
|
||
|
|
// backward compatibility with previous simulator installations.
|
||
|
|
|
||
|
|
static NSString * const kDDExtensionSeparator = @".";
|
||
|
|
|
||
|
|
static NSString *_xattrToExtensionName(NSString *attrName) {
|
||
|
|
static NSDictionary<NSString *, NSString *>* _xattrToExtensionNameMap;
|
||
|
|
static dispatch_once_t _token;
|
||
|
|
dispatch_once(&_token, ^{
|
||
|
|
_xattrToExtensionNameMap = @{ kDDXAttrArchivedName: @"archived" };
|
||
|
|
});
|
||
|
|
return [_xattrToExtensionNameMap objectForKey:attrName];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName {
|
||
|
|
// This method is only used on the iPhone simulator for backward compatibility reason.
|
||
|
|
|
||
|
|
// Split the file name into components. File name may have various format, but generally
|
||
|
|
// structure is same:
|
||
|
|
//
|
||
|
|
// <name part>.<extension part> and <name part>.archived.<extension part>
|
||
|
|
// or
|
||
|
|
// <name part> and <name part>.archived
|
||
|
|
//
|
||
|
|
// So we want to search for the attrName in the components (ignoring the first array index).
|
||
|
|
|
||
|
|
NSArray *components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator];
|
||
|
|
|
||
|
|
// Watch out for file names without an extension
|
||
|
|
|
||
|
|
for (NSUInteger i = 1; i < components.count; i++) {
|
||
|
|
NSString *attr = components[i];
|
||
|
|
|
||
|
|
if ([attrName isEqualToString:attr]) {
|
||
|
|
return YES;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return NO;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)_removeExtensionAttributeWithName:(NSString *)attrName {
|
||
|
|
// This method is only used on the iPhone simulator for backward compatibility reason.
|
||
|
|
|
||
|
|
if ([attrName length] == 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Example:
|
||
|
|
// attrName = "archived"
|
||
|
|
//
|
||
|
|
// "mylog.archived.txt" -> "mylog.txt"
|
||
|
|
// "mylog.archived" -> "mylog"
|
||
|
|
|
||
|
|
NSArray *components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator];
|
||
|
|
|
||
|
|
NSUInteger count = [components count];
|
||
|
|
|
||
|
|
NSUInteger estimatedNewLength = [[self fileName] length];
|
||
|
|
NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
|
||
|
|
|
||
|
|
if (count > 0) {
|
||
|
|
[newFileName appendString:components[0]];
|
||
|
|
}
|
||
|
|
|
||
|
|
BOOL found = NO;
|
||
|
|
|
||
|
|
NSUInteger i;
|
||
|
|
|
||
|
|
for (i = 1; i < count; i++) {
|
||
|
|
NSString *attr = components[i];
|
||
|
|
|
||
|
|
if ([attrName isEqualToString:attr]) {
|
||
|
|
found = YES;
|
||
|
|
} else {
|
||
|
|
[newFileName appendString:kDDExtensionSeparator];
|
||
|
|
[newFileName appendString:attr];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (found) {
|
||
|
|
[self renameFile:newFileName];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif /* if TARGET_IPHONE_SIMULATOR */
|
||
|
|
|
||
|
|
- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName {
|
||
|
|
const char *path = [filePath fileSystemRepresentation];
|
||
|
|
const char *name = [attrName UTF8String];
|
||
|
|
BOOL hasExtendedAttribute = NO;
|
||
|
|
char buffer[1];
|
||
|
|
|
||
|
|
ssize_t result = getxattr(path, name, buffer, 1, 0, 0);
|
||
|
|
|
||
|
|
// Fast path
|
||
|
|
if (result > 0 && buffer[0] == '\1') {
|
||
|
|
hasExtendedAttribute = YES;
|
||
|
|
}
|
||
|
|
// Maintain backward compatibility, but fix it for future checks
|
||
|
|
else if (result >= 0) {
|
||
|
|
hasExtendedAttribute = YES;
|
||
|
|
|
||
|
|
[self addExtendedAttributeWithName:attrName];
|
||
|
|
}
|
||
|
|
#if TARGET_IPHONE_SIMULATOR
|
||
|
|
else if ([self _hasExtensionAttributeWithName:_xattrToExtensionName(attrName)]) {
|
||
|
|
hasExtendedAttribute = YES;
|
||
|
|
|
||
|
|
[self addExtendedAttributeWithName:attrName];
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
return hasExtendedAttribute;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)addExtendedAttributeWithName:(NSString *)attrName {
|
||
|
|
const char *path = [filePath fileSystemRepresentation];
|
||
|
|
const char *name = [attrName UTF8String];
|
||
|
|
|
||
|
|
int result = setxattr(path, name, "\1", 1, 0, 0);
|
||
|
|
|
||
|
|
if (result < 0) {
|
||
|
|
if (errno != ENOENT) {
|
||
|
|
NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %s",
|
||
|
|
attrName,
|
||
|
|
filePath,
|
||
|
|
strerror(errno));
|
||
|
|
} else {
|
||
|
|
NSLogDebug(@"DDLogFileInfo: File does not exist in setxattr(%@, %@): error = %s",
|
||
|
|
attrName,
|
||
|
|
filePath,
|
||
|
|
strerror(errno));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#if TARGET_IPHONE_SIMULATOR
|
||
|
|
else {
|
||
|
|
[self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)];
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)removeExtendedAttributeWithName:(NSString *)attrName {
|
||
|
|
const char *path = [filePath fileSystemRepresentation];
|
||
|
|
const char *name = [attrName UTF8String];
|
||
|
|
|
||
|
|
int result = removexattr(path, name, 0);
|
||
|
|
|
||
|
|
if (result < 0 && errno != ENOATTR) {
|
||
|
|
NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %s",
|
||
|
|
attrName,
|
||
|
|
self.fileName,
|
||
|
|
strerror(errno));
|
||
|
|
}
|
||
|
|
|
||
|
|
#if TARGET_IPHONE_SIMULATOR
|
||
|
|
[self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)];
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
#pragma mark Comparisons
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
|
||
|
|
- (BOOL)isEqual:(id)object {
|
||
|
|
if ([object isKindOfClass:[self class]]) {
|
||
|
|
DDLogFileInfo *another = (DDLogFileInfo *)object;
|
||
|
|
|
||
|
|
return [filePath isEqualToString:[another filePath]];
|
||
|
|
}
|
||
|
|
|
||
|
|
return NO;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSUInteger)hash {
|
||
|
|
return [filePath hash];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another {
|
||
|
|
__auto_type us = [self creationDate];
|
||
|
|
__auto_type them = [another creationDate];
|
||
|
|
return [them compare:us];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another {
|
||
|
|
__auto_type us = [self modificationDate];
|
||
|
|
__auto_type them = [another modificationDate];
|
||
|
|
return [them compare:us];
|
||
|
|
}
|
||
|
|
|
||
|
|
@end
|
||
|
|
|