guru_sdk/guru_app/plugins/persistent/ios/Classes/LogLibrary/DDLogFileInfo.m

402 lines
12 KiB
Matlab
Raw Normal View History

//
// 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