update: extract and store XML icons

These can then be used by the client.

#344
#392
This commit is contained in:
Hans-Christoph Steiner 2018-02-14 13:44:58 +01:00
parent 5713b54e0b
commit 40fac10ebc
3 changed files with 60 additions and 34 deletions

View File

@ -61,7 +61,7 @@ APK_PERMISSION_PAT = \
re.compile(".*(name='(?P<name>.*?)')(.*maxSdkVersion='(?P<maxSdkVersion>.*?)')?.*")
APK_FEATURE_PAT = re.compile(".*name='([^']*)'.*")
screen_densities = ['640', '480', '320', '240', '160', '120']
screen_densities = ['65534', '640', '480', '320', '240', '160', '120']
screen_resolutions = {
"xxxhdpi": '640',
"xxhdpi": '480',
@ -96,9 +96,10 @@ def px_to_dpi(px):
def get_icon_dir(repodir, density):
if density == '0':
if density == '0' or density == '65534':
return os.path.join(repodir, "icons")
return os.path.join(repodir, "icons-%s" % density)
else:
return os.path.join(repodir, "icons-%s" % density)
def get_icon_dirs(repodir):
@ -1377,7 +1378,7 @@ def process_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk=Fal
.format(apkfilename=apkfile) + str(e))
# extract icons from APK zip file
iconfilename = "%s.%s.png" % (apk['packageName'], apk['versionCode'])
iconfilename = "%s.%s" % (apk['packageName'], apk['versionCode'])
try:
empty_densities = extract_apk_icons(iconfilename, apk, apkzip, repodir)
finally:
@ -1464,6 +1465,8 @@ def extract_apk_icons(icon_filename, apk, apkzip, repo_dir):
if m and m.group(4) == 'png':
density = screen_resolutions[m.group(2)]
pngs[m.group(3) + '/' + density] = m.group(0)
icon_type = None
empty_densities = []
for density in screen_densities:
if density not in apk['icons_src']:
@ -1471,7 +1474,7 @@ def extract_apk_icons(icon_filename, apk, apkzip, repo_dir):
continue
icon_src = apk['icons_src'][density]
icon_dir = get_icon_dir(repo_dir, density)
icon_dest = os.path.join(icon_dir, icon_filename)
icon_type = '.png'
# Extract the icon files per density
if icon_src.endswith('.xml'):
@ -1482,59 +1485,68 @@ def extract_apk_icons(icon_filename, apk, apkzip, repo_dir):
icon_src = name
if icon_src.endswith('.xml'):
empty_densities.append(density)
continue
icon_type = '.xml'
icon_dest = os.path.join(icon_dir, icon_filename + icon_type)
try:
with open(icon_dest, 'wb') as f:
f.write(get_icon_bytes(apkzip, icon_src))
apk['icons'][density] = icon_filename
apk['icons'][density] = icon_filename + icon_type
except (zipfile.BadZipFile, ValueError, KeyError) as e:
logging.warning("Error retrieving icon file: %s %s", icon_dest, e)
del apk['icons_src'][density]
empty_densities.append(density)
if '-1' in apk['icons_src'] and not apk['icons_src']['-1'].endswith('.xml'):
# '-1' here is a remnant of the parsing of aapt output, meaning "no DPI specified"
if '-1' in apk['icons_src']:
icon_src = apk['icons_src']['-1']
icon_path = os.path.join(get_icon_dir(repo_dir, '0'), icon_filename)
icon_type = icon_src[-4:]
icon_path = os.path.join(get_icon_dir(repo_dir, '0'), icon_filename + icon_type)
with open(icon_path, 'wb') as f:
f.write(get_icon_bytes(apkzip, icon_src))
im = None
try:
im = Image.open(icon_path)
dpi = px_to_dpi(im.size[0])
for density in screen_densities:
if density in apk['icons']:
break
if density == screen_densities[-1] or dpi >= int(density):
apk['icons'][density] = icon_filename
shutil.move(icon_path,
os.path.join(get_icon_dir(repo_dir, density), icon_filename))
empty_densities.remove(density)
break
except Exception as e:
logging.warning(_("Failed reading {path}: {error}")
.format(path=icon_path, error=e))
finally:
if im and hasattr(im, 'close'):
im.close()
if icon_type == '.png':
im = None
try:
im = Image.open(icon_path)
dpi = px_to_dpi(im.size[0])
for density in screen_densities:
if density in apk['icons']:
break
if density == screen_densities[-1] or dpi >= int(density):
apk['icons'][density] = icon_filename
shutil.move(icon_path,
os.path.join(get_icon_dir(repo_dir, density), icon_filename))
empty_densities.remove(density)
break
except Exception as e:
logging.warning(_("Failed reading {path}: {error}")
.format(path=icon_path, error=e))
finally:
if im and hasattr(im, 'close'):
im.close()
if apk['icons']:
apk['icon'] = icon_filename
apk['icon'] = icon_filename + icon_type
return empty_densities
def fill_missing_icon_densities(empty_densities, icon_filename, apk, repo_dir):
"""
Resize existing icons for densities missing in the APK to ensure all densities are available
Resize existing PNG icons for densities missing in the APK to ensure all densities are available
:param empty_densities: A list of icon densities that are missing
:param icon_filename: A string representing the icon's file name
:param apk: A populated dictionary containing APK metadata. Needs to have 'icons' key
:param repo_dir: The directory of the APK's repository
"""
icon_filename += '.png'
# First try resizing down to not lose quality
last_density = None
for density in screen_densities:
if density == '65534': # not possible to generate 'anydpi' from other densities
continue
if density not in empty_densities:
last_density = density
continue

