1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-09-11 15:13:27 +02:00

🛠️ update.py: finish minimal IPA support

This add a few missing pieces to get IPA support working. (added and
lastUpdated dates + caching for ipa files)
This commit is contained in:
Michael Pöhn 2023-11-30 16:27:09 +01:00 committed by Hans-Christoph Steiner
parent 60371093e2
commit ea9374ecf6

View File

@ -525,7 +525,10 @@ def insert_obbs(repodir, apps, apks):
def version_string_to_int(version): def version_string_to_int(version):
"""Approximately convert a [Major].[Minor].[Patch] version string """
Convert sermver version designation to version code.
Approximately convert a [Major].[Minor].[Patch] version string
consisting of numeric characters (0-9) and periods to a number. The consisting of numeric characters (0-9) and periods to a number. The
exponents are chosen such that it still fits in the 64bit JSON/Android range. exponents are chosen such that it still fits in the 64bit JSON/Android range.
""" """
@ -536,46 +539,73 @@ def version_string_to_int(version):
return major * 10**12 + minor * 10**6 + patch return major * 10**12 + minor * 10**6 + patch
def process_ipa(repodir, apks): def parse_ipa(ipa_path, file_size, sha256):
"""Scan the .ipa files in a given repo directory. from biplist import readPlist
ipa = {
"apkName": os.path.basename(ipa_path),
"hash": sha256,
"hashType": "sha256",
"size": file_size,
}
with zipfile.ZipFile(ipa_path) as ipa_zip:
for info in ipa_zip.infolist():
if re.match("Payload/[^/]*.app/Info.plist", info.filename):
with ipa_zip.open(info) as plist_file:
plist = readPlist(plist_file)
ipa["packageName"] = plist["CFBundleIdentifier"]
# https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring
ipa["versionCode"] = version_string_to_int(plist["CFBundleShortVersionString"])
ipa["versionName"] = plist["CFBundleShortVersionString"]
ipa["usage"] = {k: v for k, v in plist.items() if 'Usage' in k}
return ipa
def scan_repo_for_ipas(apkcache, repodir, knownapks):
"""Scan for IPA files in a given repo directory.
Parameters Parameters
---------- ----------
apkcache
cache dictionary containting cached file infos from previous runs
repodir repodir
repo directory to scan repo directory to scan
apps knownapks
list of current, valid apps list of all known files, as per metadata.read_metadata
apks
current information on all APKs Returns
-------
ipas
list of file infos for ipa files in ./repo folder
cachechanged
ture if new ipa files were found and added to `apkcache`
""" """
def ipaWarnDelete(f, msg): cachechanged = False
logging.warning(msg + ' ' + f) ipas = []
if options.delete_unknown: for ipa_path in glob.glob(os.path.join(repodir, '*.ipa')):
logging.error(_("Deleting unknown file: {path}").format(path=f)) ipa_name = os.path.basename(ipa_path)
os.remove(f)
ipas = glob.glob(os.path.join(repodir, '*.ipa')) file_size = os.stat(ipa_path).st_size
if ipas: if file_size == 0:
from biplist import readPlist raise FDroidException(_('{path} is zero size!')
for f in ipas: .format(path=ipa_path))
ipa = {}
apks.append(ipa)
ipa["apkName"] = os.path.basename(f) sha256 = common.sha256sum(ipa_path)
ipa["hash"] = common.sha256sum(f) ipa = apkcache.get(ipa_name, {})
ipa["hashType"] = "sha256"
ipa["size"] = os.path.getsize(f)
with zipfile.ZipFile(f) as ipa_zip: if ipa.get('hash') != sha256:
for info in ipa_zip.infolist(): ipa = parse_ipa(ipa_path, file_size, sha256)
if re.match("Payload/[^/]*.app/Info.plist", info.filename): apkcache[ipa_name] = ipa
with ipa_zip.open(info) as plist_file: cachechanged = True
plist = readPlist(plist_file)
ipa["packageName"] = plist["CFBundleIdentifier"] added = knownapks.recordapk(ipa_name, ipa['packageName'])
# https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring if added:
ipa["versionCode"] = version_string_to_int(plist["CFBundleShortVersionString"]) ipa['added'] = added
ipa["versionName"] = plist["CFBundleShortVersionString"]
ipa["usage"] = {k: v for k, v in plist.items() if 'Usage' in k} ipas.append(ipa)
return ipas, cachechanged
def translate_per_build_anti_features(apps, apks): def translate_per_build_anti_features(apps, apks):
@ -1175,7 +1205,10 @@ def insert_localized_app_metadata(apps):
def scan_repo_files(apkcache, repodir, knownapks, use_date_from_file=False): def scan_repo_files(apkcache, repodir, knownapks, use_date_from_file=False):
"""Scan a repo for all files with an extension except APK/OBB. """Scan a repo for all files with an extension except APK/OBB/IPA.
This allows putting all kinds of files into repostories. E.g. Media Files,
Zip archives, ...
Parameters Parameters
---------- ----------
@ -1192,22 +1225,29 @@ def scan_repo_files(apkcache, repodir, knownapks, use_date_from_file=False):
repo_files = [] repo_files = []
repodir = repodir.encode() repodir = repodir.encode()
for name in os.listdir(repodir): for name in os.listdir(repodir):
# skip files based on file extensions, that are handled elsewhere
file_extension = common.get_file_extension(name) file_extension = common.get_file_extension(name)
if file_extension in ('apk', 'obb', 'ipa'): if file_extension in ('apk', 'obb', 'ipa'):
continue continue
# skip source tarballs generated by fdroidserver
filename = os.path.join(repodir, name) filename = os.path.join(repodir, name)
name_utf8 = name.decode() name_utf8 = name.decode()
if filename.endswith(b'_src.tar.gz'): if filename.endswith(b'_src.tar.gz'):
logging.debug(_('skipping source tarball: {path}') logging.debug(_('skipping source tarball: {path}')
.format(path=filename.decode())) .format(path=filename.decode()))
continue continue
# skip all other files generated by fdroidserver
if not common.is_repo_file(filename): if not common.is_repo_file(filename):
continue continue
stat = os.stat(filename) stat = os.stat(filename)
if stat.st_size == 0: if stat.st_size == 0:
raise FDroidException(_('{path} is zero size!') raise FDroidException(_('{path} is zero size!')
.format(path=filename)) .format(path=filename))
# load file infos from cache if not stale
shasum = common.sha256sum(filename) shasum = common.sha256sum(filename)
usecache = False usecache = False
if name_utf8 in apkcache: if name_utf8 in apkcache:
@ -1220,6 +1260,7 @@ def scan_repo_files(apkcache, repodir, knownapks, use_date_from_file=False):
logging.debug(_("Ignoring stale cache data for {apkfilename}") logging.debug(_("Ignoring stale cache data for {apkfilename}")
.format(apkfilename=name_utf8)) .format(apkfilename=name_utf8))
# scan file if info wasn't in cache
if not usecache: if not usecache:
logging.debug(_("Processing {apkfilename}").format(apkfilename=name_utf8)) logging.debug(_("Processing {apkfilename}").format(apkfilename=name_utf8))
repo_file = collections.OrderedDict() repo_file = collections.OrderedDict()
@ -2182,7 +2223,6 @@ def prepare_apps(apps, apks, repodir):
------- -------
the relevant subset of apps (as a deepcopy) the relevant subset of apps (as a deepcopy)
""" """
process_ipa(repodir, apks)
apps_with_packages = get_apps_with_packages(apps, apks) apps_with_packages = get_apps_with_packages(apps, apks)
apply_info_from_latest_apk(apps_with_packages, apks) apply_info_from_latest_apk(apps_with_packages, apks)
insert_funding_yml_donation_links(apps) insert_funding_yml_donation_links(apps)
@ -2309,6 +2349,11 @@ def main():
options.use_date_from_apk) options.use_date_from_apk)
cachechanged = cachechanged or fcachechanged cachechanged = cachechanged or fcachechanged
apks += files apks += files
ipas, icachechanged = scan_repo_for_ipas(apkcache, repodirs[0], knownapks)
cachechanged = cachechanged or icachechanged
apks += ipas
appid_has_apks = set() appid_has_apks = set()
appid_has_repo_files = set() appid_has_repo_files = set()
remove_apks = [] remove_apks = []