优化下载皮肤❤️

This commit is contained in:
2025-12-23 15:26:32 +08:00
parent fbf9fe9f2a
commit 000d603241
5 changed files with 111 additions and 20 deletions

View File

@@ -24,6 +24,7 @@ static NSString * const kKBSkinPendingKindKey = @"kind";
static NSString * const kKBSkinPendingTimestampKey = @"timestamp";
static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames";
static NSString * const kKBSkinMetadataFileName = @"metadata.plist";
static NSString * const kKBSkinForceDownloadKey = @"force_download";
static NSString * const kKBSkinMetadataNameKey = @"name";
static NSString * const kKBSkinMetadataPreviewKey = @"preview";
static NSString * const kKBSkinMetadataZipKey = @"zip_url";
@@ -220,6 +221,11 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
NSString *skinId = skinJSON[@"id"] ?: @"remote";
NSString *name = skinJSON[@"name"] ?: skinId;
NSString *zipURL = skinJSON[@"zip_url"] ?: @"";
BOOL forceDownload = NO;
id forceValue = skinJSON[kKBSkinForceDownloadKey];
if ([forceValue respondsToSelector:@selector(boolValue)]) {
forceDownload = [forceValue boolValue];
}
// key_icons
// - key_icons使
@@ -258,6 +264,21 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
BOOL hasCachedAssets = (contents.count > 0);
NSString *bgPath = [skinRoot stringByAppendingPathComponent:@"background.png"];
BOOL useTempRoot = forceDownload;
NSString *tempToken = nil;
NSString *workingRoot = skinRoot;
NSString *workingIconsDir = iconsDir;
NSString *workingBgPath = bgPath;
if (useTempRoot) {
tempToken = [NSString stringWithFormat:@"%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000)];
NSString *tmpName = [NSString stringWithFormat:@"%@__tmp_%@", skinId, tempToken];
workingRoot = [skinsRoot stringByAppendingPathComponent:tmpName];
workingIconsDir = [workingRoot stringByAppendingPathComponent:@"icons"];
workingBgPath = [workingRoot stringByAppendingPathComponent:@"background.png"];
[fm removeItemAtPath:workingRoot error:nil];
}
NSLog(@"⬇️[SkinBridge] request id=%@ force=%d cached=%d zip=%@",
skinId, forceDownload, hasCachedAssets, zipURL);
dispatch_group_t group = dispatch_group_create();
__block BOOL zipOK = YES;
@@ -265,8 +286,8 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
__block NSError *innerError = nil;
#if __has_include(<SSZipArchive/SSZipArchive.h>)
// zip_url Zip
if (!hasCachedAssets && zipURL.length > 0) {
// zip_url Zip
if ((forceDownload || !hasCachedAssets) && zipURL.length > 0) {
dispatch_group_enter(group);
void (^handleZipData)(NSData *) = ^(NSData *data) {
@@ -277,15 +298,17 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
code:KBSkinBridgeErrorZipMissing
userInfo:@{NSLocalizedDescriptionKey: @"Zip data is empty"}];
}
NSLog(@"❌[SkinBridge] zip data empty id=%@", skinId);
dispatch_group_leave(group);
return;
}
NSLog(@"📦[SkinBridge] unzip start id=%@ temp=%d", skinId, useTempRoot);
// Zip
[fm createDirectoryAtPath:skinRoot
[fm createDirectoryAtPath:workingRoot
withIntermediateDirectories:YES
attributes:nil
error:NULL];
NSString *zipPath = [skinRoot stringByAppendingPathComponent:@"skin.zip"];
NSString *zipPath = [workingRoot stringByAppendingPathComponent:@"skin.zip"];
if (![data writeToFile:zipPath atomically:YES]) {
zipOK = NO;
if (!innerError) {
@@ -293,13 +316,14 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
code:KBSkinBridgeErrorUnzipFailed
userInfo:@{NSLocalizedDescriptionKey: @"Failed to write zip file"}];
}
NSLog(@"❌[SkinBridge] zip write failed id=%@", skinId);
dispatch_group_leave(group);
return;
}
NSError *unzipError = nil;
BOOL ok = [SSZipArchive unzipFileAtPath:zipPath
toDestination:skinRoot
toDestination:workingRoot
overwrite:YES
password:nil
error:&unzipError];
@@ -311,24 +335,22 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
code:KBSkinBridgeErrorUnzipFailed
userInfo:nil];
}
NSLog(@"❌[SkinBridge] unzip failed id=%@ error=%@", skinId, unzipError);
dispatch_group_leave(group);
return;
}
// 使 icons
didUnzip = YES;
//
// Skins/<skinId>/icons Skins/<skinId>/<>/icons
// icons background.png
BOOL isDir2 = NO;
NSArray *iconsContent = [fm contentsOfDirectoryAtPath:iconsDir error:NULL];
BOOL iconsValid = ([fm fileExistsAtPath:iconsDir isDirectory:&isDir2] && isDir2 && iconsContent.count > 0);
NSArray *iconsContent = [fm contentsOfDirectoryAtPath:workingIconsDir error:NULL];
BOOL iconsValid = ([fm fileExistsAtPath:workingIconsDir isDirectory:&isDir2] && isDir2 && iconsContent.count > 0);
if (!iconsValid) {
NSArray<NSString *> *subItems = [fm contentsOfDirectoryAtPath:skinRoot error:NULL];
NSArray<NSString *> *subItems = [fm contentsOfDirectoryAtPath:workingRoot error:NULL];
for (NSString *subName in subItems) {
if ([subName isEqualToString:@"icons"] || [subName isEqualToString:@"__MACOSX"]) continue;
NSString *nestedRoot = [skinRoot stringByAppendingPathComponent:subName];
NSString *nestedRoot = [workingRoot stringByAppendingPathComponent:subName];
BOOL isDirNested = NO;
if (![fm fileExistsAtPath:nestedRoot isDirectory:&isDirNested] || !isDirNested) continue;
@@ -338,14 +360,14 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
NSArray *nestedFiles = [fm contentsOfDirectoryAtPath:nestedIcons error:NULL];
if (nestedFiles.count > 0) {
// icons
[fm createDirectoryAtPath:iconsDir
[fm createDirectoryAtPath:workingIconsDir
withIntermediateDirectories:YES
attributes:nil
error:NULL];
// icons
for (NSString *fn in nestedFiles) {
NSString *from = [nestedIcons stringByAppendingPathComponent:fn];
NSString *to = [iconsDir stringByAppendingPathComponent:fn];
NSString *to = [workingIconsDir stringByAppendingPathComponent:fn];
[fm removeItemAtPath:to error:nil];
[fm moveItemAtPath:from toPath:to error:nil];
}
@@ -355,20 +377,65 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
// background.png skinRoot
NSString *nestedBg = [nestedRoot stringByAppendingPathComponent:@"background.png"];
if ([fm fileExistsAtPath:nestedBg]) {
[fm removeItemAtPath:bgPath error:nil];
[fm moveItemAtPath:nestedBg toPath:bgPath error:nil];
[fm removeItemAtPath:workingBgPath error:nil];
[fm moveItemAtPath:nestedBg toPath:workingBgPath error:nil];
}
}
}
if (useTempRoot) {
NSString *backupName = [NSString stringWithFormat:@"%@__bak_%@", skinId, (tempToken ?: @"0")];
NSString *backupRoot = [skinsRoot stringByAppendingPathComponent:backupName];
[fm removeItemAtPath:backupRoot error:nil];
NSError *swapError = nil;
BOOL movedOld = NO;
if ([fm fileExistsAtPath:skinRoot]) {
movedOld = [fm moveItemAtPath:skinRoot toPath:backupRoot error:&swapError];
if (!movedOld && swapError) {
zipOK = NO;
if (!innerError) {
innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorUnzipFailed
userInfo:@{NSLocalizedDescriptionKey: @"Failed to backup old skin"}];
}
NSLog(@"❌[SkinBridge] backup failed id=%@ error=%@", skinId, swapError);
dispatch_group_leave(group);
return;
}
}
BOOL movedNew = [fm moveItemAtPath:workingRoot toPath:skinRoot error:&swapError];
if (!movedNew || swapError) {
zipOK = NO;
if (!innerError) {
innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorUnzipFailed
userInfo:@{NSLocalizedDescriptionKey: @"Failed to replace skin assets"}];
}
if (movedOld) {
[fm moveItemAtPath:backupRoot toPath:skinRoot error:nil];
}
NSLog(@"❌[SkinBridge] replace failed id=%@ error=%@", skinId, swapError);
dispatch_group_leave(group);
return;
}
if (movedOld) {
[fm removeItemAtPath:backupRoot error:nil];
}
NSLog(@"🧹[SkinBridge] replaced old skin id=%@", skinId);
}
// 使 icons
didUnzip = YES;
NSLog(@"✅[SkinBridge] unzip done id=%@", skinId);
dispatch_group_leave(group);
};
#if __has_include("KBNetworkManager.h")
// http/https
NSLog(@"[SkinBridge] will GET zip: %@", zipURL);
NSLog(@"🌐[SkinBridge] will GET zip: %@", zipURL);
[KBHUD showWithStatus:@"正在下载..."];
[[KBNetworkManager shared] GETData:zipURL parameters:nil headers:nil completion:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"[SkinBridge] GET finished, error = %@", error);
NSLog(@"🌐[SkinBridge] GET finished id=%@ error=%@", skinId, error);
if (error || data.length == 0) {
zipOK = NO;
if (!innerError) {
@@ -399,6 +466,9 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
}
});
#endif
} else {
NSLog(@"[SkinBridge] skip download id=%@ force=%d cached=%d zip=%@",
skinId, forceDownload, hasCachedAssets, zipURL);
}
#else
zipOK = NO;
@@ -411,11 +481,12 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//
// B
BOOL hasAssets = (hasCachedAssets || didUnzip);
BOOL hasAssets = (didUnzip || (!forceDownload && hasCachedAssets));
if (!hasAssets) {
NSError *finalError = innerError ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorZipMissing
userInfo:@{NSLocalizedDescriptionKey: @"Zip resource not available"}];
NSLog(@"❌[SkinBridge] apply aborted id=%@ error=%@", skinId, finalError);
if (completion) completion(NO, finalError);
return;
}
@@ -459,6 +530,10 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
userInfo:nil];
}
if (completion) completion(ok, finalError);
NSLog(@"%@ [SkinBridge] apply %@ id=%@",
(ok ? @"✅" : @"❌"),
(ok ? @"ok" : @"failed"),
skinId);
if (ok) {
NSString *preview = [skinJSON[@"preview"] isKindOfClass:NSString.class] ? skinJSON[@"preview"] : nil;
[self recordInstalledSkinWithId:skinId