Github每日精选(第23期):macOS下的开源清理工具lemon-cleaner

lemon-cleaner柠檬清理

lemon-cleaner 柠檬清理,开源没几天就获得了3k个点赞,大家对macos 下的清理工具兴趣还是很大的。

github下的地址在这里

在这里插入图片描述
腾讯柠檬清理是针对macOS系统专属制定的清理工具。主要功能包括重复文件和相似照片的识别、软件的定制化垃圾扫描、可视化的全盘空间分析、内存释放、浏览器隐私清理以及设备实时状态的监控等。重点聚焦清理功能,对上百款软件提供定制化的清理方案,提供专业的清理建议,帮助用户轻松完成一键式清理。

在这里插入图片描述

技术模块
  • Lemon:主工程。
  • LemonMonitor:状态栏。
  • LemonBigOldFile:用于大文件清理。
  • LemonDuplicateFile:用于重复文件清理。
  • LemonPhotoClean:用于相似照片清理。
  • LemonUninstaller:用于应用卸载。
  • LemonPrivacyClean:用于隐私清理。
  • LemonLoginItemManager:用于开机启动管理。
  • LemonSpaceAnalyse:用于磁盘空间分析。
  • LemonFileMove:用于文件搬家。
  • LemonHardware:用于硬件信息获取。
  • LemonNetSpeed:用于网络测速。
  • LemonCleaner:用于实际清理操作。
主要代码分析

lemon-cleaner 的主要代码分了几个部分,我们看两个部分的内容,1.他是怎么找到相同重复文件的,2.开机启动管理。

LemonDuplicateFile用于重复文件清理

找重复文件一个好的方法就是文件的md5

#pragma mark-
#pragma mark 查找重复文件


