From: Hans-Christoph Steiner Date: Fri, 15 Sep 2017 19:29:12 +0000 (+0000) Subject: Merge branch 'implement-gettext' into 'master' X-Git-Tag: 0.9~80 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=a2eaf373947f82584e14cbf89f4bc19aa694a529;hp=9cb074cb08cae1594c59a99f3ee9f3941341fdef;p=fdroidserver.git Merge branch 'implement-gettext' into 'master' first implementation of localization using gettext Closes #342 See merge request fdroid/fdroidserver!338 --- diff --git a/.gitignore b/.gitignore index a606026b..3bccb197 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ makebuildserver.config.py /tests/repo/info.guardianproject.checkey/en-US/phoneScreenshots/checkey.png /tests/urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234.apk /unsigned/ + +# generated by gettext +locale/*/LC_MESSAGES/fdroidserver.mo diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ce9064e..0b0a4d3e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,10 +6,12 @@ test: - cd tests - ./complete-ci-tests -# Test that the parsing of the .txt format didn't change -# from last released version. +# Test that the parsing of the .txt format didn't change from last +# released version. Ensure that the official tags are included when +# running these tests on forks as well. metadata_v0: script: + - git fetch https://gitlab.com/fdroid/fdroidserver 0.8 - cd tests - export GITCOMMIT=`git describe` - git checkout 0.8 # bump after release diff --git a/fdroid b/fdroid index 0b483e01..467551c0 100755 --- a/fdroid +++ b/fdroid @@ -22,36 +22,38 @@ import logging import fdroidserver.common import fdroidserver.metadata +from fdroidserver import _ from argparse import ArgumentError from collections import OrderedDict + commands = OrderedDict([ - ("build", "Build a package from source"), - ("init", "Quickly start a new repository"), - ("publish", "Sign and place packages in the repo"), - ("gpgsign", "Add gpg signatures for packages in repo"), - ("update", "Update repo information for new packages"), - ("verify", "Verify the integrity of downloaded packages"), - ("checkupdates", "Check for updates to applications"), - ("import", "Add a new application from its source code"), - ("install", "Install built packages on devices"), - ("readmeta", "Read all the metadata files and exit"), - ("rewritemeta", "Rewrite all the metadata files"), - ("lint", "Warn about possible metadata errors"), - ("scanner", "Scan the source code of a package"), - ("dscanner", "Dynamically scan APKs post build"), - ("stats", "Update the stats of the repo"), - ("server", "Interact with the repo HTTP server"), - ("signindex", "Sign indexes created using update --nosign"), - ("btlog", "Update the binary transparency log for a URL"), - ("signatures", "Extract signatures from APKs"), + ("build", _("Build a package from source")), + ("init", _("Quickly start a new repository")), + ("publish", _("Sign and place packages in the repo")), + ("gpgsign", _("Add gpg signatures for packages in repo")), + ("update", _("Update repo information for new packages")), + ("verify", _("Verify the integrity of downloaded packages")), + ("checkupdates", _("Check for updates to applications")), + ("import", _("Add a new application from its source code")), + ("install", _("Install built packages on devices")), + ("readmeta", _("Read all the metadata files and exit")), + ("rewritemeta", _("Rewrite all the metadata files")), + ("lint", _("Warn about possible metadata errors")), + ("scanner", _("Scan the source code of a package")), + ("dscanner", _("Dynamically scan APKs post build")), + ("stats", _("Update the stats of the repo")), + ("server", _("Interact with the repo HTTP server")), + ("signindex", _("Sign indexes created using update --nosign")), + ("btlog", _("Update the binary transparency log for a URL")), + ("signatures", _("Extract signatures from APKs")), ]) def print_help(): - print("usage: fdroid [-h|--help|--version] []") + print(_("usage: fdroid [-h|--help|--version] []")) print("") - print("Valid commands are:") + print(_("Valid commands are:")) for cmd, summary in commands.items(): print(" " + cmd + ' ' * (15 - len(cmd)) + summary) print("") @@ -70,7 +72,7 @@ def main(): sys.exit(0) elif command == '--version': import os.path - output = 'no version info found!' + output = _('no version info found!') cmddir = os.path.realpath(os.path.dirname(__file__)) moduledir = os.path.realpath(os.path.dirname(fdroidserver.common.__file__) + '/..') if cmddir == moduledir: @@ -97,7 +99,7 @@ def main(): print(output), sys.exit(0) else: - print("Command '%s' not recognised.\n" % command) + print(_("Command '%s' not recognised.\n" % command)) print_help() sys.exit(1) @@ -143,7 +145,7 @@ def main(): # These should only be unexpected crashes due to bugs in the code # str(e) often doesn't contain a reason, so just show the backtrace except Exception as e: - logging.critical("Unknown exception found!") + logging.critical(_("Unknown exception found!")) raise sys.exit(0) diff --git a/fdroidserver/__init__.py b/fdroidserver/__init__.py index e69de29b..2e88551a 100644 --- a/fdroidserver/__init__.py +++ b/fdroidserver/__init__.py @@ -0,0 +1,22 @@ + +import gettext +import glob +import os +import sys + + +# support running straight from git and standard installs +rootpaths = [ + os.path.realpath(os.path.join(os.path.dirname(__file__), '..')), + sys.prefix + 'share', +] + +localedir = None +for rootpath in rootpaths: + if len(glob.glob(os.path.join(rootpath, 'locale', '*', 'LC_MESSAGES', 'fdroidserver.mo'))) > 0: + localedir = os.path.join(rootpath, 'locale') + break + +gettext.bindtextdomain('fdroidserver', localedir) +gettext.textdomain('fdroidserver') +_ = gettext.gettext diff --git a/fdroidserver/btlog.py b/fdroidserver/btlog.py index 452addca..de357039 100755 --- a/fdroidserver/btlog.py +++ b/fdroidserver/btlog.py @@ -40,9 +40,10 @@ import xml.dom.minidom import zipfile from argparse import ArgumentParser -from .exception import FDroidException +from . import _ from . import common from . import server +from .exception import FDroidException options = None @@ -117,12 +118,12 @@ For more info on this idea: jarin.close() gitrepo.index.add([repof, ]) - files = [] - for root, dirs, filenames in os.walk(repodir): - for f in filenames: - files.append(os.path.relpath(os.path.join(root, f), repodir)) + output_files = [] + for root, dirs, files in os.walk(repodir): + for f in files: + output_files.append(os.path.relpath(os.path.join(root, f), repodir)) output = collections.OrderedDict() - for f in sorted(files): + for f in sorted(output_files): repofile = os.path.join(repodir, f) stat = os.stat(repofile) output[f] = ( @@ -151,11 +152,11 @@ def main(): common.setup_global_opts(parser) parser.add_argument("--git-repo", default=os.path.join(os.getcwd(), 'binary_transparency'), - help="Path to the git repo to use as the log") + help=_("Path to the git repo to use as the log")) parser.add_argument("-u", "--url", default='https://f-droid.org', - help="The base URL for the repo to log (default: https://f-droid.org)") + help=_("The base URL for the repo to log (default: https://f-droid.org)")) parser.add_argument("--git-remote", default=None, - help="Push the log to this git remote repository") + help=_("Push the log to this git remote repository")) options = parser.parse_args() if options.verbose: diff --git a/fdroidserver/build.py b/fdroidserver/build.py index 5dfe63ba..889945d3 100644 --- a/fdroidserver/build.py +++ b/fdroidserver/build.py @@ -32,6 +32,7 @@ from configparser import ConfigParser from argparse import ArgumentParser import logging +from . import _ from . import common from . import net from . import metadata @@ -96,19 +97,19 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force): # Helper to copy the contents of a directory to the server... def send_dir(path): - root = os.path.dirname(path) + startroot = os.path.dirname(path) main = os.path.basename(path) ftp.mkdir(main) - for r, d, f in os.walk(path): - rr = os.path.relpath(r, root) + for root, dirs, files in os.walk(path): + rr = os.path.relpath(root, startroot) ftp.chdir(rr) - for dd in d: - ftp.mkdir(dd) - for ff in f: - lfile = os.path.join(root, rr, ff) + for d in dirs: + ftp.mkdir(d) + for f in files: + lfile = os.path.join(startroot, rr, f) if not os.path.islink(lfile): - ftp.put(lfile, ff) - ftp.chmod(ff, os.stat(lfile).st_mode) + ftp.put(lfile, f) + ftp.chmod(f, os.stat(lfile).st_mode) for i in range(len(rr.split('/'))): ftp.chdir('..') ftp.chdir('..') @@ -162,7 +163,7 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force): ftp.mkdir(d) ftp.chdir(d) ftp.put(libsrc, lp[-1]) - for _ in lp[:-1]: + for _ignored in lp[:-1]: ftp.chdir('..') # Copy any srclibs that are required... srclibpaths = [] @@ -995,33 +996,33 @@ def parse_commandline(): parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") common.setup_global_opts(parser) - parser.add_argument("appid", nargs='*', help="app-id with optional versionCode in the form APPID[:VERCODE]") + parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]")) parser.add_argument("-l", "--latest", action="store_true", default=False, - help="Build only the latest version of each package") + help=_("Build only the latest version of each package")) parser.add_argument("-s", "--stop", action="store_true", default=False, - help="Make the build stop on exceptions") + help=_("Make the build stop on exceptions")) parser.add_argument("-t", "--test", action="store_true", default=False, - help="Test mode - put output in the tmp directory only, and always build, even if the output already exists.") + help=_("Test mode - put output in the tmp directory only, and always build, even if the output already exists.")) parser.add_argument("--server", action="store_true", default=False, - help="Use build server") + help=_("Use build server")) parser.add_argument("--resetserver", action="store_true", default=False, - help="Reset and create a brand new build server, even if the existing one appears to be ok.") + help=_("Reset and create a brand new build server, even if the existing one appears to be ok.")) parser.add_argument("--on-server", dest="onserver", action="store_true", default=False, - help="Specify that we're running on the build server") + help=_("Specify that we're running on the build server")) parser.add_argument("--skip-scan", dest="skipscan", action="store_true", default=False, - help="Skip scanning the source code for binaries and other problems") + help=_("Skip scanning the source code for binaries and other problems")) parser.add_argument("--dscanner", action="store_true", default=False, - help="Setup an emulator, install the apk on it and perform a drozer scan") + help=_("Setup an emulator, install the apk on it and perform a drozer scan")) parser.add_argument("--no-tarball", dest="notarball", action="store_true", default=False, - help="Don't create a source tarball, useful when testing a build") + help=_("Don't create a source tarball, useful when testing a build")) parser.add_argument("--no-refresh", dest="refresh", action="store_false", default=True, - help="Don't refresh the repository, useful when testing a build with no internet connection") + help=_("Don't refresh the repository, useful when testing a build with no internet connection")) parser.add_argument("-f", "--force", action="store_true", default=False, - help="Force build of disabled apps, and carries on regardless of scan problems. Only allowed in test mode.") + help=_("Force build of disabled apps, and carries on regardless of scan problems. Only allowed in test mode.")) parser.add_argument("-a", "--all", action="store_true", default=False, - help="Build all applications available") + help=_("Build all applications available")) parser.add_argument("-w", "--wiki", default=False, action="store_true", - help="Update the wiki") + help=_("Update the wiki")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -1316,7 +1317,7 @@ def main(): logging.info("Cleaning up after ourselves.") docker.clean() - logging.info("Finished.") + logging.info(_("Finished")) if len(build_succeeded) > 0: logging.info(str(len(build_succeeded)) + ' builds succeeded') if len(failed_apps) > 0: diff --git a/fdroidserver/checkupdates.py b/fdroidserver/checkupdates.py index 217139d1..7c269c1f 100644 --- a/fdroidserver/checkupdates.py +++ b/fdroidserver/checkupdates.py @@ -30,6 +30,7 @@ from distutils.version import LooseVersion import logging import copy +from . import _ from . import common from . import metadata from .exception import VCSException, FDroidException, MetaDataException @@ -302,10 +303,10 @@ def check_gplay(app): # Return all directories under startdir that contain any of the manifest # files, and thus are probably an Android project. def dirs_with_manifest(startdir): - for r, d, f in os.walk(startdir): - if any(m in f for m in [ + for root, dirs, files in os.walk(startdir): + if any(m in files for m in [ 'AndroidManifest.xml', 'pom.xml', 'build.gradle']): - yield r + yield root # Tries to find a new subdir starting from the root build_dir. Returns said @@ -509,15 +510,15 @@ def main(): # Parse command line... parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]") common.setup_global_opts(parser) - parser.add_argument("appid", nargs='*', help="app-id to check for updates") + parser.add_argument("appid", nargs='*', help=_("applicationId to check for updates")) parser.add_argument("--auto", action="store_true", default=False, - help="Process auto-updates") + help=_("Process auto-updates")) parser.add_argument("--autoonly", action="store_true", default=False, - help="Only process apps with auto-updates") + help=_("Only process apps with auto-updates")) parser.add_argument("--commit", action="store_true", default=False, - help="Commit changes") + help=_("Commit changes")) parser.add_argument("--gplay", action="store_true", default=False, - help="Only print differences with the Play Store") + help=_("Only print differences with the Play Store")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -567,7 +568,7 @@ def main(): except Exception as e: logging.error("...checkupdate failed for {0} : {1}".format(appid, e)) - logging.info("Finished.") + logging.info(_("Finished")) if __name__ == "__main__": diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 182949d9..815cf3eb 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -49,6 +49,7 @@ from pyasn1.error import PyAsn1Error from distutils.util import strtobool import fdroidserver.metadata +from fdroidserver import _ from fdroidserver.exception import FDroidException, VCSException, BuildException from .asynchronousfilereader import AsynchronousFileReader @@ -123,9 +124,9 @@ default_config = { def setup_global_opts(parser): parser.add_argument("-v", "--verbose", action="store_true", default=False, - help="Spew out even more information than normal") + help=_("Spew out even more information than normal")) parser.add_argument("-q", "--quiet", action="store_true", default=False, - help="Restrict output to warnings and errors") + help=_("Restrict output to warnings and errors")) def fill_config_defaults(thisconfig): @@ -1022,9 +1023,9 @@ def retrieve_string(app_dir, string, xmlfiles=None): os.path.join(app_dir, 'res'), os.path.join(app_dir, 'src', 'main', 'res'), ]: - for r, d, f in os.walk(res_dir): - if os.path.basename(r) == 'values': - xmlfiles += [os.path.join(r, x) for x in f if x.endswith('.xml')] + for root, dirs, files in os.walk(res_dir): + if os.path.basename(root) == 'values': + xmlfiles += [os.path.join(root, x) for x in files if x.endswith('.xml')] name = string[len('@string/'):] diff --git a/fdroidserver/dscanner.py b/fdroidserver/dscanner.py index a9ffa027..5622fc4d 100644 --- a/fdroidserver/dscanner.py +++ b/fdroidserver/dscanner.py @@ -24,7 +24,9 @@ from time import sleep from argparse import ArgumentParser from subprocess import CalledProcessError, check_output -from fdroidserver import common, metadata +from . import _ +from . import common +from . import metadata try: from docker import Client @@ -407,25 +409,25 @@ def main(): parser.add_argument( "app_id", nargs='*', - help="app-id with optional versioncode in the form APPID[:VERCODE]") + help=_("applicationId with optional versionCode in the form APPID[:VERCODE]")) parser.add_argument( "-l", "--latest", action="store_true", default=False, - help="Scan only the latest version of each package") + help=_("Scan only the latest version of each package")) parser.add_argument( "--clean-after", default=False, action='store_true', - help="Clean after all scans have finished") + help=_("Clean after all scans have finished")) parser.add_argument( "--clean-before", default=False, action='store_true', - help="Clean before the scans start and rebuild the container") + help=_("Clean before the scans start and rebuild the container")) parser.add_argument( "--clean-only", default=False, action='store_true', - help="Clean up all containers and then exit") + help=_("Clean up all containers and then exit")) parser.add_argument( "--init-only", default=False, action='store_true', - help="Prepare drozer to run a scan") + help=_("Prepare drozer to run a scan")) parser.add_argument( "--repo-path", default="repo", action="store", - help="Override path for repo APKs (default: ./repo)") + help=_("Override path for repo APKs (default: ./repo)")) options = parser.parse_args() config = common.read_config(options) diff --git a/fdroidserver/gpgsign.py b/fdroidserver/gpgsign.py index 159ddf40..b942a21b 100644 --- a/fdroidserver/gpgsign.py +++ b/fdroidserver/gpgsign.py @@ -21,6 +21,7 @@ import glob from argparse import ArgumentParser import logging +from . import _ from . import common from .common import FDroidPopen from .exception import FDroidException @@ -46,7 +47,7 @@ def main(): for output_dir in repodirs: if not os.path.isdir(output_dir): - raise FDroidException("Missing output directory '" + output_dir + "'") + raise FDroidException(_("Missing output directory") + " '" + output_dir + "'") # Process any apks that are waiting to be signed... for f in sorted(glob.glob(os.path.join(output_dir, '*.*'))): diff --git a/fdroidserver/import.py b/fdroidserver/import.py index 43e395b7..ba2f9342 100644 --- a/fdroidserver/import.py +++ b/fdroidserver/import.py @@ -26,6 +26,7 @@ from argparse import ArgumentParser from configparser import ConfigParser import logging +from . import _ from . import common from . import metadata from .exception import FDroidException @@ -59,7 +60,7 @@ def getrepofrompage(url): repo = page[index + 9:] index = repo.find('<') if index == -1: - return (None, "Error while getting repo address") + return (None, _("Error while getting repo address")) repo = repo[:index] repo = repo.split('"')[0] return (repotype, repo) @@ -71,12 +72,12 @@ def getrepofrompage(url): repo = page[index + 10:] index = repo.find('<') if index == -1: - return (None, "Error while getting repo address") + return (None, _("Error while getting repo address")) repo = repo[:index] repo = repo.split('"')[0] return (repotype, repo) - return (None, "No information found." + page) + return (None, _("No information found.") + page) config = None @@ -87,7 +88,7 @@ def get_metadata_from_url(app, url): tmp_dir = 'tmp' if not os.path.isdir(tmp_dir): - logging.info("Creating temporary directory") + logging.info(_("Creating temporary directory")) os.makedirs(tmp_dir) # Figure out what kind of project it is... @@ -190,15 +191,15 @@ def main(): parser = ArgumentParser() common.setup_global_opts(parser) parser.add_argument("-u", "--url", default=None, - help="Project URL to import from.") + help=_("Project URL to import from.")) parser.add_argument("-s", "--subdir", default=None, - help="Path to main android project subdirectory, if not in root.") + help=_("Path to main android project subdirectory, if not in root.")) parser.add_argument("-c", "--categories", default=None, - help="Comma separated list of categories.") + help=_("Comma separated list of categories.")) parser.add_argument("-l", "--license", default=None, - help="Overall license of the project.") + help=_("Overall license of the project.")) parser.add_argument("--rev", default=None, - help="Allows a different revision (or git branch) to be specified for the initial import") + help=_("Allows a different revision (or git branch) to be specified for the initial import")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -214,7 +215,7 @@ def main(): local_metadata_files = common.get_local_metadata_files() if local_metadata_files != []: - raise FDroidException("This repo already has local metadata: %s" % local_metadata_files[0]) + raise FDroidException(_("This repo already has local metadata: %s") % local_metadata_files[0]) if options.url is None and os.path.isdir('.git'): app.AutoName = os.path.basename(os.getcwd()) @@ -252,11 +253,11 @@ def main(): versionName, versionCode, package = common.parse_androidmanifests(paths, app) if not package: - raise FDroidException("Couldn't find package ID") + raise FDroidException(_("Couldn't find package ID")) if not versionName: - logging.warn("Couldn't find latest version name") + logging.warn(_("Couldn't find latest version name")) if not versionCode: - logging.warn("Couldn't find latest version code") + logging.warn(_("Couldn't find latest version code")) else: spec = os.path.join(root_dir, 'buildozer.spec') if os.path.exists(spec): @@ -268,7 +269,7 @@ def main(): versionName = bconfig.get('app', 'version') versionCode = None else: - raise FDroidException("No android or kivy project could be found. Specify --subdir?") + raise FDroidException(_("No android or kivy project could be found. Specify --subdir?")) # Make sure it's actually new... if package in apps: diff --git a/fdroidserver/index.py b/fdroidserver/index.py index 53d2787a..59139e17 100644 --- a/fdroidserver/index.py +++ b/fdroidserver/index.py @@ -34,7 +34,11 @@ from binascii import hexlify, unhexlify from datetime import datetime from xml.dom.minidom import Document -from fdroidserver import metadata, signindex, common, net +from . import _ +from . import common +from . import metadata +from . import net +from . import signindex from fdroidserver.common import FDroidPopen, FDroidPopenBytes from fdroidserver.exception import FDroidException, VerificationException, MetaDataException @@ -62,16 +66,16 @@ def make(apps, sortedids, apks, repodir, archive): if not common.options.nosign: if 'repo_keyalias' not in common.config: nosigningkey = True - logging.critical("'repo_keyalias' not found in config.py!") + logging.critical(_("'repo_keyalias' not found in config.py!")) if 'keystore' not in common.config: nosigningkey = True - logging.critical("'keystore' not found in config.py!") + logging.critical(_("'keystore' not found in config.py!")) if 'keystorepass' not in common.config: nosigningkey = True - logging.critical("'keystorepass' not found in config.py!") + logging.critical(_("'keystorepass' not found in config.py!")) if 'keypass' not in common.config: nosigningkey = True - logging.critical("'keypass' not found in config.py!") + logging.critical(_("'keypass' not found in config.py!")) if not os.path.exists(common.config['keystore']): nosigningkey = True logging.critical("'" + common.config['keystore'] + "' does not exist!") @@ -104,7 +108,7 @@ def make(apps, sortedids, apks, repodir, archive): for mirror in sorted(common.config.get('mirrors', [])): base = os.path.basename(urllib.parse.urlparse(mirror).path.rstrip('/')) if common.config.get('nonstandardwebroot') is not True and base != 'fdroid': - logging.error("mirror '" + mirror + "' does not end with 'fdroid'!") + logging.error(_("mirror '%s' does not end with 'fdroid'!") % mirror) mirrorcheckfailed = True # must end with / or urljoin strips a whole path segment if mirror.endswith('/'): @@ -115,7 +119,7 @@ def make(apps, sortedids, apks, repodir, archive): for url in get_mirror_service_urls(mirror): mirrors.append(url + '/' + repodir) if mirrorcheckfailed: - raise FDroidException("Malformed repository mirrors.") + raise FDroidException(_("Malformed repository mirrors.")) if mirrors: repodict['mirrors'] = mirrors @@ -144,7 +148,7 @@ def make(apps, sortedids, apks, repodir, archive): elif all(isinstance(item, str) for item in common.config[key]): packageNames = common.config[key] else: - raise TypeError('only accepts strings, lists, and tuples') + raise TypeError(_('only accepts strings, lists, and tuples')) requestsdict[command] = packageNames make_v0(appsWithPackages, apks, repodir, repodict, requestsdict) @@ -199,7 +203,7 @@ def make_v1(apps, packages, repodir, repodict, requestsdict): for package in packages: packageName = package['packageName'] if packageName not in apps: - logging.info('Ignoring package without metadata: ' + package['apkName']) + logging.info(_('Ignoring package without metadata: ') + package['apkName']) continue if packageName in output_packages: packagelist = output_packages[packageName] @@ -224,7 +228,7 @@ def make_v1(apps, packages, repodir, repodict, requestsdict): json.dump(output, fp, default=_index_encoder_default) if common.options.nosign: - logging.debug('index-v1 must have a signature, use `fdroid signindex` to create it!') + logging.debug(_('index-v1 must have a signature, use `fdroid signindex` to create it!')) else: signindex.config = common.config signindex.sign_index_v1(repodir, json_name) @@ -501,9 +505,9 @@ def make_v0(apps, apks, repodir, repodict, requestsdict): if 'repo_keyalias' in common.config: if common.options.nosign: - logging.info("Creating unsigned index in preparation for signing") + logging.info(_("Creating unsigned index in preparation for signing")) else: - logging.info("Creating signed index with this key (SHA256):") + logging.info(_("Creating signed index with this key (SHA256):")) logging.info("%s" % repo_pubkey_fingerprint) # Create a jar of the index... @@ -613,7 +617,7 @@ def download_repo_index(url_str, etag=None, verify_fingerprint=True): if verify_fingerprint: query = urllib.parse.parse_qs(url.query) if 'fingerprint' not in query: - raise VerificationException("No fingerprint in URL.") + raise VerificationException(_("No fingerprint in URL.")) fingerprint = query['fingerprint'][0] url = urllib.parse.SplitResult(url.scheme, url.netloc, url.path + '/index-v1.jar', '', '') @@ -635,7 +639,7 @@ def download_repo_index(url_str, etag=None, verify_fingerprint=True): # compare the fingerprint if verify_fingerprint is True if verify_fingerprint and fingerprint.upper() != public_key_fingerprint: - raise VerificationException("The repository's fingerprint does not match.") + raise VerificationException(_("The repository's fingerprint does not match.")) # load repository index from JSON index = json.loads(jar.read('index-v1.json').decode("utf-8")) @@ -655,7 +659,7 @@ def verify_jar_signature(file): :raises: VerificationException() if the JAR's signature could not be verified """ if not common.verify_apk_signature(file, jar=True): - raise VerificationException("The repository's index could not be verified.") + raise VerificationException(_("The repository's index could not be verified.")) def get_public_key_from_jar(jar): @@ -670,9 +674,9 @@ def get_public_key_from_jar(jar): # extract certificate from jar certs = [n for n in jar.namelist() if common.CERT_PATH_REGEX.match(n)] if len(certs) < 1: - raise VerificationException("Found no signing certificates for repository.") + raise VerificationException(_("Found no signing certificates for repository.")) if len(certs) > 1: - raise VerificationException("Found multiple signing certificates for repository.") + raise VerificationException(_("Found multiple signing certificates for repository.")) # extract public key from certificate public_key = common.get_certificate(jar.read(certs[0])) diff --git a/fdroidserver/init.py b/fdroidserver/init.py index 5a895464..a115f64c 100644 --- a/fdroidserver/init.py +++ b/fdroidserver/init.py @@ -27,6 +27,7 @@ import sys from argparse import ArgumentParser import logging +from . import _ from . import common from .exception import FDroidException @@ -53,15 +54,15 @@ def main(): parser = ArgumentParser() common.setup_global_opts(parser) parser.add_argument("-d", "--distinguished-name", default=None, - help="X.509 'Distiguished Name' used when generating keys") + help=_("X.509 'Distiguished Name' used when generating keys")) parser.add_argument("--keystore", default=None, - help="Path to the keystore for the repo signing key") + help=_("Path to the keystore for the repo signing key")) parser.add_argument("--repo-keyalias", default=None, - help="Alias of the repo signing key in the keystore") + help=_("Alias of the repo signing key in the keystore")) parser.add_argument("--android-home", default=None, - help="Path to the Android SDK (sometimes set in ANDROID_HOME)") + help=_("Path to the Android SDK (sometimes set in ANDROID_HOME)")) parser.add_argument("--no-prompt", action="store_true", default=False, - help="Do not prompt for Android SDK path, just fail") + help=_("Do not prompt for Android SDK path, just fail")) options = parser.parse_args() # find root install prefix @@ -106,8 +107,7 @@ def main(): 'AppData', 'Local', 'Android', 'android-sdk') while not options.no_prompt: try: - s = input('Enter the path to the Android SDK (' - + default_sdk_path + ') here:\n> ') + s = input(_('Enter the path to the Android SDK (%s) here:\n> ') % default_sdk_path) except KeyboardInterrupt: print('') sys.exit(1) @@ -231,21 +231,20 @@ def main(): common.write_to_config(test_config, 'keydname', c['keydname']) common.genkeystore(c) - logging.info('Built repo based in "' + fdroiddir + '"') - logging.info('with this config:') - logging.info(' Android SDK:\t\t\t' + config['sdk_path']) + msg = '\n' + msg += _('Built repo based in "%s" with this config:') % fdroiddir + msg += '\n\n Android SDK:\t\t\t' + config['sdk_path'] if aapt: - logging.info(' Android SDK Build Tools:\t' + os.path.dirname(aapt)) - logging.info(' Android NDK r12b (optional):\t$ANDROID_NDK') - logging.info(' Keystore for signing key:\t' + keystore) + msg += '\n Android SDK Build Tools:\t' + os.path.dirname(aapt) + msg += '\n Android NDK r12b (optional):\t$ANDROID_NDK' + msg += '\n ' + _('Keystore for signing key:\t') + keystore if repo_keyalias is not None: - logging.info(' Alias for key in store:\t' + repo_keyalias) - logging.info('\nTo complete the setup, add your APKs to "' + - os.path.join(fdroiddir, 'repo') + '"' + ''' + msg += '\n Alias for key in store:\t' + repo_keyalias + msg += '\n\n' + '''To complete the setup, add your APKs to "%s" then run "fdroid update -c; fdroid update". You might also want to edit "config.py" to set the URL, repo name, and more. You should also set up a signing key (a temporary one might have been automatically generated). For more info: https://f-droid.org/docs/Setup_an_F-Droid_App_Repo -and https://f-droid.org/docs/Signing_Process -''') +and https://f-droid.org/docs/Signing_Process''' % os.path.join(fdroiddir, 'repo') + logging.info(msg) diff --git a/fdroidserver/install.py b/fdroidserver/install.py index 9f126428..e3e21ff5 100644 --- a/fdroidserver/install.py +++ b/fdroidserver/install.py @@ -23,6 +23,7 @@ import glob from argparse import ArgumentParser import logging +from . import _ from . import common from .common import SdkToolsPopen from .exception import FDroidException @@ -49,19 +50,19 @@ def main(): # Parse command line... parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") common.setup_global_opts(parser) - parser.add_argument("appid", nargs='*', help="app-id with optional versionCode in the form APPID[:VERCODE]") + parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]")) parser.add_argument("-a", "--all", action="store_true", default=False, - help="Install all signed applications available") + help=_("Install all signed applications available")) options = parser.parse_args() if not options.appid and not options.all: - parser.error("option %s: If you really want to install all the signed apps, use --all" % "all") + parser.error(_("option %s: If you really want to install all the signed apps, use --all") % "all") config = common.read_config(options) output_dir = 'repo' if not os.path.isdir(output_dir): - logging.info("No signed output directory - nothing to do") + logging.info(_("No signed output directory - nothing to do")) sys.exit(0) if options.appid: @@ -84,7 +85,7 @@ def main(): for appid, apk in apks.items(): if not apk: - raise FDroidException("No signed apk available for %s" % appid) + raise FDroidException(_("No signed apk available for %s") % appid) else: @@ -95,10 +96,10 @@ def main(): # Get device list each time to avoid device not found errors devs = devices() if not devs: - raise FDroidException("No attached devices found") - logging.info("Installing %s..." % apk) + raise FDroidException(_("No attached devices found")) + logging.info(_("Installing %s...") % apk) for dev in devs: - logging.info("Installing %s on %s..." % (apk, dev)) + logging.info(_("Installing %s on %s...") % (apk, dev)) p = SdkToolsPopen(['adb', "-s", dev, "install", apk]) fail = "" for line in p.output.splitlines(): @@ -108,12 +109,12 @@ def main(): continue if fail == "INSTALL_FAILED_ALREADY_EXISTS": - logging.warn("%s is already installed on %s." % (apk, dev)) + logging.warn(_("%s is already installed on %s.") % (apk, dev)) else: - raise FDroidException("Failed to install %s on %s: %s" % ( + raise FDroidException(_("Failed to install %s on %s: %s") % ( apk, dev, fail)) - logging.info("\nFinished") + logging.info('\n' + _('Finished')) if __name__ == "__main__": diff --git a/fdroidserver/lint.py b/fdroidserver/lint.py index 04ef0f9c..492a09fc 100644 --- a/fdroidserver/lint.py +++ b/fdroidserver/lint.py @@ -22,6 +22,7 @@ import os import re import sys +from . import _ from . import common from . import metadata from . import rewritemeta @@ -47,7 +48,7 @@ https_enforcings = [ def forbid_shortener(domain): return (re.compile(r'https?://[^/]*' + re.escape(domain) + r'/.*'), - "URL shorteners should not be used") + _("URL shorteners should not be used")) http_url_shorteners = [ @@ -62,9 +63,9 @@ http_url_shorteners = [ http_checks = https_enforcings + http_url_shorteners + [ (re.compile(r'.*github\.com/[^/]+/[^/]+\.git'), - "Appending .git is not necessary"), + _("Appending .git is not necessary")), (re.compile(r'.*://[^/]*(github|gitlab|bitbucket|rawgit)[^/]*/([^/]+/){1,3}master'), - "Use /HEAD instead of /master to point at a file in the default branch"), + _("Use /HEAD instead of /master to point at a file in the default branch")), ] regex_checks = { @@ -73,44 +74,44 @@ regex_checks = { 'Repo': https_enforcings, 'IssueTracker': http_checks + [ (re.compile(r'.*github\.com/[^/]+/[^/]+/*$'), - "/issues is missing"), + _("/issues is missing")), (re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'), - "/issues is missing"), + _("/issues is missing")), ], 'Donate': http_checks + [ (re.compile(r'.*flattr\.com'), - "Flattr donation methods belong in the FlattrID flag"), + _("Flattr donation methods belong in the FlattrID flag")), ], 'Changelog': http_checks, 'Author Name': [ (re.compile(r'^\s'), - "Unnecessary leading space"), + _("Unnecessary leading space")), (re.compile(r'.*\s$'), - "Unnecessary trailing space"), + _("Unnecessary trailing space")), ], 'Summary': [ (re.compile(r'.*\b(free software|open source)\b.*', re.IGNORECASE), - "No need to specify that the app is Free Software"), + _("No need to specify that the app is Free Software")), (re.compile(r'.*((your|for).*android|android.*(app|device|client|port|version))', re.IGNORECASE), - "No need to specify that the app is for Android"), + _("No need to specify that the app is for Android")), (re.compile(r'.*[a-z0-9][.!?]( |$)'), - "Punctuation should be avoided"), + _("Punctuation should be avoided")), (re.compile(r'^\s'), - "Unnecessary leading space"), + _("Unnecessary leading space")), (re.compile(r'.*\s$'), - "Unnecessary trailing space"), + _("Unnecessary trailing space")), ], 'Description': [ (re.compile(r'\s*[*#][^ .]'), - "Invalid bulleted list"), + _("Invalid bulleted list")), (re.compile(r'^\s'), - "Unnecessary leading space"), + _("Unnecessary leading space")), (re.compile(r'.*\s$'), - "Unnecessary trailing space"), + _("Unnecessary trailing space")), (re.compile(r'.*([^[]|^)\[[^:[\]]+( |\]|$)'), - "Invalid link - use [http://foo.bar Link title] or [http://foo.bar]"), + _("Invalid link - use [http://foo.bar Link title] or [http://foo.bar]")), (re.compile(r'(^|.* )https?://[^ ]+'), - "Unlinkified link - use [http://foo.bar Link title] or [http://foo.bar]"), + _("Unlinkified link - use [http://foo.bar Link title] or [http://foo.bar]")), ], } @@ -155,7 +156,7 @@ def check_ucm_tags(app): and lastbuild.versionCode == app.CurrentVersionCode and not lastbuild.forcevercode and any(s in lastbuild.commit for s in '.,_-/')): - yield "Last used commit '%s' looks like a tag, but Update Check Mode is '%s'" % ( + yield _("Last used commit '%s' looks like a tag, but Update Check Mode is '%s'") % ( lastbuild.commit, app.UpdateCheckMode) @@ -163,11 +164,11 @@ def check_char_limits(app): limits = config['char_limits'] if len(app.Summary) > limits['summary']: - yield "Summary of length %s is over the %i char limit" % ( + yield _("Summary of length %s is over the %i char limit") % ( len(app.Summary), limits['summary']) if len(app.Description) > limits['description']: - yield "Description of length %s is over the %i char limit" % ( + yield _("Description of length %s is over the %i char limit") % ( len(app.Description), limits['description']) @@ -185,12 +186,12 @@ def check_old_links(app): for f in ['WebSite', 'SourceCode', 'IssueTracker', 'Changelog']: v = app.get(f) if any(s in v for s in old_sites): - yield "App is in '%s' but has a link to '%s'" % (app.Repo, v) + yield _("App is in '%s' but has a link to '%s'") % (app.Repo, v) def check_useless_fields(app): if app.UpdateCheckName == app.id: - yield "Update Check Name is set to the known app id - it can be removed" + yield _("Update Check Name is set to the known app id - it can be removed") filling_ucms = re.compile(r'^(Tags.*|RepoManifest.*)') @@ -199,12 +200,12 @@ filling_ucms = re.compile(r'^(Tags.*|RepoManifest.*)') def check_checkupdates_ran(app): if filling_ucms.match(app.UpdateCheckMode): if not app.AutoName and not app.CurrentVersion and app.CurrentVersionCode == '0': - yield "UCM is set but it looks like checkupdates hasn't been run yet" + yield _("UCM is set but it looks like checkupdates hasn't been run yet") def check_empty_fields(app): if not app.Categories: - yield "Categories are not set" + yield _("Categories are not set") all_categories = set([ @@ -231,12 +232,12 @@ all_categories = set([ def check_categories(app): for categ in app.Categories: if categ not in all_categories: - yield "Category '%s' is not valid" % categ + yield _("Category '%s' is not valid" % categ) def check_duplicates(app): if app.Name and app.Name == app.AutoName: - yield "Name '%s' is just the auto name - remove it" % app.Name + yield _("Name '%s' is just the auto name - remove it") % app.Name links_seen = set() for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']: @@ -245,25 +246,25 @@ def check_duplicates(app): continue v = v.lower() if v in links_seen: - yield "Duplicate link in '%s': %s" % (f, v) + yield _("Duplicate link in '%s': %s") % (f, v) else: links_seen.add(v) name = app.Name or app.AutoName if app.Summary and name: if app.Summary.lower() == name.lower(): - yield "Summary '%s' is just the app's name" % app.Summary + yield _("Summary '%s' is just the app's name") % app.Summary if app.Summary and app.Description and len(app.Description) == 1: if app.Summary.lower() == app.Description[0].lower(): - yield "Description '%s' is just the app's summary" % app.Summary + yield _("Description '%s' is just the app's summary") % app.Summary seenlines = set() for l in app.Description.splitlines(): if len(l) < 1: continue if l in seenlines: - yield "Description has a duplicate line" + yield _("Description has a duplicate line") seenlines.add(l) @@ -276,7 +277,7 @@ def check_mediawiki_links(app): url = um.group(1) for m, r in http_checks: if m.match(url): - yield "URL '%s' in Description: %s" % (url, r) + yield _("URL '%s' in Description: %s") % (url, r) def check_bulleted_lists(app): @@ -291,7 +292,7 @@ def check_bulleted_lists(app): if l[0] == lchar and l[1] == ' ': lcount += 1 if lcount > 2 and lchar not in validchars: - yield "Description has a list (%s) but it isn't bulleted (*) nor numbered (#)" % lchar + yield _("Description has a list (%s) but it isn't bulleted (*) nor numbered (#)") % lchar break else: lchar = l[0] @@ -304,18 +305,18 @@ def check_builds(app): for build in app.builds: if build.disable: if build.disable.startswith('Generated by import.py'): - yield "Build generated by `fdroid import` - remove disable line once ready" + yield _("Build generated by `fdroid import` - remove disable line once ready") continue for s in ['master', 'origin', 'HEAD', 'default', 'trunk']: if build.commit and build.commit.startswith(s): - yield "Branch '%s' used as commit in build '%s'" % (s, build.versionName) + yield _("Branch '%s' used as commit in build '%s'") % (s, build.versionName) for srclib in build.srclibs: ref = srclib.split('@')[1].split('/')[0] if ref.startswith(s): - yield "Branch '%s' used as commit in srclib '%s'" % (s, srclib) + yield _("Branch '%s' used as commit in srclib '%s'") % (s, srclib) for key in build.keys(): if key not in supported_flags: - yield key + ' is not an accepted build field' + yield _('%s is not an accepted build field') % key def check_files_dir(app): @@ -326,7 +327,7 @@ def check_files_dir(app): for name in os.listdir(dir_path): path = os.path.join(dir_path, name) if not (os.path.isfile(path) or name == 'signatures' or locale_pattern.match(name)): - yield "Found non-file at %s" % path + yield _("Found non-file at %s") % path continue files.add(name) @@ -334,45 +335,45 @@ def check_files_dir(app): for build in app.builds: for fname in build.patch: if fname not in files: - yield "Unknown file %s in build '%s'" % (fname, build.versionName) + yield _("Unknown file %s in build '%s'") % (fname, build.versionName) else: used.add(fname) for name in files.difference(used): if locale_pattern.match(name): continue - yield "Unused file at %s" % os.path.join(dir_path, name) + yield _("Unused file at %s") % os.path.join(dir_path, name) def check_format(app): if options.format and not rewritemeta.proper_format(app): - yield "Run rewritemeta to fix formatting" + yield _("Run rewritemeta to fix formatting") def check_license_tag(app): '''Ensure all license tags are in https://spdx.org/license-list''' if app.License.rstrip('+') not in SPDX: - yield 'Invalid license tag "%s"! Use only tags from https://spdx.org/license-list' \ + yield _('Invalid license tag "%s"! Use only tags from https://spdx.org/license-list') \ % (app.License) def check_extlib_dir(apps): dir_path = os.path.join('build', 'extlib') - files = set() - for root, dirs, names in os.walk(dir_path): - for name in names: - files.add(os.path.join(root, name)[len(dir_path) + 1:]) + unused_extlib_files = set() + for root, dirs, files in os.walk(dir_path): + for name in files: + unused_extlib_files.add(os.path.join(root, name)[len(dir_path) + 1:]) used = set() for app in apps: for build in app.builds: for path in build.extlibs: - if path not in files: - yield "%s: Unknown extlib %s in build '%s'" % (app.id, path, build.versionName) + if path not in unused_extlib_files: + yield _("%s: Unknown extlib %s in build '%s'") % (app.id, path, build.versionName) else: used.add(path) - for path in files.difference(used): + for path in unused_extlib_files.difference(used): if any(path.endswith(s) for s in [ '.gitignore', 'source.txt', 'origin.txt', 'md5.txt', @@ -381,7 +382,7 @@ def check_extlib_dir(apps): 'NOTICE', 'NOTICE.txt', ]): continue - yield "Unused extlib at %s" % os.path.join(dir_path, path) + yield _("Unused extlib at %s") % os.path.join(dir_path, path) def check_for_unsupported_metadata_files(basedir=""): @@ -397,7 +398,7 @@ def check_for_unsupported_metadata_files(basedir=""): for t in formats: exists = exists or os.path.exists(f + '.' + t) if not exists: - print('"' + f + '/" has no matching metadata file!') + print(_('"%s/" has no matching metadata file!') % f) return_value = True elif not os.path.splitext(f)[1][1:] in formats: print('"' + f.replace(basedir, '') @@ -415,8 +416,8 @@ def main(): parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]") common.setup_global_opts(parser) parser.add_argument("-f", "--format", action="store_true", default=False, - help="Also warn about formatting issues, like rewritemeta -l") - parser.add_argument("appid", nargs='*', help="app-id in the form APPID") + help=_("Also warn about formatting issues, like rewritemeta -l")) + parser.add_argument("appid", nargs='*', help=_("applicationId in the form APPID")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W diff --git a/fdroidserver/metadata.py b/fdroidserver/metadata.py index 25e05377..d1602d9e 100644 --- a/fdroidserver/metadata.py +++ b/fdroidserver/metadata.py @@ -35,6 +35,7 @@ except ImportError: YamlLoader = Loader import fdroidserver.common +from fdroidserver import _ from fdroidserver.exception import MetaDataException, FDroidException srclibs = None @@ -1519,4 +1520,4 @@ def write_metadata(metadatapath, app): def add_metadata_arguments(parser): '''add common command line flags related to metadata processing''' parser.add_argument("-W", default='error', - help="force errors to be warnings, or ignore") + help=_("force errors to be warnings, or ignore")) diff --git a/fdroidserver/publish.py b/fdroidserver/publish.py index ad200906..94cf166b 100644 --- a/fdroidserver/publish.py +++ b/fdroidserver/publish.py @@ -26,6 +26,7 @@ import hashlib from argparse import ArgumentParser import logging +from . import _ from . import common from . import metadata from .common import FDroidPopen, SdkToolsPopen @@ -43,7 +44,7 @@ def main(): parser = ArgumentParser(usage="%(prog)s [options] " "[APPID[:VERCODE] [APPID[:VERCODE] ...]]") common.setup_global_opts(parser) - parser.add_argument("appid", nargs='*', help="app-id with optional versionCode in the form APPID[:VERCODE]") + parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -51,27 +52,27 @@ def main(): 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!') + logging.critical(_('Java JDK not found! Install in standard location or set java_paths!')) sys.exit(1) log_dir = 'logs' if not os.path.isdir(log_dir): - logging.info("Creating log directory") + logging.info(_("Creating log directory")) os.makedirs(log_dir) tmp_dir = 'tmp' if not os.path.isdir(tmp_dir): - logging.info("Creating temporary directory") + logging.info(_("Creating temporary directory")) os.makedirs(tmp_dir) output_dir = 'repo' if not os.path.isdir(output_dir): - logging.info("Creating output directory") + logging.info(_("Creating output directory")) os.makedirs(output_dir) unsigned_dir = 'unsigned' if not os.path.isdir(unsigned_dir): - logging.warning("No unsigned directory - nothing to do") + logging.warning(_("No unsigned directory - nothing to do")) sys.exit(1) if not os.path.exists(config['keystore']): @@ -95,7 +96,7 @@ def main(): m.update(appid.encode('utf-8')) keyalias = m.hexdigest()[:8] if keyalias in allaliases: - logging.error("There is a keyalias collision - publishing halted") + logging.error(_("There is a keyalias collision - publishing halted")) sys.exit(1) allaliases.append(keyalias) logging.info("{0} apps, {0} key aliases".format(len(allapps), @@ -208,13 +209,13 @@ def main(): 'SHA1withRSA', '-digestalg', 'SHA1', apkfile, keyalias], envs=env_vars) if p.returncode != 0: - raise BuildException("Failed to sign application") + raise BuildException(_("Failed to sign application")) # Zipalign it... p = SdkToolsPopen(['zipalign', '-v', '4', apkfile, os.path.join(output_dir, apkfilename)]) if p.returncode != 0: - raise BuildException("Failed to align application") + raise BuildException(_("Failed to align application")) os.remove(apkfile) # Move the source tarball into the output directory... diff --git a/fdroidserver/rewritemeta.py b/fdroidserver/rewritemeta.py index ae4627eb..776e1f14 100644 --- a/fdroidserver/rewritemeta.py +++ b/fdroidserver/rewritemeta.py @@ -22,6 +22,7 @@ import os import logging import io +from . import _ from . import common from . import metadata @@ -51,10 +52,10 @@ def main(): parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]") common.setup_global_opts(parser) parser.add_argument("-l", "--list", action="store_true", default=False, - help="List files that would be reformatted") + help=_("List files that would be reformatted")) parser.add_argument("-t", "--to", default=None, - help="Rewrite to a specific format: " + ', '.join(supported)) - parser.add_argument("appid", nargs='*', help="app-id in the form APPID") + help=_("Rewrite to a specific format: ") + ', '.join(supported)) + parser.add_argument("appid", nargs='*', help=_("applicationId in the form APPID")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -66,21 +67,21 @@ def main(): apps = common.read_app_args(options.appid, allapps, False) if options.list and options.to is not None: - parser.error("Cannot use --list and --to at the same time") + parser.error(_("Cannot use --list and --to at the same time")) if options.to is not None and options.to not in supported: - parser.error("Unsupported metadata format, use: --to [" + ' '.join(supported) + "]") + parser.error(_("Unsupported metadata format, use: --to [%s]") % ' '.join(supported)) for appid, app in apps.items(): path = app.metadatapath base, ext = common.get_extension(path) if not options.to and ext not in supported: - logging.info("Ignoring %s file at '%s'" % (ext, path)) + logging.info(_("Ignoring %s file at '%s'") % (ext, path)) continue elif options.to is not None: - logging.info("rewriting '%s' to %s" % (appid, options.to)) + logging.info(_("rewriting '%s' to %s") % (appid, options.to)) else: - logging.info("rewriting '%s'" % (appid)) + logging.info(_("rewriting '%s'") % (appid)) to_ext = ext if options.to is not None: @@ -107,7 +108,7 @@ def main(): if ext != to_ext: os.remove(path) - logging.debug("Finished.") + logging.debug(_("Finished")) if __name__ == "__main__": diff --git a/fdroidserver/scanner.py b/fdroidserver/scanner.py index f768e678..874f0b00 100644 --- a/fdroidserver/scanner.py +++ b/fdroidserver/scanner.py @@ -22,6 +22,7 @@ import traceback from argparse import ArgumentParser import logging +from . import _ from . import common from . import metadata from .exception import BuildException, VCSException @@ -165,20 +166,20 @@ def scan_source(build_dir, build): return any(command.match(line) for command in gradle_compile_commands) # Iterate through all files in the source code - for dirpath, dirnames, filenames in os.walk(build_dir, topdown=True): + for root, dirs, files in os.walk(build_dir, topdown=True): # It's topdown, so checking the basename is enough for ignoredir in ('.hg', '.git', '.svn', '.bzr'): - if ignoredir in dirnames: - dirnames.remove(ignoredir) + if ignoredir in dirs: + dirs.remove(ignoredir) - for curfile in filenames: + for curfile in files: if curfile in ['.DS_Store']: continue # Path (relative) to the file - filepath = os.path.join(dirpath, curfile) + filepath = os.path.join(root, curfile) if os.path.islink(filepath): continue @@ -256,7 +257,7 @@ def main(): # Parse command line... parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") common.setup_global_opts(parser) - parser.add_argument("appid", nargs='*', help="app-id with optional versionCode in the form APPID[:VERCODE]") + parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -329,8 +330,8 @@ def main(): appid, traceback.format_exc())) probcount += 1 - logging.info("Finished:") - print("%d problems found" % probcount) + logging.info(_("Finished")) + print(_("%d problems found") % probcount) if __name__ == "__main__": diff --git a/fdroidserver/server.py b/fdroidserver/server.py index 3b36ecee..aa15284f 100644 --- a/fdroidserver/server.py +++ b/fdroidserver/server.py @@ -29,6 +29,7 @@ from argparse import ArgumentParser import logging import shutil +from . import _ from . import common from .exception import FDroidException @@ -154,7 +155,7 @@ def update_awsbucket_libcloud(repo_section): if obj.name.startswith(upload_dir + '/'): objs[obj.name] = obj - for root, _, files in os.walk(os.path.join(os.getcwd(), repo_section)): + for root, dirs, files in os.walk(os.path.join(os.getcwd(), repo_section)): for name in files: upload = False file_to_upload = os.path.join(root, name) @@ -307,9 +308,9 @@ def update_localcopy(repo_section, local_copy_dir): def _get_size(start_path='.'): '''get size of all files in a dir https://stackoverflow.com/a/1392549''' total_size = 0 - for dirpath, dirnames, filenames in os.walk(start_path): - for f in filenames: - fp = os.path.join(dirpath, f) + for root, dirs, files in os.walk(start_path): + for f in files: + fp = os.path.join(root, f) total_size += os.path.getsize(fp) return total_size @@ -577,19 +578,19 @@ def main(): # Parse command line... parser = ArgumentParser() common.setup_global_opts(parser) - parser.add_argument("command", help="command to execute, either 'init' or 'update'") + parser.add_argument("command", help=_("command to execute, either 'init' or 'update'")) parser.add_argument("-i", "--identity-file", default=None, - help="Specify an identity file to provide to SSH for rsyncing") + help=_("Specify an identity file to provide to SSH for rsyncing")) parser.add_argument("--local-copy-dir", default=None, - help="Specify a local folder to sync the repo to") + help=_("Specify a local folder to sync the repo to")) parser.add_argument("--no-checksum", action="store_true", default=False, - help="Don't use rsync checksums") + help=_("Don't use rsync checksums")) options = parser.parse_args() config = common.read_config(options) if options.command != 'init' and options.command != 'update': - logging.critical("The only commands currently supported are 'init' and 'update'") + logging.critical(_("The only commands currently supported are 'init' and 'update'")) sys.exit(1) if config.get('nonstandardwebroot') is True: @@ -605,7 +606,7 @@ def main(): elif len(s) == 2: host, fdroiddir = s else: - logging.error('Malformed serverwebroot line: ' + serverwebroot) + logging.error(_('Malformed serverwebroot line:') + ' ' + serverwebroot) sys.exit(1) repobase = os.path.basename(fdroiddir) if standardwebroot and repobase != 'fdroid': @@ -624,7 +625,7 @@ def main(): if local_copy_dir is not None: fdroiddir = local_copy_dir.rstrip('/') if os.path.exists(fdroiddir) and not os.path.isdir(fdroiddir): - logging.error('local_copy_dir must be directory, not a file!') + logging.error(_('local_copy_dir must be directory, not a file!')) sys.exit(1) if not os.path.exists(os.path.dirname(fdroiddir)): logging.error('The root dir for local_copy_dir "' @@ -632,7 +633,7 @@ def main(): + '" does not exist!') sys.exit(1) if not os.path.isabs(fdroiddir): - logging.error('local_copy_dir must be an absolute path!') + logging.error(_('local_copy_dir must be an absolute path!')) sys.exit(1) repobase = os.path.basename(fdroiddir) if standardwebroot and repobase != 'fdroid': @@ -652,8 +653,8 @@ def main(): and not config.get('binary_transparency_remote') \ and not config.get('virustotal_apikey') \ and local_copy_dir is None: - logging.warn('No option set! Edit your config.py to set at least one among:\n' - + 'serverwebroot, servergitmirrors, local_copy_dir, awsbucket, virustotal_apikey, androidobservatory, or binary_transparency_remote') + logging.warn(_('No option set! Edit your config.py to set at least one of these:') + + '\nserverwebroot, servergitmirrors, local_copy_dir, awsbucket, virustotal_apikey, androidobservatory, or binary_transparency_remote') sys.exit(1) repo_sections = ['repo'] diff --git a/fdroidserver/signatures.py b/fdroidserver/signatures.py index 298711ae..fb6c53a9 100644 --- a/fdroidserver/signatures.py +++ b/fdroidserver/signatures.py @@ -22,6 +22,7 @@ import os import sys import logging +from . import _ from . import common from . import net from .exception import FDroidException @@ -52,7 +53,7 @@ def extract(config, options): os.mkdir(tmp_dir) if not options.APK or len(options.APK) <= 0: - logging.critical('no APK supplied') + logging.critical(_('no APK supplied')) sys.exit(1) # iterate over supplied APKs downlaod and extract them... @@ -61,21 +62,21 @@ def extract(config, options): try: if os.path.isfile(apk): sigdir = extract_signature(apk) - logging.info('fetched singatures for %s -> %s', apk, sigdir) + logging.info(_('fetched signatures for %s -> %s'), apk, sigdir) elif httpre.match(apk): if apk.startswith('https') or options.no_check_https: try: tmp_apk = os.path.join(tmp_dir, 'signed.apk') net.download_file(apk, tmp_apk) sigdir = extract_signature(tmp_apk) - logging.info('fetched singatures for %s -> %s', apk, sigdir) + logging.info(_('fetched signatures for %s -> %s'), apk, sigdir) finally: if tmp_apk and os.path.exists(tmp_apk): os.remove(tmp_apk) else: - logging.warn('refuse downloading via insecure http connection (use https or specify --no-https-check): %s', apk) + logging.warn(_('refuse downloading via insecure http connection (use https or specify --no-https-check): %s'), apk) except FDroidException as e: - logging.warning("failed fetching signatures for '%s': %s", apk, e) + logging.warning(_("failed fetching signatures for '%s': %s"), apk, e) if e.detail: logging.debug(e.detail) @@ -88,7 +89,7 @@ def main(): parser = ArgumentParser(usage="%(prog)s [options] APK [APK...]") common.setup_global_opts(parser) parser.add_argument("APK", nargs='*', - help="signed APK, either a file-path or Https-URL are fine here.") + help=_("signed APK, either a file-path or HTTPS URL.")) parser.add_argument("--no-check-https", action="store_true", default=False) options = parser.parse_args() diff --git a/fdroidserver/signindex.py b/fdroidserver/signindex.py index 6b5dd983..cbd55239 100644 --- a/fdroidserver/signindex.py +++ b/fdroidserver/signindex.py @@ -21,6 +21,7 @@ import zipfile from argparse import ArgumentParser import logging +from . import _ from . import common from .exception import FDroidException @@ -87,7 +88,7 @@ def main(): if 'jarsigner' not in config: raise FDroidException( - 'Java jarsigner not found! Install in standard location or set java_paths!') + _('Java jarsigner not found! Install in standard location or set java_paths!')) repodirs = ['repo'] if config['archive_older'] != 0: @@ -114,7 +115,7 @@ def main(): signed += 1 if signed == 0: - logging.info("Nothing to do") + logging.info(_("Nothing to do")) if __name__ == "__main__": diff --git a/fdroidserver/stats.py b/fdroidserver/stats.py index 8a1aec2a..e87db35f 100644 --- a/fdroidserver/stats.py +++ b/fdroidserver/stats.py @@ -30,6 +30,7 @@ import logging import subprocess from collections import Counter +from . import _ from . import common from . import metadata @@ -61,12 +62,12 @@ def main(): parser = ArgumentParser() common.setup_global_opts(parser) parser.add_argument("-d", "--download", action="store_true", default=False, - help="Download logs we don't have") + help=_("Download logs we don't have")) parser.add_argument("--recalc", action="store_true", default=False, - help="Recalculate aggregate stats - use when changes " - "have been made that would invalidate old cached data.") + help=_("Recalculate aggregate stats - use when changes " + "have been made that would invalidate old cached data.")) parser.add_argument("--nologs", action="store_true", default=False, - help="Don't do anything logs-related") + help=_("Don't do anything logs-related")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -171,10 +172,10 @@ def main(): uri = match.group('uri') if not uri.endswith('.apk'): continue - _, apkname = os.path.split(uri) + _ignored, apkname = os.path.split(uri) app = knownapks.getapp(apkname) if app: - appid, _ = app + appid, _ignored = app today['apps'][appid] += 1 # Strip the '.apk' from apkname appver = apkname[:-4] @@ -298,7 +299,7 @@ def main(): for apk in unknownapks: logging.info(apk) - logging.info("Finished.") + logging.info(_("Finished")) if __name__ == "__main__": diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 3d1635a2..2c46b7f2 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -37,6 +37,7 @@ from binascii import hexlify from PIL import Image import logging +from . import _ from . import common from . import index from . import metadata @@ -1697,34 +1698,34 @@ def main(): parser = ArgumentParser() common.setup_global_opts(parser) parser.add_argument("--create-key", action="store_true", default=False, - help="Create a repo signing key in a keystore") + help=_("Create a repo signing key in a keystore")) parser.add_argument("-c", "--create-metadata", action="store_true", default=False, - help="Create skeleton metadata files that are missing") + help=_("Create skeleton metadata files that are missing")) parser.add_argument("--delete-unknown", action="store_true", default=False, - help="Delete APKs and/or OBBs without metadata from the repo") + help=_("Delete APKs and/or OBBs without metadata from the repo")) parser.add_argument("-b", "--buildreport", action="store_true", default=False, - help="Report on build data status") + help=_("Report on build data status")) parser.add_argument("-i", "--interactive", default=False, action="store_true", - help="Interactively ask about things that need updating.") + help=_("Interactively ask about things that need updating.")) parser.add_argument("-I", "--icons", action="store_true", default=False, - help="Resize all the icons exceeding the max pixel size and exit") + help=_("Resize all the icons exceeding the max pixel size and exit")) parser.add_argument("-e", "--editor", default="/etc/alternatives/editor", - help="Specify editor to use in interactive mode. Default " + + help=_("Specify editor to use in interactive mode. Default ") + "is /etc/alternatives/editor") parser.add_argument("-w", "--wiki", default=False, action="store_true", - help="Update the wiki") + help=_("Update the wiki")) parser.add_argument("--pretty", action="store_true", default=False, - help="Produce human-readable index.xml") + help=_("Produce human-readable index.xml")) parser.add_argument("--clean", action="store_true", default=False, - help="Clean update - don't uses caches, reprocess all apks") + 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") + 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") + help=_("Use date from apk instead of current time for newly added apks")) parser.add_argument("--rename-apks", action="store_true", default=False, - help="Rename APK files that do not match package.name_123.apk") + help=_("Rename APK files that do not match package.name_123.apk")) parser.add_argument("--allow-disabled-algorithms", action="store_true", default=False, - help="Include APKs that are signed with disabled algorithms like MD5") + help=_("Include APKs that are signed with disabled algorithms like MD5")) metadata.add_metadata_arguments(parser) options = parser.parse_args() metadata.warnings_action = options.W @@ -1899,7 +1900,7 @@ def main(): if options.wiki: update_wiki(apps, sortedids, apks + archapks) - logging.info("Finished.") + logging.info(_("Finished")) if __name__ == "__main__": diff --git a/fdroidserver/verify.py b/fdroidserver/verify.py index bd629f97..9c401558 100644 --- a/fdroidserver/verify.py +++ b/fdroidserver/verify.py @@ -23,6 +23,7 @@ import requests from argparse import ArgumentParser import logging +from . import _ from . import common from . import net from .exception import FDroidException @@ -38,19 +39,19 @@ def main(): # Parse command line... parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") common.setup_global_opts(parser) - parser.add_argument("appid", nargs='*', help="app-id with optional versionCode in the form APPID[:VERCODE]") + parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]")) options = parser.parse_args() config = common.read_config(options) tmp_dir = 'tmp' if not os.path.isdir(tmp_dir): - logging.info("Creating temporary directory") + logging.info(_("Creating temporary directory")) os.makedirs(tmp_dir) unsigned_dir = 'unsigned' if not os.path.isdir(unsigned_dir): - logging.error("No unsigned directory - nothing to do") + logging.error(_("No unsigned directory - nothing to do")) sys.exit(0) verified = 0 @@ -83,7 +84,7 @@ def main(): try: net.download_file(url.replace('/repo', '/archive'), dldir=tmp_dir) except requests.exceptions.HTTPError as e: - raise FDroidException('Downloading %s failed. %s', (url, e)) + raise FDroidException(_('Downloading %s failed. %s'), (url, e)) compare_result = common.verify_apks( remoteapk, @@ -99,7 +100,7 @@ def main(): logging.info("...NOT verified - {0}".format(e)) notverified += 1 - logging.info("Finished") + logging.info(_("Finished")) logging.info("{0} successfully verified".format(verified)) logging.info("{0} NOT verified".format(notverified)) diff --git a/setup.py b/setup.py index 6ee8c3a8..ef153039 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ setup(name='fdroidserver', author='The F-Droid Project', author_email='team@f-droid.org', url='https://f-droid.org', + license='AGPL-3.0', packages=['fdroidserver', 'fdroidserver.asynchronousfilereader'], scripts=['fdroid', 'fd-commit', 'makebuildserver'], data_files=[ @@ -29,6 +30,7 @@ setup(name='fdroidserver', 'examples/public-read-only-s3-bucket-policy.json', 'examples/template.yml']), ], + python_requires='>=3.4', install_requires=[ 'clint', 'GitPython', diff --git a/tests/update.TestCase b/tests/update.TestCase index 5d292f4b..76a93802 100755 --- a/tests/update.TestCase +++ b/tests/update.TestCase @@ -8,11 +8,13 @@ import logging import optparse import os import shutil +import subprocess import sys import tempfile import unittest import yaml from binascii import unhexlify +from distutils.version import LooseVersion localmodule = os.path.realpath( os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..')) @@ -397,8 +399,15 @@ class UpdateTest(unittest.TestCase): self.assertFalse(os.path.exists(os.path.join('archive', apkName))) self.assertTrue(os.path.exists(os.path.join('repo', apkName))) + javac = config['jarsigner'].replace('jarsigner', 'javac') + v = subprocess.check_output([javac, '-version'], stderr=subprocess.STDOUT)[6:-1].decode('utf-8') + if LooseVersion(v) < LooseVersion('1.8.0_132'): + print('SKIPPING: running tests with old Java (' + v + ')') + return + # this test only works on systems with fully updated Java/jarsigner # that has MD5 listed in jdk.jar.disabledAlgorithms in java.security + # https://blogs.oracle.com/java-platform-group/oracle-jre-will-no-longer-trust-md5-signed-code-by-default skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo', knownapks, allow_disabled_algorithms=False,