You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
14 KiB
415 lines
14 KiB
# -*- 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')
|
|
|