Binary file not shown.

View File

@ -253,14 +253,14 @@ class UpdateTest(unittest.TestCase):
apps = fdroidserver.metadata.read_metadata(xref=True)
knownapks = fdroidserver.common.KnownApks()
apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
self.assertEqual(len(apks), 12)
self.assertEqual(len(apks), 13)
apk = apks[0]
self.assertEqual(apk['packageName'], 'com.politedroid')
self.assertEqual(apk['versionCode'], 3)
self.assertEqual(apk['minSdkVersion'], '3')
self.assertEqual(apk['targetSdkVersion'], '3')
self.assertFalse('maxSdkVersion' in apk)
apk = apks[5]
apk = apks[6]
self.assertEqual(apk['packageName'], 'obb.main.oldversion')
self.assertEqual(apk['versionCode'], 1444412523)
self.assertEqual(apk['minSdkVersion'], '4')
@ -298,7 +298,6 @@ class UpdateTest(unittest.TestCase):
raise Exception('This test must be run in the "tests/" subdir')
apk_info = fdroidserver.update.scan_apk('org.dyndns.fules.ck_20.apk')
self.assertEqual(apk_info['icons_src'], {'240': 'res/drawable-hdpi-v4/icon_launcher.png',
'120': 'res/drawable-ldpi-v4/icon_launcher.png',
'160': 'res/drawable-mdpi-v4/icon_launcher.png',
@ -319,6 +318,21 @@ class UpdateTest(unittest.TestCase):
self.assertEqual(apk_info['hashType'], 'sha256')
self.assertEqual(apk_info['targetSdkVersion'], '8')
apk_info = fdroidserver.update.scan_apk('org.bitbucket.tickytacky.mirrormirror_4.apk')
self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable-mdpi/mirror.png',
'-1': 'res/drawable-mdpi/mirror.png'})
apk_info = fdroidserver.update.scan_apk('repo/info.zwanenburg.caffeinetile_4.apk')
self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable/ic_coffee_on.xml',
'-1': 'res/drawable/ic_coffee_on.xml'})
apk_info = fdroidserver.update.scan_apk('repo/com.politedroid_6.apk')
self.assertEqual(apk_info['icons_src'], {'120': 'res/drawable-ldpi-v4/icon.png',
'160': 'res/drawable-mdpi-v4/icon.png',
'240': 'res/drawable-hdpi-v4/icon.png',
'320': 'res/drawable-xhdpi-v4/icon.png',
'-1': 'res/drawable-mdpi-v4/icon.png'})
def test_scan_apk_no_sig(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)
@ -527,7 +541,7 @@ class UpdateTest(unittest.TestCase):
knownapks = fdroidserver.common.KnownApks()
apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
fdroidserver.update.translate_per_build_anti_features(apps, apks)
self.assertEqual(len(apks), 12)
self.assertEqual(len(apks), 13)
foundtest = False
for apk in apks:
if apk['packageName'] == 'com.politedroid' and apk['versionCode'] == 3: