diff --git a/examples/config.py b/examples/config.py index eed07d3c..6026e5b4 100644 --- a/examples/config.py +++ b/examples/config.py @@ -56,6 +56,12 @@ archive_description = """ The repository of older versions of applications from the main demo repository. """ +# Normally, all apps are collected into a single app repository, like on +# https://f-droid.org. For certain situations, it is better to make a repo +# that is made up of APKs only from a single app. For example, an automated +# build server that publishes nightly builds. +# per_app_repos = True + # `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: diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 3e085624..fe159105 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -58,6 +58,7 @@ default_config = { 'mvn3': "mvn", 'gradle': 'gradle', 'sync_from_local_copy_dir': False, + 'per_app_repos': False, 'make_current_version_link': True, 'current_version_name_source': 'Name', 'update_stats': False, @@ -2135,3 +2136,26 @@ def download_file(url, local_filename=None, dldir='tmp'): f.write(chunk) f.flush() return local_filename + + +def get_per_app_repos(): + '''per-app repos are dirs named with the packageName of a single app''' + + # Android packageNames are Java packages, they may contain uppercase or + # lowercase letters ('A' through 'Z'), numbers, and underscores + # ('_'). However, individual package name parts may only start with + # letters. https://developer.android.com/guide/topics/manifest/manifest-element.html#package + p = re.compile('^([a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)*)?$') + + repos = [] + for root, dirs, files in os.walk(os.getcwd()): + for d in dirs: + print 'checking', root, 'for', d + if d in ('archive', 'metadata', 'repo', 'srclibs', 'tmp'): + # standard parts of an fdroid repo, so never packageNames + continue + elif p.match(d) \ + and os.path.exists(os.path.join(d, 'fdroid', 'repo', 'index.jar')): + repos.append(d) + break + return repos diff --git a/fdroidserver/server.py b/fdroidserver/server.py index 93447767..003dc396 100644 --- a/fdroidserver/server.py +++ b/fdroidserver/server.py @@ -285,6 +285,8 @@ def main(): repo_sections.append('archive') if not os.path.exists('archive'): os.mkdir('archive') + if config['per_app_repos']: + repo_sections += common.get_per_app_repos() if args[0] == 'init': ssh = paramiko.SSHClient() diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 657259f9..3c3a95fd 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -1016,6 +1016,28 @@ def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversi apks.remove(apk) +def add_apks_to_per_app_repos(repodir, apks): + apks_per_app = dict() + for apk in apks: + apk['per_app_dir'] = os.path.join(apk['id'], 'fdroid') + apk['per_app_repo'] = os.path.join(apk['per_app_dir'], 'repo') + apk['per_app_icons'] = os.path.join(apk['per_app_repo'], 'icons') + apks_per_app[apk['id']] = apk + + if not os.path.exists(apk['per_app_icons']): + logging.info('Adding new repo for only ' + apk['id']) + os.makedirs(apk['per_app_icons']) + + apkpath = os.path.join(repodir, apk['apkname']) + shutil.copy(apkpath, apk['per_app_repo']) + apksigpath = apkpath + '.sig' + if os.path.exists(apksigpath): + shutil.copy(apksigpath, apk['per_app_repo']) + apkascpath = apkpath + '.asc' + if os.path.exists(apkascpath): + shutil.copy(apkascpath, apk['per_app_repo']) + + config = None options = None @@ -1215,6 +1237,20 @@ def main(): # name comes from there!) sortedids = sorted(apps.iterkeys(), key=lambda appid: apps[appid]['Name'].upper()) + # APKs are placed into multiple repos based on the app package, providing + # per-app subscription feeds for nightly builds and things like it + if config['per_app_repos']: + add_apks_to_per_app_repos(repodirs[0], apks) + for appid, app in apps.iteritems(): + repodir = os.path.join(appid, 'fdroid', 'repo') + appdict = dict() + appdict[appid] = app + if os.path.isdir(repodir): + make_index(appdict, [appid], apks, repodir, False, categories) + else: + logging.info('Skipping index generation for ' + appid) + return + if len(repodirs) > 1: archive_old_apks(apps, apks, archapks, repodirs[0], repodirs[1], config['archive_older'])