import zipfile
import hashlib
import pickle
+import urlparse
from datetime import datetime, timedelta
from xml.dom.minidom import Document
from argparse import ArgumentParser
requiresroot = 'Yes'
else:
requiresroot = 'No'
- wikidata += '{{App|id=%s|name=%s|added=%s|lastupdated=%s|source=%s|tracker=%s|web=%s|changelog=%s|donate=%s|flattr=%s|bitcoin=%s|litecoin=%s|license=%s|root=%s}}\n' % (
+ wikidata += '{{App|id=%s|name=%s|added=%s|lastupdated=%s|source=%s|tracker=%s|web=%s|changelog=%s|donate=%s|flattr=%s|bitcoin=%s|litecoin=%s|license=%s|root=%s|author=%s|email=%s}}\n' % (
appid,
app.Name,
time.strftime('%Y-%m-%d', app.added) if app.added else '',
app.Bitcoin,
app.Litecoin,
app.License,
- requiresroot)
+ requiresroot,
+ app.AuthorName,
+ app.AuthorEmail)
if app.Provides:
wikidata += "This app provides: %s" % ', '.join(app.Summary.split(','))
if validapks == 0 and not app.Disabled:
wikidata += '\n[[Category:Apps with no packages]]\n'
if cantupdate and not app.Disabled:
- wikidata += "\n[[Category:Apps we can't update]]\n"
+ wikidata += "\n[[Category:Apps we cannot update]]\n"
if buildfails and not app.Disabled:
wikidata += "\n[[Category:Apps with failing builds]]\n"
elif not gotcurrentver and not cantupdate and not app.Disabled and app.UpdateCheckMode != "Static":
iconpath, oldsize, im.size))
im.save(iconpath, "PNG")
- except Exception, e:
+ except Exception as e:
logging.error("Failed resizing {0} - {1}".format(iconpath, e))
cert = None
# verify the jar signature is correct
- args = ['jarsigner', '-verify', apkpath]
+ args = [config['jarsigner'], '-verify', apkpath]
p = FDroidPopen(args)
if p.returncode != 0:
logging.critical(apkpath + " has a bad signature!")
return md5(cert_encoded.encode('hex')).hexdigest()
-def scan_apks(apps, apkcache, repodir, knownapks):
+def scan_apks(apps, apkcache, repodir, knownapks, use_date_from_apk=False):
"""Scan the apks in the given repo directory.
This also extracts the icons.
:param apkcache: current apk cache information
:param repodir: repo directory to scan
:param knownapks: known apks info
+ :param use_date_from_apk: use date from APK (instead of current date)
+ for newly added APKs
:returns: (apks, cachechanged) where apks is a list of apk information,
and cachechanged is True if the apkcache got changed.
"""
apk['id'] = re.match(name_pat, line).group(1)
apk['versioncode'] = int(re.match(vercode_pat, line).group(1))
apk['version'] = re.match(vername_pat, line).group(1)
- except Exception, e:
+ except Exception as e:
logging.error("Package matching failed: " + str(e))
logging.info("Line was: " + line)
sys.exit(1)
# has to be more than 24 hours newer because ZIP/APK files do not
# store timezone info
manifest = apkzip.getinfo('AndroidManifest.xml')
- dt_obj = datetime(*manifest.date_time)
- checkdt = dt_obj - timedelta(1)
- if datetime.today() < checkdt:
- logging.warn('System clock is older than manifest in: '
- + apkfilename + '\nSet clock to that time using:\n'
- + 'sudo date -s "' + str(dt_obj) + '"')
+ if manifest.date_time[1] == 0: # month can't be zero
+ logging.debug('AndroidManifest.xml has no date')
+ else:
+ dt_obj = datetime(*manifest.date_time)
+ checkdt = dt_obj - timedelta(1)
+ if datetime.today() < checkdt:
+ logging.warn('System clock is older than manifest in: '
+ + apkfilename
+ + '\nSet clock to that time using:\n'
+ + 'sudo date -s "' + str(dt_obj) + '"')
iconfilename = "%s.%s.png" % (
apk['id'],
os.path.join(get_icon_dir(repodir, density), iconfilename))
empty_densities.remove(density)
break
- except Exception, e:
+ except Exception as e:
logging.warn("Failed reading {0} - {1}".format(iconpath, e))
if apk['icons']:
# Record in known apks, getting the added date at the same time..
added = knownapks.recordapk(apk['apkname'], apk['id'])
if added:
+ if use_date_from_apk and manifest.date_time[1] != 0:
+ added = datetime(*manifest.date_time).timetuple()
+ logging.debug("Using date from APK")
+
apk['added'] = added
apkcache[apkfilename] = apk
if 'repo_pubkey' in config:
pubkey = unhexlify(config['repo_pubkey'])
else:
- p = FDroidPopen(['keytool', '-exportcert',
+ p = FDroidPopen([config['keytool'], '-exportcert',
'-alias', config['repo_keyalias'],
'-keystore', config['keystore'],
'-storepass:file', config['keystorepassfile']]
- + config['smartcardoptions'], output=False)
+ + config['smartcardoptions'],
+ output=False, stderr_to_stdout=False)
if p.returncode != 0 or len(p.output) < 20:
msg = "Failed to get repo pubkey!"
if config['keystore'] == 'NONE':
repoel = doc.createElement("repo")
+ mirrorcheckfailed = False
+ for mirror in config.get('mirrors', []):
+ base = os.path.basename(urlparse.urlparse(mirror).path.rstrip('/'))
+ if config.get('nonstandardwebroot') is not True and base != 'fdroid':
+ logging.error("mirror '" + mirror + "' does not end with 'fdroid'!")
+ mirrorcheckfailed = True
+ if mirrorcheckfailed:
+ sys.exit(1)
+
if archive:
repoel.setAttribute("name", config['archive_name'])
if config['repo_maxage'] != 0:
repoel.setAttribute("icon", os.path.basename(config['archive_icon']))
repoel.setAttribute("url", config['archive_url'])
addElement('description', config['archive_description'], doc, repoel)
+ urlbasepath = os.path.basename(urlparse.urlparse(config['archive_url']).path)
+ for mirror in config.get('mirrors', []):
+ addElement('mirror', urlparse.urljoin(mirror, urlbasepath), doc, repoel)
else:
repoel.setAttribute("name", config['repo_name'])
repoel.setAttribute("icon", os.path.basename(config['repo_icon']))
repoel.setAttribute("url", config['repo_url'])
addElement('description', config['repo_description'], doc, repoel)
+ urlbasepath = os.path.basename(urlparse.urlparse(config['repo_url']).path)
+ for mirror in config.get('mirrors', []):
+ addElement('mirror', urlparse.urljoin(mirror, urlbasepath), doc, repoel)
- repoel.setAttribute("version", "14")
+ repoel.setAttribute("version", "15")
repoel.setAttribute("timestamp", str(int(time.time())))
nosigningkey = False
addElement('source', app.SourceCode, doc, apel)
addElement('tracker', app.IssueTracker, doc, apel)
addElementNonEmpty('changelog', app.Changelog, doc, apel)
+ addElementNonEmpty('author', app.AuthorName, doc, apel)
+ addElementNonEmpty('email', app.AuthorEmail, doc, apel)
addElementNonEmpty('donate', app.Donate, doc, apel)
addElementNonEmpty('bitcoin', app.Bitcoin, doc, apel)
addElementNonEmpty('litecoin', app.Litecoin, doc, apel)
if os.path.exists(signed):
os.remove(signed)
else:
- args = ['jarsigner', '-keystore', config['keystore'],
+ args = [config['jarsigner'], '-keystore', config['keystore'],
'-storepass:file', config['keystorepassfile'],
'-digestalg', 'SHA1', '-sigalg', 'SHA1withRSA',
signed, config['repo_keyalias']]
help="Clean update - don't uses caches, reprocess all apks")
parser.add_argument("--nosign", action="store_true", default=False,
help="When configured for signed indexes, create only unsigned indexes at this stage")
+ parser.add_argument("--use-date-from-apk", action="store_true", default=False,
+ help="Use date from apk instead of current time for newly added apks")
options = parser.parse_args()
config = common.read_config(options)
+ if not ('jarsigner' in config and 'keytool' in config):
+ logging.critical('Java JDK not found! Install in standard location or set java_paths!')
+ sys.exit(1)
+
repodirs = ['repo']
if config['archive_older'] != 0:
repodirs.append('archive')
delete_disabled_builds(apps, apkcache, repodirs)
# Scan all apks in the main repo
- apks, cachechanged = scan_apks(apps, apkcache, repodirs[0], knownapks)
+ apks, cachechanged = scan_apks(apps, apkcache, repodirs[0], knownapks, options.use_date_from_apk)
# Generate warnings for apk's with no metadata (or create skeleton
# metadata files, if requested on the command line)
# Scan the archive repo for apks as well
if len(repodirs) > 1:
- archapks, cc = scan_apks(apps, apkcache, repodirs[1], knownapks)
+ archapks, cc = scan_apks(apps, apkcache, repodirs[1], knownapks, options.use_date_from_apk)
if cc:
cachechanged = True
else:
if app.Name is None:
app.Name = bestapk['name']
app.icon = bestapk['icon'] if 'icon' in bestapk else None
+ if app.CurrentVersionCode is None:
+ app.CurrentVersionCode = str(bestver)
# 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