diff --git a/examples/config.py b/examples/config.py index e1272649..974a4d5b 100644 --- a/examples/config.py +++ b/examples/config.py @@ -52,6 +52,15 @@ archive_description = """ The repository of older versions of applications from the main demo repository. """ +# `fdroid update` will create a link to the current version of a given app. +# This provides a static path to the current APK. To disable the creation of +# this link, uncomment this: +# make_current_version_link = False + +# By default, the "current version" link will be based on the "Name" of the +# app from the metadata. You can change it to use a different field from the +# metadata here: +# current_version_name_source = 'id' # The ID of a GPG key for making detached signatures for apks. Optional. # gpgkey = '1DBA2E89' diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 033cba8b..f6e9538f 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -47,6 +47,8 @@ default_config = { 'mvn3': "mvn", 'gradle': 'gradle', 'sync_from_local_copy_dir': False, + 'make_current_version_link': True, + 'current_version_name_source': 'Name', 'update_stats': False, 'stats_ignore': [], 'stats_server': None, diff --git a/fdroidserver/server.py b/fdroidserver/server.py index e0217836..b5da4be2 100644 --- a/fdroidserver/server.py +++ b/fdroidserver/server.py @@ -18,6 +18,7 @@ # along with this program. If not, see . import sys +import glob import hashlib import os import paramiko @@ -120,7 +121,9 @@ def update_awsbucket(repo_section): def update_serverwebroot(serverwebroot, repo_section): - rsyncargs = ['rsync', '--archive', '--delete'] + # use a checksum comparison for accurate comparisons on different + # filesystems, for example, FAT has a low resolution timestamp + rsyncargs = ['rsync', '--archive', '--delete', '--checksum'] if options.verbose: rsyncargs += ['--verbose'] if options.quiet: @@ -131,18 +134,29 @@ def update_serverwebroot(serverwebroot, repo_section): rsyncargs += ['-e', 'ssh -i ' + config['identity_file']] indexxml = os.path.join(repo_section, 'index.xml') indexjar = os.path.join(repo_section, 'index.jar') - # serverwebroot is guaranteed to have a trailing slash in common.py + # upload the first time without the index so that the repo stays working + # while this update is running. Then once it is complete, rerun the + # command again to upload the index. Always using the same target with + # rsync allows for very strict settings on the receiving server, you can + # literally specify the one rsync command that is allowed to run in + # ~/.ssh/authorized_keys. (serverwebroot is guaranteed to have a trailing + # slash in common.py) if subprocess.call(rsyncargs + ['--exclude', indexxml, '--exclude', indexjar, repo_section, serverwebroot]) != 0: sys.exit(1) - # use stricter checking on the indexes since they provide the signature - rsyncargs += ['--checksum'] - sectionpath = serverwebroot + repo_section - if subprocess.call(rsyncargs + [indexxml, sectionpath]) != 0: - sys.exit(1) - if subprocess.call(rsyncargs + [indexjar, sectionpath]) != 0: + if subprocess.call(rsyncargs + [repo_section, serverwebroot]) != 0: sys.exit(1) + # upload "current version" symlinks if requested + if config['make_current_version_link'] and repo_section == 'repo': + links_to_upload = [] + for f in glob.glob('*.apk') \ + + glob.glob('*.apk.asc') + glob.glob('*.apk.sig'): + if os.path.islink(f): + links_to_upload.append(f) + if len(links_to_upload) > 0: + if subprocess.call(rsyncargs + links_to_upload + [serverwebroot]) != 0: + sys.exit(1) def _local_sync(fromdir, todir): diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 225f594d..0339f460 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -824,7 +824,15 @@ def make_index(apps, sortedids, apks, repodir, archive, categories): apklist[i]['apkname'], apklist[i + 1]['apkname'])) sys.exit(1) + current_version_code = 0 + current_version_file = None for apk in apklist: + # find the APK for the "Current Version" + if current_version_code < apk['versioncode']: + current_version_code = apk['versioncode'] + if current_version_code < int(app['Current Version Code']): + current_version_file = apk['apkname'] + apkel = doc.createElement("package") apel.appendChild(apkel) addElement('version', apk['version'], doc, apkel) @@ -857,6 +865,25 @@ def make_index(apps, sortedids, apks, repodir, archive, categories): if len(apk['features']) > 0: addElement('features', ','.join(apk['features']), doc, apkel) + if current_version_file is not None \ + and config['make_current_version_link'] \ + and repodir == 'repo': # only create these + sanitized_name = re.sub('''[ '"&%?+=]''', '', + app[config['current_version_name_source']]) + apklinkname = sanitized_name + '.apk' + current_version_path = os.path.join(repodir, current_version_file) + if os.path.exists(apklinkname): + os.remove(apklinkname) + os.symlink(current_version_path, apklinkname) + # also symlink gpg signature, if it exists + for extension in ('.asc', '.sig'): + sigfile_path = current_version_path + extension + if os.path.exists(sigfile_path): + siglinkname = apklinkname + extension + if os.path.exists(siglinkname): + os.remove(siglinkname) + os.symlink(sigfile_path, siglinkname) + of = open(os.path.join(repodir, 'index.xml'), 'wb') if options.pretty: output = doc.toprettyxml()