# -*- coding: utf-8 -*- # make_update_list.py # 包的格式必须符合这个标准 arpg_cn_1.0.0.25.apk import zipfile import os.path import os import sys import hashlib import shutil import platform import time # ************************************一些参数配置********************************************************star class CFG: # 是否比较全部包体,不是就比较gen_obb_file内容 IS_COMPARISION_ALL = True # ************************************一些参数配置********************************************************end Ipa_Streamingassets_Path = 'Payload/ChestQuestMiniRPG.app/Data/Raw' Apk_Streamingassets_Path = 'assets' Aab_Streamingassets_Path = "base_assets/assets" Manifest_Suffix = '.manifest' Bundle_Hash_File_Name = 'BundleHash.txt' Bundle_Hash_Dict = {} def CalcMD5(filepath): with open(filepath, 'rb') as f: md5obj = hashlib.md5() md5obj.update(f.read()) hash = md5obj.hexdigest() f.close() return hash def get_language_from_filename(filename): return 'en' # splitname=filename.split('_') # return splitname[1] def get_version_from_filename(filename): splitname = filename.split('_') # 兼容 arpg_google_oversea_1.0.162_20230207_234802.aab 类型文件名 # 当数组索引为2的item等于oversea时, 返回index为3的item #return splitname[3] if splitname[2] == "oversea" else splitname[2] return splitname[3] if "." in splitname[3] else splitname[2] # end get_version_from_filename #文件名字符串转时间戳 def get_daytime_from_filename(filename): splitname = filename.split('_') dayIndex = 3 if(splitname[2] == "oversea"): dayIndex = 4 strday = splitname[dayIndex] strday = strday[0:8] strtime = "000000" if(len(splitname) > dayIndex + 1): strtimetmp = splitname[dayIndex + 1] strtimetmp = strtimetmp[0:6] #判断strtimetmp是数字还是字符串 if(strtimetmp.isdigit()): strtime = strtimetmp print('file:' + filename + ' day:' + strday + ' time:' + strtime) return int(time.mktime(time.strptime(strday + ' ' + strtime, "%Y%m%d %H%M%S"))) # 获取版本main.mid.min.build中的build def get_build_from_fullversion(strversion): return int(strversion.split('.')[-1]) # end get_build_from_fullversion # 获取版本main.mid.min.build中的main.mid.min def get_version_from_fullversion(strversion): sp = strversion.split('.') return sp[0] + '.' + sp[1] + '.' + sp[2] # end get_version_from_fullversion # 补丁文件所在路径,不带版本号的 def get_patch_path_exclude_version(fileType, newapkname): language = get_language_from_filename(newapkname) strOs = 'android' if fileType == 'ipa': strOs = 'ios' # [20220614] Linux环境生成路径修改 # rootPatchPath = f'/data/wwwroot/{fileType}_arpgpatch/' rootPatchPath = f'./{fileType}_patch/' if platform.system() == 'Windows': rootPatchPath = f'./{fileType}_patch/' return rootPatchPath + strOs + '/' + language # end get_patch_path_exclude_version # 补丁文件所在路径,带版本号的 def get_patch_path_include_version(fileType, newapkname): newfullversion = get_version_from_filename(newapkname) return get_patch_path_exclude_version(fileType, newapkname) + '/' + newfullversion # end get_patch_path_include_version def get_filelist_from_zip(zipfilename): zf = zipfile.ZipFile(zipfilename, 'r') zfinfolist = zf.infolist() for zfinfo in zfinfolist: filename = zfinfo.filename print(filename) zf.close() return # end get_filelist_from_zip def make_patch_from_apk(fileType, path, newapkname, oldapkname, streampath): # -4去掉.obb newdir = newapkname[0:-4] newlist = newdir + '.list' olddir = oldapkname[0:-4] oldlist = olddir + '.list' oldlistpath = streampath + '/' + oldlist language = get_language_from_filename(newapkname) newfullversion = get_version_from_filename(newapkname) oldfullversion = get_version_from_filename(oldapkname) # 构造zip包的名字 diffzipname = 'pandora_' + language + '_' + oldfullversion + '_' + newfullversion + '.upk' diffzippath = get_patch_path_include_version(fileType, newapkname) print('make diff zip file: ' + diffzipname) # + ' diffzippath: '+diffzippath print('make diff zip path: ' + diffzippath) # + ' diffzippath: '+diffzippath if not os.path.exists(diffzippath): os.makedirs(diffzippath) zf = zipfile.ZipFile(diffzippath + '/' + diffzipname, 'w', zipfile.ZIP_DEFLATED) olddict = {} # print oldlistpath # print 'oldlistpath: '+oldlistpath + ' streampath: '+streampath file = open(oldlistpath, 'rt') while True: line = file.readline() if line: line = line.strip('\n') # print line olddict[line] = 1 # print 'oldline: '+line else: break file.close() file = open(streampath + '/' + newlist, 'rt') for line in file: line = line.strip('\n') if not line in list(olddict.keys()): keysp = line.split('\t') # print 'keysp[0]:'+keysp[0] filepathinzip = keysp[0] if filepathinzip is not None: filename = streampath + '/' + newdir + '/' if fileType == 'obb' or fileType == 'apk': filename = filename + Apk_Streamingassets_Path elif fileType == 'aab': filename = filename + Aab_Streamingassets_Path else: filename = filename + Ipa_Streamingassets_Path filename = filename + '/' + keysp[0] zf.write(filename, filepathinzip) if os.path.exists(filename + Manifest_Suffix): zf.write(filename + Manifest_Suffix, keysp[0] + Manifest_Suffix) file.close() zf.close() return # end make_diff_zip_from_apk # 解压并且生成文件列表信息, 返回版本号 # path:apk保存目录 zipfilename:apk def extract_and_make_filelist(path, zipfilename, fileType): extractdirname = zipfilename[0:-4] print('find file:' + zipfilename + ', try extract zip files to dir:' + extractdirname) listfilename = extractdirname + '.list' extractdirfull = path + '/' + extractdirname # 从文件中读取版本号 fullversion = get_version_from_filename(zipfilename) print('fullversion:' + fullversion) if fullversion == None or fullversion == '': return None #之前是判断解压目录存在则不再重新解压,apk文件出错(比如zip格式损坏)的时候是有问题的,改成判断md5文件 #if not os.path.exists(extractdirfull) : md5zipfilename = path + '/' + zipfilename + '.md5' md5zipfileold = '' if os.path.exists(md5zipfilename): lf = open(md5zipfilename, 'r') md5zipfileold = lf.read() lf.close() md5zipfilehash = CalcMD5(path + '/' + zipfilename) if md5zipfilehash == md5zipfileold: print('zipfile md5 is same ' + md5zipfilehash) return fullversion print('new zipfile md5 ' + md5zipfilehash + " old " + md5zipfileold) print('extract zipfile to dir' + extractdirfull) #如果存在解压目录,先删除 if os.path.exists(extractdirfull): shutil.rmtree(extractdirfull) zf = zipfile.ZipFile(path + '/' + zipfilename, 'r') zf.extractall(extractdirfull) zf.close() dirneeddiff = extractdirfull if fileType == 'obb' or fileType == 'apk': dirneeddiff = extractdirfull + '/' + Apk_Streamingassets_Path elif fileType == 'aab': dirneeddiff = extractdirfull + '/' + Aab_Streamingassets_Path else: dirneeddiff = extractdirfull + '/' + Ipa_Streamingassets_Path dirneeddiff = dirneeddiff.replace('\\', '/') print(f"extract_and_make_filelist {zipfilename}") iBuild = get_build_from_fullversion(fullversion) # 需要忽略的目录 skipdiffdir = dirneeddiff + '/bin' lf = open(path + '/' + listfilename, 'wt') for root, dirs, files in os.walk(dirneeddiff): for filename in files: onefile = os.path.join(root, filename) onefile = onefile.replace('\\', '/') if onefile.find(skipdiffdir) != -1: continue namegroup = os.path.splitext(onefile) # 忽略后缀是.manifest的文件 if namegroup[1] == Manifest_Suffix: continue # 计算MD5 filehash = CalcMD5(onefile) # 把前面的绝对路径去掉 nPos = onefile.find(dirneeddiff) onefile = onefile[nPos + len(dirneeddiff) + 1:] fileinfostr = onefile + '\t' + filehash + '\n' # print fileinfostr lf.write(fileinfostr) lf.close() #全部成功后写入apk文件的md5文件,下次比较md5相等就可以提高效率 print('gen zipfile md5 ' + md5zipfilehash) lf = open(md5zipfilename, 'wt') lf.write(md5zipfilehash) lf.close() return fullversion # end extract_and_make_filelist # 获取配置比较的文件列表 def get_gen_apk_file_list(apkpath, fileType): gen_list = [] # 不存在就读取全部 filelist = os.listdir(apkpath) for filename in filelist: if (filename.split('.')[-1] == fileType): gen_list.append(filename) #判断有没有相同版本的文件,如果有,则删除旧文件相关资源,只关心最后一个版本 iMaxBuild = 0 maxbuildapkfilename = '' #重复的文件名 dupfilename = '' for filename in gen_list: fullversion = get_version_from_filename(filename) if fullversion == None or fullversion == '': return None iBuild = get_build_from_fullversion(fullversion) if (iBuild > iMaxBuild): iMaxBuild = iBuild maxbuildapkfilename = filename elif (iBuild == iMaxBuild): maxbuildtime = get_daytime_from_filename(maxbuildapkfilename) filetime = get_daytime_from_filename(filename) if(filetime > maxbuildtime): dupfilename = maxbuildapkfilename else: dupfilename = filename if(dupfilename != ''): print('find dup version apk file ' + dupfilename + ', delete it') gen_list.remove(dupfilename) #删除解压目录 extractdirname = dupfilename[0:-4] extractdirfull = apkpath + '/' + extractdirname if(os.path.exists(extractdirfull)): shutil.rmtree(extractdirfull) #删除md5文件 md5zipfilename = apkpath + '/' + dupfilename + '.md5' if(os.path.exists(md5zipfilename)): os.remove(md5zipfilename) #删除list文件 listfilename = extractdirfull + '.list' if(os.path.exists(listfilename)): os.remove(listfilename) #删除zip文件 zipfilename = apkpath + '/' + dupfilename if(os.path.exists(zipfilename)): os.remove(zipfilename) return gen_list # fType 为apk或者ipa def gen_all_patch_file_by_type(path, fileType): print('begin gen_all_patch_file_by_type for all ' + fileType + ' files') versionpath = os.path.abspath(path) apkpath = versionpath + '/' + fileType if not os.path.exists(apkpath): print('no ' + fileType + ' dir exist in path ' + versionpath + ', skip.') return filelist = get_gen_apk_file_list(apkpath, fileType) iMaxBuild = 0 newapkfilename = '' newfullversion = '' # 先获取最新的版本的那个文件 for filename in filelist: fullversion = extract_and_make_filelist(apkpath, filename, fileType) if fullversion == None or fullversion == '': print('get version failed...' + filename) return iBuild = get_build_from_fullversion(fullversion) if (iBuild > iMaxBuild): iMaxBuild = iBuild newapkfilename = filename newfullversion = fullversion print('last build file:' + newapkfilename) if iMaxBuild == 0: return # 遍历所有obb和最新版本的obb进行差异比较 for filename in filelist: if (filename != newapkfilename): make_patch_from_apk(fileType, apkpath, newapkfilename, filename, apkpath) # 最新包的路径 newapkPath = apkpath + '/' + newapkfilename # 更新目录, /data/wwwroot/android/en patchBasePath = get_patch_path_exclude_version(fileType, newapkfilename) # copy完整的obb文件到版本更新目录 updatePatchPath = patchBasePath + '/' + newfullversion if not os.path.exists(updatePatchPath): os.makedirs(updatePatchPath) shutil.copy(newapkPath, updatePatchPath) # 最后写版本文件, 版本服务器会读这个文件 version_file = patchBasePath + '/version' print('make version file for VersionServer: ' + version_file) lf = open(version_file, 'wt') lf.write(newfullversion) lf.close() print('Done.') print('now version is:' + newfullversion) # end gen_all_patch_file_by_type if __name__ == '__main__': platform_param = "all" if len(sys.argv) == 2: platform_param = sys.argv[1] if platform_param == "all": gen_all_patch_file_by_type('.', 'ipa') gen_all_patch_file_by_type('.', 'aab') gen_all_patch_file_by_type('.', 'apk') elif platform_param == "ios": gen_all_patch_file_by_type('.', 'ipa') elif platform_param == "android": gen_all_patch_file_by_type('.', 'obb') elif platform_param == "aab": gen_all_patch_file_by_type('.', 'aab') elif platform_param == "apk": gen_all_patch_file_by_type('.', 'apk')