@@ -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 : s kinRoot
[ fm createDirectoryAtPath : wor king Root
withIntermediateDirectories : YES
attributes : nil
error : NULL ] ;
NSString * zipPath = [ s kinRoot stringByAppendingPathComponent : @ "skin.zip" ] ;
NSString * zipPath = [ wor king Root 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 : s kinRoot
toDestination : wor king Root
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 : i consDir error : NULL ] ;
BOOL iconsValid = ( [ fm fileExistsAtPath : i consDir isDirectory : & isDir2 ] && isDir2 && iconsContent . count > 0 ) ;
NSArray * iconsContent = [ fm contentsOfDirectoryAtPath : workingI consDir error : NULL ] ;
BOOL iconsValid = ( [ fm fileExistsAtPath : workingI consDir isDirectory : & isDir2 ] && isDir2 && iconsContent . count > 0 ) ;
if ( ! iconsValid ) {
NSArray < NSString * > * subItems = [ fm contentsOfDirectoryAtPath : s kinRoot error : NULL ] ;
NSArray < NSString * > * subItems = [ fm contentsOfDirectoryAtPath : wor king Root error : NULL ] ;
for ( NSString * subName in subItems ) {
if ( [ subName isEqualToString : @ "icons" ] || [ subName isEqualToString : @ "__MACOSX" ] ) continue ;
NSString * nestedRoot = [ s kinRoot stringByAppendingPathComponent : subName ] ;
NSString * nestedRoot = [ wor king Root 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 : i consDir
[ fm createDirectoryAtPath : workingI consDir
withIntermediateDirectories : YES
attributes : nil
error : NULL ] ;
// 将 icons 下 所 有 文 件 上 移 一 层
for ( NSString * fn in nestedFiles ) {
NSString * from = [ nestedIcons stringByAppendingPathComponent : fn ] ;
NSString * to = [ i consDir stringByAppendingPathComponent : fn ] ;
NSString * to = [ workingI consDir 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 : b gPath error : nil ] ;
[ fm moveItemAtPath : nestedBg toPath : b gPath error : nil ] ;
[ fm removeItemAtPath : workingB gPath error : nil ] ;
[ fm moveItemAtPath : nestedBg toPath : workingB gPath 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