from xml.dom.minidom import Document
from optparse import OptionParser
import time
+from pyasn1.error import PyAsn1Error
+from pyasn1.codec.der import decoder, encoder
+from pyasn1_modules import rfc2315
+from hashlib import md5
+
from PIL import Image
import logging
import common
import metadata
-from common import FDroidPopen
+from common import FDroidPopen, SilentPopen
from metadata import MetaDataException
yield os.path.join(repodir, "icons")
-def update_wiki(apps, apks):
+def update_wiki(apps, sortedids, apks):
"""Update the wiki
:param apps: fully populated list of all applications
wikiredircat = 'App Redirects'
import mwclient
site = mwclient.Site((config['wiki_protocol'], config['wiki_server']),
- path=config['wiki_path'])
+ path=config['wiki_path'])
site.login(config['wiki_user'], config['wiki_password'])
generated_pages = {}
generated_redirects = {}
- for app in apps:
+
+ for appid in sortedids:
+ app = apps[appid]
+
wikidata = ''
if app['Disabled']:
wikidata += '{{Disabled|' + app['Disabled'] + '}}\n'
for af in app['AntiFeatures'].split(','):
wikidata += '{{AntiFeature|' + af + '}}\n'
wikidata += '{{App|id=%s|name=%s|added=%s|lastupdated=%s|source=%s|tracker=%s|web=%s|donate=%s|flattr=%s|bitcoin=%s|litecoin=%s|dogecoin=%s|license=%s|root=%s}}\n' % (
- app['id'],
- app['Name'],
- time.strftime('%Y-%m-%d', app['added']) if 'added' in app else '',
- time.strftime('%Y-%m-%d', app['lastupdated']) if 'lastupdated' in app else '',
- app['Source Code'],
- app['Issue Tracker'],
- app['Web Site'],
- app['Donate'],
- app['FlattrID'],
- app['Bitcoin'],
- app['Litecoin'],
- app['Dogecoin'],
- app['License'],
- app.get('Requires Root', 'No'))
+ appid,
+ app['Name'],
+ time.strftime('%Y-%m-%d', app['added']) if 'added' in app else '',
+ time.strftime('%Y-%m-%d', app['lastupdated']) if 'lastupdated' in app else '',
+ app['Source Code'],
+ app['Issue Tracker'],
+ app['Web Site'],
+ app['Donate'],
+ app['FlattrID'],
+ app['Bitcoin'],
+ app['Litecoin'],
+ app['Dogecoin'],
+ app['License'],
+ app.get('Requires Root', 'No'))
if app['Provides']:
wikidata += "This app provides: %s" % ', '.join(app['Summary'].split(','))
wikidata += app['Summary']
- wikidata += " - [https://f-droid.org/repository/browse/?fdid=" + app['id'] + " view in repository]\n\n"
+ wikidata += " - [https://f-droid.org/repository/browse/?fdid=" + appid + " view in repository]\n\n"
wikidata += "=Description=\n"
wikidata += metadata.description_wiki(app['Description']) + "\n"
wikidata += "=Maintainer Notes=\n"
if 'Maintainer Notes' in app:
wikidata += metadata.description_wiki(app['Maintainer Notes']) + "\n"
- wikidata += "\nMetadata: [https://gitlab.com/fdroid/fdroiddata/blob/master/metadata/{0}.txt current] [https://gitlab.com/fdroid/fdroiddata/commits/master/metadata/{0}.txt history]\n".format(app['id'])
+ wikidata += "\nMetadata: [https://gitlab.com/fdroid/fdroiddata/blob/master/metadata/{0}.txt current] [https://gitlab.com/fdroid/fdroiddata/commits/master/metadata/{0}.txt history]\n".format(appid)
# Get a list of all packages for this application...
apklist = []
cantupdate = False
buildfails = False
for apk in apks:
- if apk['id'] == app['id']:
+ if apk['id'] == appid:
if str(apk['versioncode']) == app['Current Version Code']:
gotcurrentver = True
apklist.append(apk)
# Include ones we can't build, as a special case...
for thisbuild in app['builds']:
- if 'disable' in thisbuild:
+ if thisbuild['disable']:
if thisbuild['vercode'] == app['Current Version Code']:
cantupdate = True
- apklist.append({
- #TODO: Nasty: vercode is a string in the build, and an int elsewhere
- 'versioncode': int(thisbuild['vercode']),
- 'version': thisbuild['version'],
- 'buildproblem': thisbuild['disable']
- })
+ # TODO: Nasty: vercode is a string in the build, and an int elsewhere
+ apklist.append({'versioncode': int(thisbuild['vercode']),
+ 'version': thisbuild['version'],
+ 'buildproblem': thisbuild['disable']
+ })
else:
builtit = False
for apk in apklist:
break
if not builtit:
buildfails = True
- apklist.append({
- 'versioncode': int(thisbuild['vercode']),
- 'version': thisbuild['version'],
- 'buildproblem': "The build for this version appears to have failed. Check the [[{0}/lastbuild|build log]].".format(app['id'])
- })
+ apklist.append({'versioncode': int(thisbuild['vercode']),
+ 'version': thisbuild['version'],
+ 'buildproblem': "The build for this version appears to have failed. Check the [[{0}/lastbuild_{1}|build log]].".format(appid, thisbuild['vercode'])
+ })
if app['Current Version Code'] == '0':
cantupdate = True
# Sort with most recent first...
# We can't have underscores in the page name, even if they're in
# the package ID, because MediaWiki messes with them...
- pagename = app['id'].replace('_', ' ')
+ pagename = appid.replace('_', ' ')
# Drop a trailing newline, because mediawiki is going to drop it anyway
# and it we don't we'll think the page has changed when it hasn't...
# Drop double spaces caused mostly by replacing ':' above
apppagename = apppagename.replace(' ', ' ')
for expagename in site.allpages(prefix=apppagename,
- filterredir='nonredirects', generator=False):
+ filterredir='nonredirects',
+ generator=False):
if expagename == apppagename:
noclobber = True
# Another reason not to make the redirect page is if the app name
generated_redirects[apppagename] = "#REDIRECT [[" + pagename + "]]\n[[Category:" + wikiredircat + "]]"
for tcat, genp in [(wikicat, generated_pages),
- (wikiredircat, generated_redirects)]:
+ (wikiredircat, generated_redirects)]:
catpages = site.Pages['Category:' + tcat]
existingpages = []
for page in catpages:
page.delete('No longer published')
for pagename, text in genp.items():
logging.debug("Checking " + pagename)
- if not pagename in existingpages:
+ if pagename not in existingpages:
logging.debug("Creating page " + pagename)
try:
newpage = site.Pages[pagename]
:param apkcache: current apk cache information
:param repodirs: the repo directories to process
"""
- for app in apps:
+ for appid, app in apps.iteritems():
for build in app['builds']:
- if 'disable' in build:
- apkfilename = app['id'] + '_' + str(build['vercode']) + '.apk'
+ if build['disable']:
+ apkfilename = appid + '_' + str(build['vercode']) + '.apk'
for repodir in repodirs:
apkpath = os.path.join(repodir, apkfilename)
+ ascpath = apkpath + ".asc"
srcpath = os.path.join(repodir, apkfilename[:-4] + "_src.tar.gz")
- for name in [apkpath, srcpath]:
+ for name in [apkpath, srcpath, ascpath]:
if os.path.exists(name):
logging.warn("Deleting disabled build output " + apkfilename)
os.remove(name)
iconpath, oldsize, im.size))
im.save(iconpath, "PNG")
- else:
- logging.info("%s is small enough: %s" % im.size)
-
except Exception, e:
logging.error("Failed resizing {0} - {1}".format(iconpath, e))
resize_icon(iconpath, density)
+cert_path_regex = re.compile(r'^META-INF/.*\.RSA$')
+
+
+def getsig(apkpath):
+ """ Get the signing certificate of an apk. To get the same md5 has that
+ Android gets, we encode the .RSA certificate in a specific format and pass
+ it hex-encoded to the md5 digest algorithm.
+
+ :param apkpath: path to the apk
+ :returns: A string containing the md5 of the signature of the apk or None
+ if an error occurred.
+ """
+
+ cert = None
+
+ with zipfile.ZipFile(apkpath, 'r') as apk:
+
+ certs = [n for n in apk.namelist() if cert_path_regex.match(n)]
+
+ if len(certs) < 1:
+ logging.error("Found no signing certificates on %s" % apkpath)
+ return None
+ if len(certs) > 1:
+ logging.error("Found multiple signing certificates on %s" % apkpath)
+ return None
+
+ cert = apk.read(certs[0])
+
+ content = decoder.decode(cert, asn1Spec=rfc2315.ContentInfo())[0]
+ if content.getComponentByName('contentType') != rfc2315.signedData:
+ logging.error("Unexpected format.")
+ return None
+
+ content = decoder.decode(content.getComponentByName('content'),
+ asn1Spec=rfc2315.SignedData())[0]
+ try:
+ certificates = content.getComponentByName('certificates')
+ except PyAsn1Error:
+ logging.error("Certificates not found.")
+ return None
+
+ cert_encoded = encoder.encode(certificates)[4:]
+
+ return md5(cert_encoded.encode('hex')).hexdigest()
+
+
def scan_apks(apps, apkcache, repodir, knownapks):
"""Scan the apks in the given repo directory.
apkfilename = apkfile[len(repodir) + 1:]
if ' ' in apkfilename:
- logging.error("No spaces in APK filenames!")
+ logging.critical("Spaces in filenames are not allowed.")
sys.exit(1)
if apkfilename in apkcache:
thisinfo = apkcache[apkfilename]
else:
-
- logging.info("Processing " + apkfilename)
+ logging.debug("Processing " + apkfilename)
thisinfo = {}
thisinfo['apkname'] = apkfilename
srcfilename = apkfilename[:-4] + "_src.tar.gz"
if os.path.exists(os.path.join(repodir, srcfilename)):
thisinfo['srcname'] = srcfilename
thisinfo['size'] = os.path.getsize(apkfile)
- thisinfo['permissions'] = []
- thisinfo['features'] = []
+ thisinfo['permissions'] = set()
+ thisinfo['features'] = set()
thisinfo['icons_src'] = {}
thisinfo['icons'] = {}
- p = FDroidPopen([os.path.join(config['sdk_path'],
- 'build-tools', config['build_tools'], 'aapt'),
- 'dump', 'badging', apkfile])
+ p = SilentPopen([config['aapt'], 'dump', 'badging', apkfile])
if p.returncode != 0:
- logging.critical("Failed to get apk information")
- sys.exit(1)
- for line in p.stdout.splitlines():
+ if options.delete_unknown:
+ if os.path.exists(apkfile):
+ logging.error("Failed to get apk information, deleting " + apkfile)
+ os.remove(apkfile)
+ else:
+ logging.error("Could not find {0} to remove it".format(apkfile))
+ else:
+ logging.error("Failed to get apk information, skipping " + apkfile)
+ continue
+ for line in p.output.splitlines():
if line.startswith("package:"):
try:
thisinfo['id'] = re.match(name_pat, line).group(1)
thisinfo['versioncode'] = int(re.match(vercode_pat, line).group(1))
thisinfo['version'] = re.match(vername_pat, line).group(1)
except Exception, e:
- logging.info("Package matching failed: " + str(e))
+ logging.error("Package matching failed: " + str(e))
logging.info("Line was: " + line)
sys.exit(1)
elif line.startswith("application:"):
thisinfo['icons_src']['-1'] = match.group(1)
elif line.startswith("launchable-activity:"):
# Only use launchable-activity as fallback to application
+ if not thisinfo['name']:
+ thisinfo['name'] = re.match(label_pat, line).group(1)
if '-1' not in thisinfo['icons_src']:
match = re.match(icon_pat_nodpi, line)
if match:
path = match.group(2)
thisinfo['icons_src'][density] = path
elif line.startswith("sdkVersion:"):
- thisinfo['sdkversion'] = re.match(sdkversion_pat, line).group(1)
+ m = re.match(sdkversion_pat, line)
+ if m is None:
+ logging.error(line.replace('sdkVersion:', '')
+ + ' is not a valid minSdkVersion!')
+ else:
+ thisinfo['sdkversion'] = m.group(1)
elif line.startswith("maxSdkVersion:"):
thisinfo['maxsdkversion'] = re.match(sdkversion_pat, line).group(1)
elif line.startswith("native-code:"):
perm = re.match(string_pat, line).group(1)
if perm.startswith("android.permission."):
perm = perm[19:]
- thisinfo['permissions'].append(perm)
+ thisinfo['permissions'].add(perm)
elif line.startswith("uses-feature:"):
perm = re.match(string_pat, line).group(1)
- #Filter out this, it's only added with the latest SDK tools and
- #causes problems for lots of apps.
- if (perm != "android.hardware.screen.portrait" and
- perm != "android.hardware.screen.landscape"):
+ # Filter out this, it's only added with the latest SDK tools and
+ # causes problems for lots of apps.
+ if perm != "android.hardware.screen.portrait" \
+ and perm != "android.hardware.screen.landscape":
if perm.startswith("android.feature."):
perm = perm[16:]
- thisinfo['features'].append(perm)
+ thisinfo['features'].add(perm)
- if not 'sdkversion' in thisinfo:
- logging.warn("no SDK version information found")
+ if 'sdkversion' not in thisinfo:
+ logging.warn("No SDK version information found in {0}".format(apkfile))
thisinfo['sdkversion'] = 0
# Check for debuggable apks...
if common.isApkDebuggable(apkfile, config):
- logging.warn("{0} is debuggable... {1}".format(apkfile, line))
+ logging.warn('{0} is set to android:debuggable="true"'.format(apkfile))
# Calculate the sha256...
sha = hashlib.sha256()
sha.update(t)
thisinfo['sha256'] = sha.hexdigest()
- # Get the signature (or md5 of, to be precise)...
- getsig_dir = os.path.join(os.path.dirname(__file__), 'getsig')
- if not os.path.exists(getsig_dir + "/getsig.class"):
- logging.critical("getsig.class not found. To fix: cd '%s' && ./make.sh" % getsig_dir)
+ # verify the jar signature is correct
+ args = ['jarsigner', '-verify']
+ if options.verbose:
+ args += ['-verbose', '-certs']
+ args += apkfile
+ p = FDroidPopen(args)
+ if p.returncode != 0:
+ logging.critical(apkfile + " has a bad signature!")
sys.exit(1)
- p = FDroidPopen(['java', '-cp', os.path.join(os.path.dirname(__file__), 'getsig'),
- 'getsig', os.path.join(os.getcwd(), apkfile)])
- if p.returncode != 0 or not p.stdout.startswith('Result:'):
+
+ # Get the signature (or md5 of, to be precise)...
+ thisinfo['sig'] = getsig(os.path.join(os.getcwd(), apkfile))
+ if not thisinfo['sig']:
logging.critical("Failed to get apk signature")
sys.exit(1)
- thisinfo['sig'] = p.stdout[7:].strip()
apk = zipfile.ZipFile(apkfile, 'r')
iconfilename = "%s.%s.png" % (
- thisinfo['id'],
- thisinfo['versioncode'])
+ thisinfo['id'],
+ thisinfo['versioncode'])
# Extract the icon file...
densities = get_densities()
if '-1' in thisinfo['icons_src']:
iconsrc = thisinfo['icons_src']['-1']
iconpath = os.path.join(
- get_icon_dir(repodir, None), iconfilename)
+ get_icon_dir(repodir, None), iconfilename)
iconfile = open(iconpath, 'wb')
iconfile.write(apk.read(iconsrc))
iconfile.close()
if density == densities[-1] or dpi >= int(density):
thisinfo['icons'][density] = iconfilename
shutil.move(iconpath,
- os.path.join(get_icon_dir(repodir, density), iconfilename))
+ os.path.join(get_icon_dir(repodir, density), iconfilename))
empty_densities.remove(density)
break
except Exception, e:
continue
if last_density is None:
continue
- logging.info("Density %s not available, resizing down from %s" % (
- density, last_density))
+ logging.debug("Density %s not available, resizing down from %s"
+ % (density, last_density))
last_iconpath = os.path.join(
- get_icon_dir(repodir, last_density), iconfilename)
+ get_icon_dir(repodir, last_density), iconfilename)
iconpath = os.path.join(
- get_icon_dir(repodir, density), iconfilename)
+ get_icon_dir(repodir, density), iconfilename)
try:
im = Image.open(last_iconpath)
except:
continue
if last_density is None:
continue
- logging.info("Density %s not available, copying from lower density %s" % (
- density, last_density))
+ logging.debug("Density %s not available, copying from lower density %s"
+ % (density, last_density))
shutil.copyfile(
- os.path.join(get_icon_dir(repodir, last_density), iconfilename),
- os.path.join(get_icon_dir(repodir, density), iconfilename))
+ os.path.join(get_icon_dir(repodir, last_density), iconfilename),
+ os.path.join(get_icon_dir(repodir, density), iconfilename))
empty_densities.remove(density)
baseline = os.path.join(get_icon_dir(repodir, '160'), iconfilename)
if os.path.isfile(baseline):
shutil.copyfile(baseline,
- os.path.join(get_icon_dir(repodir, None), iconfilename))
+ os.path.join(get_icon_dir(repodir, None), iconfilename))
# Record in known apks, getting the added date at the same time..
added = knownapks.recordapk(thisinfo['apkname'], thisinfo['id'])
repo_pubkey_fingerprint = None
-def make_index(apps, apks, repodir, archive, categories):
+def make_index(apps, sortedids, apks, repodir, archive, categories):
"""Make a repo index.
:param apps: fully populated apps list
def extract_pubkey():
p = FDroidPopen(['keytool', '-exportcert',
- '-alias', config['repo_keyalias'],
- '-keystore', config['keystore'],
- '-storepass:file', config['keystorepassfile']]
- + config['smartcardoptions'])
+ '-alias', config['repo_keyalias'],
+ '-keystore', config['keystore'],
+ '-storepass:file', config['keystorepassfile']]
+ + config['smartcardoptions'], output=False)
if p.returncode != 0:
msg = "Failed to get repo pubkey!"
if config['keystore'] == 'NONE':
logging.critical(msg)
sys.exit(1)
global repo_pubkey_fingerprint
- repo_pubkey_fingerprint = cert_fingerprint(p.stdout)
- return "".join("%02x" % ord(b) for b in p.stdout)
+ repo_pubkey_fingerprint = cert_fingerprint(p.output)
+ return "".join("%02x" % ord(b) for b in p.output)
repoel.setAttribute("pubkey", extract_pubkey())
root.appendChild(repoel)
- for app in apps:
+ for appid in sortedids:
+ app = apps[appid]
if app['Disabled'] is not None:
continue
# Get a list of the apks for this app...
apklist = []
for apk in apks:
- if apk['id'] == app['id']:
+ if apk['id'] == appid:
apklist.append(apk)
if len(apklist) == 0:
if app['icon']:
addElement('icon', app['icon'], doc, apel)
- def linkres(link):
- for app in apps:
- if app['id'] == link:
- return ("fdroid.app:" + link, app['Name'])
- raise MetaDataException("Cannot resolve app id " + link)
+ def linkres(appid):
+ if appid in apps:
+ return ("fdroid.app:" + appid, apps[appid]['Name'])
+ raise MetaDataException("Cannot resolve app id " + appid)
+
addElement('desc',
- metadata.description_html(app['Description'], linkres), doc, apel)
+ metadata.description_html(app['Description'], linkres),
+ doc, apel)
addElement('license', app['License'], doc, apel)
if 'Categories' in app:
addElement('categories', ','.join(app["Categories"]), doc, apel)
# Check for duplicates - they will make the client unhappy...
for i in range(len(apklist) - 1):
- if apklist[i]['versioncode'] == apklist[i+1]['versioncode']:
+ if apklist[i]['versioncode'] == apklist[i + 1]['versioncode']:
logging.critical("duplicate versions: '%s' - '%s'" % (
- apklist[i]['apkname'], apklist[i+1]['apkname']))
+ apklist[i]['apkname'], apklist[i + 1]['apkname']))
sys.exit(1)
for apk in apklist:
if 'srcname' in apk:
addElement('srcname', apk['srcname'], doc, apkel)
for hash_type in ['sha256']:
- if not hash_type in apk:
+ if hash_type not in apk:
continue
hashel = doc.createElement("hash")
hashel.setAttribute("type", hash_type)
addElement('added', time.strftime('%Y-%m-%d', apk['added']), doc, apkel)
if app['Requires Root']:
if 'ACCESS_SUPERUSER' not in apk['permissions']:
- apk['permissions'].append('ACCESS_SUPERUSER')
+ apk['permissions'].add('ACCESS_SUPERUSER')
if len(apk['permissions']) > 0:
addElement('permissions', ','.join(apk['permissions']), doc, apkel)
if 'repo_keyalias' in config:
- logging.info("Creating signed index with this key:")
- logging.info("SHA256: %s" % repo_pubkey_fingerprint)
+ logging.info("Creating signed index with this key (SHA256):")
+ logging.info("%s" % repo_pubkey_fingerprint)
- #Create a jar of the index...
+ # Create a jar of the index...
p = FDroidPopen(['jar', 'cf', 'index.jar', 'index.xml'], cwd=repodir)
if p.returncode != 0:
logging.critical("Failed to create jar file")
p = FDroidPopen(args)
# TODO keypass should be sent via stdin
if p.returncode != 0:
- logging.info("Failed to sign index")
+ logging.critical("Failed to sign index")
sys.exit(1)
# Copy the repo icon into the repo directory...
def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversions):
- for app in apps:
+ for appid, app in apps.iteritems():
# Get a list of the apks for this app...
apklist = []
for apk in apks:
- if apk['id'] == app['id']:
+ if apk['id'] == appid:
apklist.append(apk)
# Sort the apk list into version order...
for apk in apklist[keepversions:]:
logging.info("Moving " + apk['apkname'] + " to archive")
shutil.move(os.path.join(repodir, apk['apkname']),
- os.path.join(archivedir, apk['apkname']))
+ os.path.join(archivedir, apk['apkname']))
if 'srcname' in apk:
shutil.move(os.path.join(repodir, apk['srcname']),
- os.path.join(archivedir, apk['srcname']))
+ os.path.join(archivedir, apk['srcname']))
+ # Move GPG signature too...
+ sigfile = apk['srcname'] + '.asc'
+ sigsrc = os.path.join(repodir, sigfile)
+ if os.path.exists(sigsrc):
+ shutil.move(sigsrc, os.path.join(archivedir, sigfile))
+
archapks.append(apk)
apks.remove(apk)
# Parse command line...
parser = OptionParser()
- parser.add_option("-c", "--createmeta", action="store_true", default=False,
+ parser.add_option("-c", "--create-metadata", action="store_true", default=False,
help="Create skeleton metadata files that are missing")
+ parser.add_option("--delete-unknown", action="store_true", default=False,
+ help="Delete APKs without metadata from the repo")
parser.add_option("-v", "--verbose", action="store_true", default=False,
help="Spew out even more information than normal")
parser.add_option("-q", "--quiet", action="store_true", default=False,
help="Resize all the icons exceeding the max pixel size and exit")
parser.add_option("-e", "--editor", default="/etc/alternatives/editor",
help="Specify editor to use in interactive mode. Default " +
- "is /etc/alternatives/editor")
+ "is /etc/alternatives/editor")
parser.add_option("-w", "--wiki", default=False, action="store_true",
help="Update the wiki")
parser.add_option("", "--pretty", action="store_true", default=False,
resize_all_icons(repodirs)
sys.exit(0)
+ # check that icons exist now, rather than fail at the end of `fdroid update`
+ for k in ['repo_icon', 'archive_icon']:
+ if k in config:
+ if not os.path.exists(config[k]):
+ logging.critical(k + ' "' + config[k] + '" does not exist! Correct it in config.py.')
+ sys.exit(1)
+
# Get all apps...
apps = metadata.read_metadata()
# Generate a list of categories...
categories = set()
- for app in apps:
+ for app in apps.itervalues():
categories.update(app['Categories'])
# Read known apks data (will be updated and written back when we've finished)
if cc:
cachechanged = True
+ # Generate warnings for apk's with no metadata (or create skeleton
+ # metadata files, if requested on the command line)
+ newmetadata = False
+ for apk in apks:
+ if apk['id'] not in apps:
+ if options.create_metadata:
+ if 'name' not in apk:
+ logging.error(apk['id'] + ' does not have a name! Skipping...')
+ continue
+ f = open(os.path.join('metadata', apk['id'] + '.txt'), 'w')
+ f.write("License:Unknown\n")
+ f.write("Web Site:\n")
+ f.write("Source Code:\n")
+ f.write("Issue Tracker:\n")
+ f.write("Summary:" + apk['name'] + "\n")
+ f.write("Description:\n")
+ f.write(apk['name'] + "\n")
+ f.write(".\n")
+ f.close()
+ logging.info("Generated skeleton metadata for " + apk['id'])
+ newmetadata = True
+ else:
+ msg = apk['apkname'] + " (" + apk['id'] + ") has no metadata!"
+ if options.delete_unknown:
+ logging.warn(msg + "\n\tdeleting: repo/" + apk['apkname'])
+ rmf = os.path.join(repodirs[0], apk['apkname'])
+ if not os.path.exists(rmf):
+ logging.error("Could not find {0} to remove it".format(rmf))
+ else:
+ os.remove(rmf)
+ else:
+ logging.warn(msg + "\n\tUse `fdroid update -c` to create it.")
+
+ # update the metadata with the newly created ones included
+ if newmetadata:
+ apps = metadata.read_metadata()
+
# Scan the archive repo for apks as well
if len(repodirs) > 1:
archapks, cc = scan_apks(apps, apkcache, repodirs[1], knownapks)
# level. When doing this, we use the info from the most recent version's apk.
# We deal with figuring out when the app was added and last updated at the
# same time.
- for app in apps:
+ for appid, app in apps.iteritems():
bestver = 0
added = None
lastupdated = None
for apk in apks + archapks:
- if apk['id'] == app['id']:
+ if apk['id'] == appid:
if apk['versioncode'] > bestver:
bestver = apk['versioncode']
bestapk = apk
if added:
app['added'] = added
else:
- logging.warn("Don't know when " + app['id'] + " was added")
+ logging.warn("Don't know when " + appid + " was added")
if lastupdated:
app['lastupdated'] = lastupdated
else:
- logging.warn("Don't know when " + app['id'] + " was last updated")
+ logging.warn("Don't know when " + appid + " was last updated")
if bestver == 0:
if app['Name'] is None:
- app['Name'] = app['id']
+ app['Name'] = app['Auto Name'] or appid
app['icon'] = None
- logging.warn("Application " + app['id'] + " has no packages")
+ logging.warn("Application " + appid + " has no packages")
else:
if app['Name'] is None:
app['Name'] = bestapk['name']
# Sort the app list by name, then the web site doesn't have to by default.
# (we had to wait until we'd scanned the apks to do this, because mostly the
# name comes from there!)
- apps = sorted(apps, key=lambda app: app['Name'].upper())
-
- # Generate warnings for apk's with no metadata (or create skeleton
- # metadata files, if requested on the command line)
- for apk in apks:
- found = False
- for app in apps:
- if app['id'] == apk['id']:
- found = True
- break
- if not found:
- if options.createmeta:
- f = open(os.path.join('metadata', apk['id'] + '.txt'), 'w')
- f.write("License:Unknown\n")
- f.write("Web Site:\n")
- f.write("Source Code:\n")
- f.write("Issue Tracker:\n")
- f.write("Summary:" + apk['name'] + "\n")
- f.write("Description:\n")
- f.write(apk['name'] + "\n")
- f.write(".\n")
- f.close()
- logging.info("Generated skeleton metadata for " + apk['id'])
- else:
- logging.warn(apk['apkname'] + " (" + apk['id'] + ") has no metadata - removing")
- rmf = os.path.join(repodirs[0], apk['apkname'])
- if not os.path.exists(rmf):
- logging.error("Could not find {0} to remove it".format(rmf))
- else:
- os.remove(rmf)
+ sortedids = sorted(apps.iterkeys(), key=lambda appid: apps[appid]['Name'].upper())
if len(repodirs) > 1:
archive_old_apks(apps, apks, archapks, repodirs[0], repodirs[1], config['archive_older'])
# Make the index for the main repo...
- make_index(apps, apks, repodirs[0], False, categories)
+ make_index(apps, sortedids, apks, repodirs[0], False, categories)
# If there's an archive repo, make the index for it. We already scanned it
# earlier on.
if len(repodirs) > 1:
- make_index(apps, archapks, repodirs[1], True, categories)
+ make_index(apps, sortedids, archapks, repodirs[1], True, categories)
if config['update_stats']:
for line in file(os.path.join('stats', 'latestapps.txt')):
appid = line.rstrip()
data += appid + "\t"
- for app in apps:
- if app['id'] == appid:
- data += app['Name'] + "\t"
- if app['icon'] is not None:
- data += app['icon'] + "\t"
- data += app['License'] + "\n"
- break
+ app = apps[appid]
+ data += app['Name'] + "\t"
+ if app['icon'] is not None:
+ data += app['icon'] + "\t"
+ data += app['License'] + "\n"
f = open(os.path.join(repodirs[0], 'latestapps.dat'), 'w')
f.write(data)
f.close()
# Update the wiki...
if options.wiki:
- update_wiki(apps, apks + archapks)
+ update_wiki(apps, sortedids, apks + archapks)
logging.info("Finished.")
if __name__ == "__main__":
main()
-