// insert file to hash dictionary
// key of dictionary is hash string
// fileList是返回的 array
- (void)insertHashDictionary:(NSArray *)fileList process:(float)process {
    @autoreleasepool {
        if ([fileList count] < 2)
            return;
        
        //resultDict 用 md5做 key,item 做 value
        NSMutableDictionary *resultDict = [NSMutableDictionary dictionary];

        // 文件大小
        uint32 chunkSize = 1024 * 1024;

        // 读取前面10MB数据
        @try{
//            for (QMFileItem *fileItem in fileList) {
//
//            }
            
            if ([fileList count] == 0)
                return;
            
            // 多线程计算md5(前10m 的数据)
            // dispatch_apply 阻塞的 apply 对 sync group 的封装
            dispatch_apply(fileList.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
                @autoreleasepool{
                    if (index >= [fileList count]) {
                        return;
                    }
                    QMFileItem *fileItem = [fileList objectAtIndex:index];
                    if (fileItem.hashStr)
                        return;
                    
                    UInt64 fileSize = [fileItem fileSize];
                    NSFileHandle *innerFileHandle;
                    innerFileHandle = [NSFileHandle
                                  fileHandleForReadingAtPath:fileItem.filePath];
                NSString *md5Str = nil;
                @autoreleasepool{
                    NSMutableData *headData = [NSMutableData dataWithCapacity:MIN(fileSize, 10 * chunkSize)];
                    
                    // 读取10m 的数据
                    uint64 offset = 0;
                    NSData *readData = nil;
                    do {
                        @autoreleasepool{
                            @try {
                                readData = [innerFileHandle readDataOfLength:chunkSize];
                            } @catch (NSException *exception) {
                                NSLog(@"%s, exception = %@", __FUNCTION__, exception);
                                readData = nil;
                            } @finally {
                            
                            }
                            
                            if (readData)
                                [headData appendData:readData];
                            else
                                break;
                            offset += [readData length];
                            [innerFileHandle seekToFileOffset:offset];
                        }
                    } while ([readData length] > 0 && offset < 10 * chunkSize);
                    
                    [innerFileHandle closeFile];
                    if (headData.length == 0)
                        return;
                    
                    if (!self->showDelegate) {
                        self->_stopped = YES;
                        return;
                    }
                    
                    if (self.stopped)
                        return;
                    
                    //计算 md5
                    md5Str = (__bridge_transfer NSString *) (FileMD5HashWithData(headData, 0));
                    // 小文件用 md5 做 hash, 大文件用 md5 + crc 做 hash
                }
                    fileSize = [fileItem fileSize];
                    BOOL bigFile = NO;
                    if (fileSize > 1024 * 1024 * 10)
                        bigFile = YES;
                    else
                        fileItem.hashStr = md5Str;
                    
                    // 大文件需要计算crc
                    if (bigFile) {
                        // 头部hash和crc作为新的key
                        dispatch_sync(self->md5Queue, ^{
                            
                            NSMutableArray *sameHashArray = resultDict[md5Str];
                            if (sameHashArray == nil) {
                                sameHashArray = [NSMutableArray arrayWithCapacity:10];
                                resultDict[md5Str] = sameHashArray;
                            }
                            [sameHashArray addObject:fileItem];
                        });
                    }
                }
                
            });
            
            // 大文件进行crc算法比对
            if (resultDict.count > 0) {
                
                for (NSString *md5Str in [resultDict allKeys]) {
                    NSMutableArray *sameHashArray = resultDict[md5Str];
                    if ([sameHashArray count] <= 1)
                        continue;
                    
                    for (QMFileItem *fileItem in sameHashArray) {
                        @autoreleasepool{
                        // 计算文件的crc
                         NSFileHandle *fileHandle = [NSFileHandle
                                                    fileHandleForReadingAtPath:fileItem.filePath];
                        
                        uLong crc = crc32(0L, Z_NULL, 0);
                        uint64 offset = 0;
                        uint32 itemChunkSize = 1024 * 1024;     //Read 1M
                        [fileHandle seekToFileOffset:10 * itemChunkSize];
                        NSData *data = [fileHandle readDataOfLength:itemChunkSize];
                        
                        while ([data length] > 0) {
                            
                            @autoreleasepool {
                                //Make sure for the next line you choose the appropriate string encoding.
                                
                                crc = crc32(crc, data.bytes, (uInt) data.length);
                                /* PERFORM STRING PROCESSING HERE */
                                
                                /* END STRING PROCESSING */
                                offset += [data length];
                                
                                [fileHandle seekToFileOffset:offset];
                                data = [fileHandle readDataOfLength:itemChunkSize];
                                
                                if (!showDelegate) {
                                    _stopped = YES;
                                    return;
                                }
                                
                                // 显示文件比对进度.
                                CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
                                if(now - _lastFileCompareTime >= 0.3){
                                    
                                    NSString *dotString = @"";
                                    if(_compareDotNum % 3 == 0){
                                        dotString = @".";
                                    }else if(_compareDotNum % 3 == 1){
                                        dotString = @"..";
                                    }else if(_compareDotNum % 3 == 2){
                                        dotString = @"...";
                                    }
                                    
                                    NSString *processString = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"QMDuplicateFiles_insertHashDictionary_NSString_1", nil, [NSBundle bundleForClass:[self class]], @""),[SizeHelper getFileSizeStringBySize:fileItem.fileSize], dotString];
                                    
                                    if(showDelegate){
                                        [showDelegate progressRate:process progressStr:processString];
                                    }
                                    _lastFileCompareTime = now;
                                    _compareDotNum ++;
                                }
                                
                                
                                if (self.stopped) {
                                    return;
                                }
                               
                            }
                        }
                        
                        // 头部hash和crc作为新的key
                        NSString *md5Hash = [NSString stringWithFormat:@"%@_%lu", md5Str, crc];
                        fileItem.hashStr = md5Hash;
                        
                        [fileHandle closeFile];

                        if (self.stopped)
                            return;
                    }
                  }
                }
            }
        }@catch(NSException *exception){
            NSLog(@"duplicate file compare error : %@", exception);
        }@finally{
            
        }
       
    }
}
LemonLoginItemManager用于开机启动管理

获取开机启动项的信息,在界面上添加。

#pragma mark -- Launch service
/**
 获取Launch plist数据
 */
- (NSArray *)getLaunchServiceItems {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSMutableArray *launchServiceItemArray = [[NSMutableArray alloc]init];
    for (NSString *directoryPath in self.launchServiceFilePathArray) {
        NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:directoryPath error:NULL];
        for (NSString *fileName in fileNames) {
            if ([self needFilterFile:fileName]) {
                continue;
            }
            if ([self.delegate respondsToSelector:@selector(needFilterLaunchServiceFile:)]) {
                if ([self.delegate needFilterLaunchServiceFile:fileName]) {
                    continue;
                }
            }
            NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
            NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:filePath];
            NSString *label = dict[LAUNCH_SERVICE_LABEL];
            if (!label || [label isEqualToString:@""]) {
                continue;
            }
            QMAppLaunchItem *launchItem = [[QMAppLaunchItem alloc] initWithLaunchFilePath:filePath itemType:LoginItemTypeService];
            launchItem.label = label;
            launchItem.isEnable = [self serviceIsEnabledWithItem:launchItem];;
//            NSLog(@"%s, launch login itme filePath : %@",__FUNCTION__, filePath);
            //将launch plist文件与App进行匹配;
            //1. 先通过bundleId进行匹配,如果plist文件名包含bundleId,则匹配成功
            //2. 分析plist文件,与其中包含的App进行匹配
            QMLocalApp *localApp = [self getAppInfoWithFileName:fileName];
            if (localApp) {
                launchItem.appName = localApp.appName;
                launchItem.appPath = localApp.bundlePath;
            } else if(dict) {
                NSArray *array = dict[LAUNCH_SERVICE_PROGRAM_ARGUMENTS];
                if(array && array.count > 0){
                    NSString *programPath = array[0];
                    //判断是否包含app
                    if (([programPath containsString:@".app/"] || [programPath hasSuffix:@"app"])) {
                        NSRange range = [programPath rangeOfString:@".app"];
                        if (range.location != NSNotFound) {
                            NSUInteger index = range.location + 4;
                            NSString *appPath = [programPath substringToIndex:index];
                            launchItem.appPath = appPath;
                            launchItem.appName = [appPath lastPathComponent];
                        }
                    }
                }
            }
            [launchServiceItemArray addObject:launchItem];
        }
        
    }
    NSLog(@"%s, launchItemArray count : %lu",__FUNCTION__, (unsigned long)launchServiceItemArray.count);
    return launchServiceItemArray;
}

添加各个部分:

#pragma mark -- handle data

- (void)handleData {
    //以App进行分类:将同一个App的启动项包装成一个LMAppLoginItemInfo,
    //LMAppLoginItemInfo中添加launchd service item数据
    for (QMBaseLoginItem *loginItem in self.launchItemArray) {
        if(loginItem.appName) {
            LMAppLoginItemInfo *itemInfo = [self getLoginItemInfoWithAppName:loginItem.appName];
            itemInfo.appPath = loginItem.appPath;
            [itemInfo addLaunchItem:loginItem];
        } else {
            //如果没有匹配到App,则归纳为未知应用
            if (!self.otherLoginItemInfo) {
                NSString *localString = LMLocalizedString(@"LemonLoginItemManagerViewController_unknown_app", self.class);
                self.otherLoginItemInfo = [[LMAppLoginItemInfo alloc] initWithAppName:localString];
            }
            [self.otherLoginItemInfo addLaunchItem:loginItem];
        }
    }
    //添加app login item数据
    for (QMBaseLoginItem *loginItem in self.appLoginItemArray) {
        LMAppLoginItemInfo *itemInfo = [self getLoginItemInfoWithAppName:loginItem.appName];
        itemInfo.appPath = loginItem.appPath;
        [itemInfo addAppLoginItem:loginItem];
    }
    
    //添加系统登录项数据
    for (QMBaseLoginItem *loginItem in self.systemLoginItemArray) {
        LMAppLoginItemInfo *itemInfo = [self getLoginItemInfoWithAppName:loginItem.appName];
        itemInfo.appPath = loginItem.appPath;
        [itemInfo addAppLoginItem:loginItem];
    }
    
    //将未知应用添加到数据源末尾
    if (self.otherLoginItemInfo) {
        [self.dataArray addObject:self.otherLoginItemInfo];
    }
    
    for (LMAppLoginItemInfo *itemInfo in self.dataArray) {
        NSArray *loginItemDataArray = itemInfo.getLoginItemData;
        NSArray *launchItemDataArray = itemInfo.getLaunchItemData;
        //统计App启动项的数量
        itemInfo.totalItemCount = loginItemDataArray.count + launchItemDataArray.count;
        [itemInfo updateEnableStatus];
        //统计App启动项类型和数量,以及每类对应的数据
        if (loginItemDataArray) {
            [itemInfo addAppLoginItemTypeInfoWithArray:loginItemDataArray];
        }
        if (launchItemDataArray) {
            [itemInfo addLaunchItemTypeInfoWithArray:launchItemDataArray];
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go2coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值