1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-07-14 21:10:09 +02:00

Merge branch 'implement-gettext' into 'master'

first implementation of localization using gettext

Closes #342

See merge request fdroid/fdroidserver!338
This commit is contained in:
Hans-Christoph Steiner 2017-09-15 19:29:12 +00:00
commit a2eaf37394
27 changed files with 335 additions and 272 deletions

3
.gitignore vendored
View File

@ -44,3 +44,6 @@ makebuildserver.config.py
/tests/repo/info.guardianproject.checkey/en-US/phoneScreenshots/checkey.png /tests/repo/info.guardianproject.checkey/en-US/phoneScreenshots/checkey.png
/tests/urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234.apk /tests/urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234.apk
/unsigned/ /unsigned/
# generated by gettext
locale/*/LC_MESSAGES/fdroidserver.mo

View File

@ -6,10 +6,12 @@ test:
- cd tests - cd tests
- ./complete-ci-tests - ./complete-ci-tests
# Test that the parsing of the .txt format didn't change # Test that the parsing of the .txt format didn't change from last
# from last released version. # released version. Ensure that the official tags are included when
# running these tests on forks as well.
metadata_v0: metadata_v0:
script: script:
- git fetch https://gitlab.com/fdroid/fdroidserver 0.8
- cd tests - cd tests
- export GITCOMMIT=`git describe` - export GITCOMMIT=`git describe`
- git checkout 0.8 # bump after release - git checkout 0.8 # bump after release

50
fdroid
View File

@ -22,36 +22,38 @@ import logging
import fdroidserver.common import fdroidserver.common
import fdroidserver.metadata import fdroidserver.metadata
from fdroidserver import _
from argparse import ArgumentError from argparse import ArgumentError
from collections import OrderedDict from collections import OrderedDict
commands = OrderedDict([ commands = OrderedDict([
("build", "Build a package from source"), ("build", _("Build a package from source")),
("init", "Quickly start a new repository"), ("init", _("Quickly start a new repository")),
("publish", "Sign and place packages in the repo"), ("publish", _("Sign and place packages in the repo")),
("gpgsign", "Add gpg signatures for packages in repo"), ("gpgsign", _("Add gpg signatures for packages in repo")),
("update", "Update repo information for new packages"), ("update", _("Update repo information for new packages")),
("verify", "Verify the integrity of downloaded packages"), ("verify", _("Verify the integrity of downloaded packages")),
("checkupdates", "Check for updates to applications"), ("checkupdates", _("Check for updates to applications")),
("import", "Add a new application from its source code"), ("import", _("Add a new application from its source code")),
("install", "Install built packages on devices"), ("install", _("Install built packages on devices")),
("readmeta", "Read all the metadata files and exit"), ("readmeta", _("Read all the metadata files and exit")),
("rewritemeta", "Rewrite all the metadata files"), ("rewritemeta", _("Rewrite all the metadata files")),
("lint", "Warn about possible metadata errors"), ("lint", _("Warn about possible metadata errors")),
("scanner", "Scan the source code of a package"), ("scanner", _("Scan the source code of a package")),
("dscanner", "Dynamically scan APKs post build"), ("dscanner", _("Dynamically scan APKs post build")),
("stats", "Update the stats of the repo"), ("stats", _("Update the stats of the repo")),
("server", "Interact with the repo HTTP server"), ("server", _("Interact with the repo HTTP server")),
("signindex", "Sign indexes created using update --nosign"), ("signindex", _("Sign indexes created using update --nosign")),
("btlog", "Update the binary transparency log for a URL"), ("btlog", _("Update the binary transparency log for a URL")),
("signatures", "Extract signatures from APKs"), ("signatures", _("Extract signatures from APKs")),
]) ])
def print_help(): def print_help():
print("usage: fdroid [-h|--help|--version] <command> [<args>]") print(_("usage: fdroid [-h|--help|--version] <command> [<args>]"))
print("") print("")
print("Valid commands are:") print(_("Valid commands are:"))
for cmd, summary in commands.items(): for cmd, summary in commands.items():
print(" " + cmd + ' ' * (15 - len(cmd)) + summary) print(" " + cmd + ' ' * (15 - len(cmd)) + summary)
print("") print("")
@ -70,7 +72,7 @@ def main():
sys.exit(0) sys.exit(0)
elif command == '--version': elif command == '--version':
import os.path import os.path
output = 'no version info found!' output = _('no version info found!')
cmddir = os.path.realpath(os.path.dirname(__file__)) cmddir = os.path.realpath(os.path.dirname(__file__))
moduledir = os.path.realpath(os.path.dirname(fdroidserver.common.__file__) + '/..') moduledir = os.path.realpath(os.path.dirname(fdroidserver.common.__file__) + '/..')
if cmddir == moduledir: if cmddir == moduledir:
@ -97,7 +99,7 @@ def main():
print(output), print(output),
sys.exit(0) sys.exit(0)
else: else:
print("Command '%s' not recognised.\n" % command) print(_("Command '%s' not recognised.\n" % command))
print_help() print_help()
sys.exit(1) sys.exit(1)
@ -143,7 +145,7 @@ def main():
# These should only be unexpected crashes due to bugs in the code # 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 # str(e) often doesn't contain a reason, so just show the backtrace
except Exception as e: except Exception as e:
logging.critical("Unknown exception found!") logging.critical(_("Unknown exception found!"))
raise raise
sys.exit(0) sys.exit(0)

View File

@ -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

View File

@ -40,9 +40,10 @@ import xml.dom.minidom
import zipfile import zipfile
from argparse import ArgumentParser from argparse import ArgumentParser
from .exception import FDroidException from . import _
from . import common from . import common
from . import server from . import server
from .exception import FDroidException
options = None options = None
@ -117,12 +118,12 @@ For more info on this idea:
jarin.close() jarin.close()
gitrepo.index.add([repof, ]) gitrepo.index.add([repof, ])
files = [] output_files = []
for root, dirs, filenames in os.walk(repodir): for root, dirs, files in os.walk(repodir):
for f in filenames: for f in files:
files.append(os.path.relpath(os.path.join(root, f), repodir)) output_files.append(os.path.relpath(os.path.join(root, f), repodir))
output = collections.OrderedDict() output = collections.OrderedDict()
for f in sorted(files): for f in sorted(output_files):
repofile = os.path.join(repodir, f) repofile = os.path.join(repodir, f)
stat = os.stat(repofile) stat = os.stat(repofile)
output[f] = ( output[f] = (
@ -151,11 +152,11 @@ def main():
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("--git-repo", parser.add_argument("--git-repo",
default=os.path.join(os.getcwd(), 'binary_transparency'), 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', 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, 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() options = parser.parse_args()
if options.verbose: if options.verbose:

View File

@ -32,6 +32,7 @@ from configparser import ConfigParser
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from . import net from . import net
from . import metadata 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... # Helper to copy the contents of a directory to the server...
def send_dir(path): def send_dir(path):
root = os.path.dirname(path) startroot = os.path.dirname(path)
main = os.path.basename(path) main = os.path.basename(path)
ftp.mkdir(main) ftp.mkdir(main)
for r, d, f in os.walk(path): for root, dirs, files in os.walk(path):
rr = os.path.relpath(r, root) rr = os.path.relpath(root, startroot)
ftp.chdir(rr) ftp.chdir(rr)
for dd in d: for d in dirs:
ftp.mkdir(dd) ftp.mkdir(d)
for ff in f: for f in files:
lfile = os.path.join(root, rr, ff) lfile = os.path.join(startroot, rr, f)
if not os.path.islink(lfile): if not os.path.islink(lfile):
ftp.put(lfile, ff) ftp.put(lfile, f)
ftp.chmod(ff, os.stat(lfile).st_mode) ftp.chmod(f, os.stat(lfile).st_mode)
for i in range(len(rr.split('/'))): for i in range(len(rr.split('/'))):
ftp.chdir('..') ftp.chdir('..')
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.mkdir(d)
ftp.chdir(d) ftp.chdir(d)
ftp.put(libsrc, lp[-1]) ftp.put(libsrc, lp[-1])
for _ in lp[:-1]: for _ignored in lp[:-1]:
ftp.chdir('..') ftp.chdir('..')
# Copy any srclibs that are required... # Copy any srclibs that are required...
srclibpaths = [] srclibpaths = []
@ -995,33 +996,33 @@ def parse_commandline():
parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]")
common.setup_global_opts(parser) 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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", parser.add_argument("-w", "--wiki", default=False, action="store_true",
help="Update the wiki") help=_("Update the wiki"))
metadata.add_metadata_arguments(parser) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -1316,7 +1317,7 @@ def main():
logging.info("Cleaning up after ourselves.") logging.info("Cleaning up after ourselves.")
docker.clean() docker.clean()
logging.info("Finished.") logging.info(_("Finished"))
if len(build_succeeded) > 0: if len(build_succeeded) > 0:
logging.info(str(len(build_succeeded)) + ' builds succeeded') logging.info(str(len(build_succeeded)) + ' builds succeeded')
if len(failed_apps) > 0: if len(failed_apps) > 0:

View File

@ -30,6 +30,7 @@ from distutils.version import LooseVersion
import logging import logging
import copy import copy
from . import _
from . import common from . import common
from . import metadata from . import metadata
from .exception import VCSException, FDroidException, MetaDataException 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 # Return all directories under startdir that contain any of the manifest
# files, and thus are probably an Android project. # files, and thus are probably an Android project.
def dirs_with_manifest(startdir): def dirs_with_manifest(startdir):
for r, d, f in os.walk(startdir): for root, dirs, files in os.walk(startdir):
if any(m in f for m in [ if any(m in files for m in [
'AndroidManifest.xml', 'pom.xml', 'build.gradle']): 'AndroidManifest.xml', 'pom.xml', 'build.gradle']):
yield r yield root
# Tries to find a new subdir starting from the root build_dir. Returns said # Tries to find a new subdir starting from the root build_dir. Returns said
@ -509,15 +510,15 @@ def main():
# Parse command line... # Parse command line...
parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]") parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]")
common.setup_global_opts(parser) 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, 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, 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, parser.add_argument("--commit", action="store_true", default=False,
help="Commit changes") help=_("Commit changes"))
parser.add_argument("--gplay", action="store_true", default=False, 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) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -567,7 +568,7 @@ def main():
except Exception as e: except Exception as e:
logging.error("...checkupdate failed for {0} : {1}".format(appid, e)) logging.error("...checkupdate failed for {0} : {1}".format(appid, e))
logging.info("Finished.") logging.info(_("Finished"))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -49,6 +49,7 @@ from pyasn1.error import PyAsn1Error
from distutils.util import strtobool from distutils.util import strtobool
import fdroidserver.metadata import fdroidserver.metadata
from fdroidserver import _
from fdroidserver.exception import FDroidException, VCSException, BuildException from fdroidserver.exception import FDroidException, VCSException, BuildException
from .asynchronousfilereader import AsynchronousFileReader from .asynchronousfilereader import AsynchronousFileReader
@ -123,9 +124,9 @@ default_config = {
def setup_global_opts(parser): def setup_global_opts(parser):
parser.add_argument("-v", "--verbose", action="store_true", default=False, 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, 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): 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, 'res'),
os.path.join(app_dir, 'src', 'main', 'res'), os.path.join(app_dir, 'src', 'main', 'res'),
]: ]:
for r, d, f in os.walk(res_dir): for root, dirs, files in os.walk(res_dir):
if os.path.basename(r) == 'values': if os.path.basename(root) == 'values':
xmlfiles += [os.path.join(r, x) for x in f if x.endswith('.xml')] xmlfiles += [os.path.join(root, x) for x in files if x.endswith('.xml')]
name = string[len('@string/'):] name = string[len('@string/'):]

View File

@ -24,7 +24,9 @@ from time import sleep
from argparse import ArgumentParser from argparse import ArgumentParser
from subprocess import CalledProcessError, check_output from subprocess import CalledProcessError, check_output
from fdroidserver import common, metadata from . import _
from . import common
from . import metadata
try: try:
from docker import Client from docker import Client
@ -407,25 +409,25 @@ def main():
parser.add_argument( parser.add_argument(
"app_id", nargs='*', "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( parser.add_argument(
"-l", "--latest", action="store_true", default=False, "-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( parser.add_argument(
"--clean-after", default=False, action='store_true', "--clean-after", default=False, action='store_true',
help="Clean after all scans have finished") help=_("Clean after all scans have finished"))
parser.add_argument( parser.add_argument(
"--clean-before", default=False, action='store_true', "--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( parser.add_argument(
"--clean-only", default=False, action='store_true', "--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( parser.add_argument(
"--init-only", default=False, action='store_true', "--init-only", default=False, action='store_true',
help="Prepare drozer to run a scan") help=_("Prepare drozer to run a scan"))
parser.add_argument( parser.add_argument(
"--repo-path", default="repo", action="store", "--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() options = parser.parse_args()
config = common.read_config(options) config = common.read_config(options)

View File

@ -21,6 +21,7 @@ import glob
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from .common import FDroidPopen from .common import FDroidPopen
from .exception import FDroidException from .exception import FDroidException
@ -46,7 +47,7 @@ def main():
for output_dir in repodirs: for output_dir in repodirs:
if not os.path.isdir(output_dir): 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... # Process any apks that are waiting to be signed...
for f in sorted(glob.glob(os.path.join(output_dir, '*.*'))): for f in sorted(glob.glob(os.path.join(output_dir, '*.*'))):

View File

@ -26,6 +26,7 @@ from argparse import ArgumentParser
from configparser import ConfigParser from configparser import ConfigParser
import logging import logging
from . import _
from . import common from . import common
from . import metadata from . import metadata
from .exception import FDroidException from .exception import FDroidException
@ -59,7 +60,7 @@ def getrepofrompage(url):
repo = page[index + 9:] repo = page[index + 9:]
index = repo.find('<') index = repo.find('<')
if index == -1: if index == -1:
return (None, "Error while getting repo address") return (None, _("Error while getting repo address"))
repo = repo[:index] repo = repo[:index]
repo = repo.split('"')[0] repo = repo.split('"')[0]
return (repotype, repo) return (repotype, repo)
@ -71,12 +72,12 @@ def getrepofrompage(url):
repo = page[index + 10:] repo = page[index + 10:]
index = repo.find('<') index = repo.find('<')
if index == -1: if index == -1:
return (None, "Error while getting repo address") return (None, _("Error while getting repo address"))
repo = repo[:index] repo = repo[:index]
repo = repo.split('"')[0] repo = repo.split('"')[0]
return (repotype, repo) return (repotype, repo)
return (None, "No information found." + page) return (None, _("No information found.") + page)
config = None config = None
@ -87,7 +88,7 @@ def get_metadata_from_url(app, url):
tmp_dir = 'tmp' tmp_dir = 'tmp'
if not os.path.isdir(tmp_dir): if not os.path.isdir(tmp_dir):
logging.info("Creating temporary directory") logging.info(_("Creating temporary directory"))
os.makedirs(tmp_dir) os.makedirs(tmp_dir)
# Figure out what kind of project it is... # Figure out what kind of project it is...
@ -190,15 +191,15 @@ def main():
parser = ArgumentParser() parser = ArgumentParser()
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("-u", "--url", default=None, 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, 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, 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, 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, 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) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -214,7 +215,7 @@ def main():
local_metadata_files = common.get_local_metadata_files() local_metadata_files = common.get_local_metadata_files()
if 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'): if options.url is None and os.path.isdir('.git'):
app.AutoName = os.path.basename(os.getcwd()) app.AutoName = os.path.basename(os.getcwd())
@ -252,11 +253,11 @@ def main():
versionName, versionCode, package = common.parse_androidmanifests(paths, app) versionName, versionCode, package = common.parse_androidmanifests(paths, app)
if not package: if not package:
raise FDroidException("Couldn't find package ID") raise FDroidException(_("Couldn't find package ID"))
if not versionName: if not versionName:
logging.warn("Couldn't find latest version name") logging.warn(_("Couldn't find latest version name"))
if not versionCode: if not versionCode:
logging.warn("Couldn't find latest version code") logging.warn(_("Couldn't find latest version code"))
else: else:
spec = os.path.join(root_dir, 'buildozer.spec') spec = os.path.join(root_dir, 'buildozer.spec')
if os.path.exists(spec): if os.path.exists(spec):
@ -268,7 +269,7 @@ def main():
versionName = bconfig.get('app', 'version') versionName = bconfig.get('app', 'version')
versionCode = None versionCode = None
else: 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... # Make sure it's actually new...
if package in apps: if package in apps:

View File

@ -34,7 +34,11 @@ from binascii import hexlify, unhexlify
from datetime import datetime from datetime import datetime
from xml.dom.minidom import Document 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.common import FDroidPopen, FDroidPopenBytes
from fdroidserver.exception import FDroidException, VerificationException, MetaDataException from fdroidserver.exception import FDroidException, VerificationException, MetaDataException
@ -62,16 +66,16 @@ def make(apps, sortedids, apks, repodir, archive):
if not common.options.nosign: if not common.options.nosign:
if 'repo_keyalias' not in common.config: if 'repo_keyalias' not in common.config:
nosigningkey = True 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: if 'keystore' not in common.config:
nosigningkey = True nosigningkey = True
logging.critical("'keystore' not found in config.py!") logging.critical(_("'keystore' not found in config.py!"))
if 'keystorepass' not in common.config: if 'keystorepass' not in common.config:
nosigningkey = True nosigningkey = True
logging.critical("'keystorepass' not found in config.py!") logging.critical(_("'keystorepass' not found in config.py!"))
if 'keypass' not in common.config: if 'keypass' not in common.config:
nosigningkey = True 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']): if not os.path.exists(common.config['keystore']):
nosigningkey = True nosigningkey = True
logging.critical("'" + common.config['keystore'] + "' does not exist!") 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', [])): for mirror in sorted(common.config.get('mirrors', [])):
base = os.path.basename(urllib.parse.urlparse(mirror).path.rstrip('/')) base = os.path.basename(urllib.parse.urlparse(mirror).path.rstrip('/'))
if common.config.get('nonstandardwebroot') is not True and base != 'fdroid': 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 mirrorcheckfailed = True
# must end with / or urljoin strips a whole path segment # must end with / or urljoin strips a whole path segment
if mirror.endswith('/'): if mirror.endswith('/'):
@ -115,7 +119,7 @@ def make(apps, sortedids, apks, repodir, archive):
for url in get_mirror_service_urls(mirror): for url in get_mirror_service_urls(mirror):
mirrors.append(url + '/' + repodir) mirrors.append(url + '/' + repodir)
if mirrorcheckfailed: if mirrorcheckfailed:
raise FDroidException("Malformed repository mirrors.") raise FDroidException(_("Malformed repository mirrors."))
if mirrors: if mirrors:
repodict['mirrors'] = 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]): elif all(isinstance(item, str) for item in common.config[key]):
packageNames = common.config[key] packageNames = common.config[key]
else: else:
raise TypeError('only accepts strings, lists, and tuples') raise TypeError(_('only accepts strings, lists, and tuples'))
requestsdict[command] = packageNames requestsdict[command] = packageNames
make_v0(appsWithPackages, apks, repodir, repodict, requestsdict) make_v0(appsWithPackages, apks, repodir, repodict, requestsdict)
@ -199,7 +203,7 @@ def make_v1(apps, packages, repodir, repodict, requestsdict):
for package in packages: for package in packages:
packageName = package['packageName'] packageName = package['packageName']
if packageName not in apps: if packageName not in apps:
logging.info('Ignoring package without metadata: ' + package['apkName']) logging.info(_('Ignoring package without metadata: ') + package['apkName'])
continue continue
if packageName in output_packages: if packageName in output_packages:
packagelist = output_packages[packageName] packagelist = output_packages[packageName]
@ -224,7 +228,7 @@ def make_v1(apps, packages, repodir, repodict, requestsdict):
json.dump(output, fp, default=_index_encoder_default) json.dump(output, fp, default=_index_encoder_default)
if common.options.nosign: 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: else:
signindex.config = common.config signindex.config = common.config
signindex.sign_index_v1(repodir, json_name) 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 'repo_keyalias' in common.config:
if common.options.nosign: if common.options.nosign:
logging.info("Creating unsigned index in preparation for signing") logging.info(_("Creating unsigned index in preparation for signing"))
else: 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) logging.info("%s" % repo_pubkey_fingerprint)
# Create a jar of the index... # Create a jar of the index...
@ -613,7 +617,7 @@ def download_repo_index(url_str, etag=None, verify_fingerprint=True):
if verify_fingerprint: if verify_fingerprint:
query = urllib.parse.parse_qs(url.query) query = urllib.parse.parse_qs(url.query)
if 'fingerprint' not in query: if 'fingerprint' not in query:
raise VerificationException("No fingerprint in URL.") raise VerificationException(_("No fingerprint in URL."))
fingerprint = query['fingerprint'][0] fingerprint = query['fingerprint'][0]
url = urllib.parse.SplitResult(url.scheme, url.netloc, url.path + '/index-v1.jar', '', '') 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 # compare the fingerprint if verify_fingerprint is True
if verify_fingerprint and fingerprint.upper() != public_key_fingerprint: 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 # load repository index from JSON
index = json.loads(jar.read('index-v1.json').decode("utf-8")) 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 :raises: VerificationException() if the JAR's signature could not be verified
""" """
if not common.verify_apk_signature(file, jar=True): 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): def get_public_key_from_jar(jar):
@ -670,9 +674,9 @@ def get_public_key_from_jar(jar):
# extract certificate from jar # extract certificate from jar
certs = [n for n in jar.namelist() if common.CERT_PATH_REGEX.match(n)] certs = [n for n in jar.namelist() if common.CERT_PATH_REGEX.match(n)]
if len(certs) < 1: if len(certs) < 1:
raise VerificationException("Found no signing certificates for repository.") raise VerificationException(_("Found no signing certificates for repository."))
if len(certs) > 1: 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 # extract public key from certificate
public_key = common.get_certificate(jar.read(certs[0])) public_key = common.get_certificate(jar.read(certs[0]))

View File

@ -27,6 +27,7 @@ import sys
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from .exception import FDroidException from .exception import FDroidException
@ -53,15 +54,15 @@ def main():
parser = ArgumentParser() parser = ArgumentParser()
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("-d", "--distinguished-name", default=None, 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, 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, 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, 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, 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() options = parser.parse_args()
# find root install prefix # find root install prefix
@ -106,8 +107,7 @@ def main():
'AppData', 'Local', 'Android', 'android-sdk') 'AppData', 'Local', 'Android', 'android-sdk')
while not options.no_prompt: while not options.no_prompt:
try: try:
s = input('Enter the path to the Android SDK (' s = input(_('Enter the path to the Android SDK (%s) here:\n> ') % default_sdk_path)
+ default_sdk_path + ') here:\n> ')
except KeyboardInterrupt: except KeyboardInterrupt:
print('') print('')
sys.exit(1) sys.exit(1)
@ -231,21 +231,20 @@ def main():
common.write_to_config(test_config, 'keydname', c['keydname']) common.write_to_config(test_config, 'keydname', c['keydname'])
common.genkeystore(c) common.genkeystore(c)
logging.info('Built repo based in "' + fdroiddir + '"') msg = '\n'
logging.info('with this config:') msg += _('Built repo based in "%s" with this config:') % fdroiddir
logging.info(' Android SDK:\t\t\t' + config['sdk_path']) msg += '\n\n Android SDK:\t\t\t' + config['sdk_path']
if aapt: if aapt:
logging.info(' Android SDK Build Tools:\t' + os.path.dirname(aapt)) msg += '\n Android SDK Build Tools:\t' + os.path.dirname(aapt)
logging.info(' Android NDK r12b (optional):\t$ANDROID_NDK') msg += '\n Android NDK r12b (optional):\t$ANDROID_NDK'
logging.info(' Keystore for signing key:\t' + keystore) msg += '\n ' + _('Keystore for signing key:\t') + keystore
if repo_keyalias is not None: if repo_keyalias is not None:
logging.info(' Alias for key in store:\t' + repo_keyalias) msg += '\n Alias for key in store:\t' + repo_keyalias
logging.info('\nTo complete the setup, add your APKs to "' + msg += '\n\n' + '''To complete the setup, add your APKs to "%s"
os.path.join(fdroiddir, 'repo') + '"' + '''
then run "fdroid update -c; fdroid update". You might also want to edit 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 "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). 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 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)

View File

@ -23,6 +23,7 @@ import glob
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from .common import SdkToolsPopen from .common import SdkToolsPopen
from .exception import FDroidException from .exception import FDroidException
@ -49,19 +50,19 @@ def main():
# Parse command line... # Parse command line...
parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]")
common.setup_global_opts(parser) 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, 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() options = parser.parse_args()
if not options.appid and not options.all: 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) config = common.read_config(options)
output_dir = 'repo' output_dir = 'repo'
if not os.path.isdir(output_dir): 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) sys.exit(0)
if options.appid: if options.appid:
@ -84,7 +85,7 @@ def main():
for appid, apk in apks.items(): for appid, apk in apks.items():
if not apk: if not apk:
raise FDroidException("No signed apk available for %s" % appid) raise FDroidException(_("No signed apk available for %s") % appid)
else: else:
@ -95,10 +96,10 @@ def main():
# Get device list each time to avoid device not found errors # Get device list each time to avoid device not found errors
devs = devices() devs = devices()
if not devs: if not devs:
raise FDroidException("No attached devices found") raise FDroidException(_("No attached devices found"))
logging.info("Installing %s..." % apk) logging.info(_("Installing %s...") % apk)
for dev in devs: 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]) p = SdkToolsPopen(['adb', "-s", dev, "install", apk])
fail = "" fail = ""
for line in p.output.splitlines(): for line in p.output.splitlines():
@ -108,12 +109,12 @@ def main():
continue continue
if fail == "INSTALL_FAILED_ALREADY_EXISTS": 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: else:
raise FDroidException("Failed to install %s on %s: %s" % ( raise FDroidException(_("Failed to install %s on %s: %s") % (
apk, dev, fail)) apk, dev, fail))
logging.info("\nFinished") logging.info('\n' + _('Finished'))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -22,6 +22,7 @@ import os
import re import re
import sys import sys
from . import _
from . import common from . import common
from . import metadata from . import metadata
from . import rewritemeta from . import rewritemeta
@ -47,7 +48,7 @@ https_enforcings = [
def forbid_shortener(domain): def forbid_shortener(domain):
return (re.compile(r'https?://[^/]*' + re.escape(domain) + r'/.*'), return (re.compile(r'https?://[^/]*' + re.escape(domain) + r'/.*'),
"URL shorteners should not be used") _("URL shorteners should not be used"))
http_url_shorteners = [ http_url_shorteners = [
@ -62,9 +63,9 @@ http_url_shorteners = [
http_checks = https_enforcings + http_url_shorteners + [ http_checks = https_enforcings + http_url_shorteners + [
(re.compile(r'.*github\.com/[^/]+/[^/]+\.git'), (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'), (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 = { regex_checks = {
@ -73,44 +74,44 @@ regex_checks = {
'Repo': https_enforcings, 'Repo': https_enforcings,
'IssueTracker': http_checks + [ 'IssueTracker': http_checks + [
(re.compile(r'.*github\.com/[^/]+/[^/]+/*$'), (re.compile(r'.*github\.com/[^/]+/[^/]+/*$'),
"/issues is missing"), _("/issues is missing")),
(re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'), (re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'),
"/issues is missing"), _("/issues is missing")),
], ],
'Donate': http_checks + [ 'Donate': http_checks + [
(re.compile(r'.*flattr\.com'), (re.compile(r'.*flattr\.com'),
"Flattr donation methods belong in the FlattrID flag"), _("Flattr donation methods belong in the FlattrID flag")),
], ],
'Changelog': http_checks, 'Changelog': http_checks,
'Author Name': [ 'Author Name': [
(re.compile(r'^\s'), (re.compile(r'^\s'),
"Unnecessary leading space"), _("Unnecessary leading space")),
(re.compile(r'.*\s$'), (re.compile(r'.*\s$'),
"Unnecessary trailing space"), _("Unnecessary trailing space")),
], ],
'Summary': [ 'Summary': [
(re.compile(r'.*\b(free software|open source)\b.*', re.IGNORECASE), (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), (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][.!?]( |$)'), (re.compile(r'.*[a-z0-9][.!?]( |$)'),
"Punctuation should be avoided"), _("Punctuation should be avoided")),
(re.compile(r'^\s'), (re.compile(r'^\s'),
"Unnecessary leading space"), _("Unnecessary leading space")),
(re.compile(r'.*\s$'), (re.compile(r'.*\s$'),
"Unnecessary trailing space"), _("Unnecessary trailing space")),
], ],
'Description': [ 'Description': [
(re.compile(r'\s*[*#][^ .]'), (re.compile(r'\s*[*#][^ .]'),
"Invalid bulleted list"), _("Invalid bulleted list")),
(re.compile(r'^\s'), (re.compile(r'^\s'),
"Unnecessary leading space"), _("Unnecessary leading space")),
(re.compile(r'.*\s$'), (re.compile(r'.*\s$'),
"Unnecessary trailing space"), _("Unnecessary trailing space")),
(re.compile(r'.*([^[]|^)\[[^:[\]]+( |\]|$)'), (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?://[^ ]+'), (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 lastbuild.versionCode == app.CurrentVersionCode
and not lastbuild.forcevercode and not lastbuild.forcevercode
and any(s in lastbuild.commit for s in '.,_-/')): 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) lastbuild.commit, app.UpdateCheckMode)
@ -163,11 +164,11 @@ def check_char_limits(app):
limits = config['char_limits'] limits = config['char_limits']
if len(app.Summary) > limits['summary']: 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']) len(app.Summary), limits['summary'])
if len(app.Description) > limits['description']: 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']) len(app.Description), limits['description'])
@ -185,12 +186,12 @@ def check_old_links(app):
for f in ['WebSite', 'SourceCode', 'IssueTracker', 'Changelog']: for f in ['WebSite', 'SourceCode', 'IssueTracker', 'Changelog']:
v = app.get(f) v = app.get(f)
if any(s in v for s in old_sites): 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): def check_useless_fields(app):
if app.UpdateCheckName == app.id: 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.*)') filling_ucms = re.compile(r'^(Tags.*|RepoManifest.*)')
@ -199,12 +200,12 @@ filling_ucms = re.compile(r'^(Tags.*|RepoManifest.*)')
def check_checkupdates_ran(app): def check_checkupdates_ran(app):
if filling_ucms.match(app.UpdateCheckMode): if filling_ucms.match(app.UpdateCheckMode):
if not app.AutoName and not app.CurrentVersion and app.CurrentVersionCode == '0': 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): def check_empty_fields(app):
if not app.Categories: if not app.Categories:
yield "Categories are not set" yield _("Categories are not set")
all_categories = set([ all_categories = set([
@ -231,12 +232,12 @@ all_categories = set([
def check_categories(app): def check_categories(app):
for categ in app.Categories: for categ in app.Categories:
if categ not in all_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): def check_duplicates(app):
if app.Name and app.Name == app.AutoName: 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() links_seen = set()
for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']: for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']:
@ -245,25 +246,25 @@ def check_duplicates(app):
continue continue
v = v.lower() v = v.lower()
if v in links_seen: if v in links_seen:
yield "Duplicate link in '%s': %s" % (f, v) yield _("Duplicate link in '%s': %s") % (f, v)
else: else:
links_seen.add(v) links_seen.add(v)
name = app.Name or app.AutoName name = app.Name or app.AutoName
if app.Summary and name: if app.Summary and name:
if app.Summary.lower() == name.lower(): 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 and app.Description and len(app.Description) == 1:
if app.Summary.lower() == app.Description[0].lower(): 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() seenlines = set()
for l in app.Description.splitlines(): for l in app.Description.splitlines():
if len(l) < 1: if len(l) < 1:
continue continue
if l in seenlines: if l in seenlines:
yield "Description has a duplicate line" yield _("Description has a duplicate line")
seenlines.add(l) seenlines.add(l)
@ -276,7 +277,7 @@ def check_mediawiki_links(app):
url = um.group(1) url = um.group(1)
for m, r in http_checks: for m, r in http_checks:
if m.match(url): 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): def check_bulleted_lists(app):
@ -291,7 +292,7 @@ def check_bulleted_lists(app):
if l[0] == lchar and l[1] == ' ': if l[0] == lchar and l[1] == ' ':
lcount += 1 lcount += 1
if lcount > 2 and lchar not in validchars: 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 break
else: else:
lchar = l[0] lchar = l[0]
@ -304,18 +305,18 @@ def check_builds(app):
for build in app.builds: for build in app.builds:
if build.disable: if build.disable:
if build.disable.startswith('Generated by import.py'): 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 continue
for s in ['master', 'origin', 'HEAD', 'default', 'trunk']: for s in ['master', 'origin', 'HEAD', 'default', 'trunk']:
if build.commit and build.commit.startswith(s): 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: for srclib in build.srclibs:
ref = srclib.split('@')[1].split('/')[0] ref = srclib.split('@')[1].split('/')[0]
if ref.startswith(s): 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(): for key in build.keys():
if key not in supported_flags: 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): def check_files_dir(app):
@ -326,7 +327,7 @@ def check_files_dir(app):
for name in os.listdir(dir_path): for name in os.listdir(dir_path):
path = os.path.join(dir_path, name) path = os.path.join(dir_path, name)
if not (os.path.isfile(path) or name == 'signatures' or locale_pattern.match(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 continue
files.add(name) files.add(name)
@ -334,45 +335,45 @@ def check_files_dir(app):
for build in app.builds: for build in app.builds:
for fname in build.patch: for fname in build.patch:
if fname not in files: 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: else:
used.add(fname) used.add(fname)
for name in files.difference(used): for name in files.difference(used):
if locale_pattern.match(name): if locale_pattern.match(name):
continue 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): def check_format(app):
if options.format and not rewritemeta.proper_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): def check_license_tag(app):
'''Ensure all license tags are in https://spdx.org/license-list''' '''Ensure all license tags are in https://spdx.org/license-list'''
if app.License.rstrip('+') not in SPDX: 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) % (app.License)
def check_extlib_dir(apps): def check_extlib_dir(apps):
dir_path = os.path.join('build', 'extlib') dir_path = os.path.join('build', 'extlib')
files = set() unused_extlib_files = set()
for root, dirs, names in os.walk(dir_path): for root, dirs, files in os.walk(dir_path):
for name in names: for name in files:
files.add(os.path.join(root, name)[len(dir_path) + 1:]) unused_extlib_files.add(os.path.join(root, name)[len(dir_path) + 1:])
used = set() used = set()
for app in apps: for app in apps:
for build in app.builds: for build in app.builds:
for path in build.extlibs: for path in build.extlibs:
if path not in files: if path not in unused_extlib_files:
yield "%s: Unknown extlib %s in build '%s'" % (app.id, path, build.versionName) yield _("%s: Unknown extlib %s in build '%s'") % (app.id, path, build.versionName)
else: else:
used.add(path) 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 [ if any(path.endswith(s) for s in [
'.gitignore', '.gitignore',
'source.txt', 'origin.txt', 'md5.txt', 'source.txt', 'origin.txt', 'md5.txt',
@ -381,7 +382,7 @@ def check_extlib_dir(apps):
'NOTICE', 'NOTICE.txt', 'NOTICE', 'NOTICE.txt',
]): ]):
continue 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=""): def check_for_unsupported_metadata_files(basedir=""):
@ -397,7 +398,7 @@ def check_for_unsupported_metadata_files(basedir=""):
for t in formats: for t in formats:
exists = exists or os.path.exists(f + '.' + t) exists = exists or os.path.exists(f + '.' + t)
if not exists: if not exists:
print('"' + f + '/" has no matching metadata file!') print(_('"%s/" has no matching metadata file!') % f)
return_value = True return_value = True
elif not os.path.splitext(f)[1][1:] in formats: elif not os.path.splitext(f)[1][1:] in formats:
print('"' + f.replace(basedir, '') print('"' + f.replace(basedir, '')
@ -415,8 +416,8 @@ def main():
parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]") parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]")
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("-f", "--format", action="store_true", default=False, parser.add_argument("-f", "--format", action="store_true", default=False,
help="Also warn about formatting issues, like rewritemeta -l") help=_("Also warn about formatting issues, like rewritemeta -l"))
parser.add_argument("appid", nargs='*', help="app-id in the form APPID") parser.add_argument("appid", nargs='*', help=_("applicationId in the form APPID"))
metadata.add_metadata_arguments(parser) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W

View File

@ -35,6 +35,7 @@ except ImportError:
YamlLoader = Loader YamlLoader = Loader
import fdroidserver.common import fdroidserver.common
from fdroidserver import _
from fdroidserver.exception import MetaDataException, FDroidException from fdroidserver.exception import MetaDataException, FDroidException
srclibs = None srclibs = None
@ -1519,4 +1520,4 @@ def write_metadata(metadatapath, app):
def add_metadata_arguments(parser): def add_metadata_arguments(parser):
'''add common command line flags related to metadata processing''' '''add common command line flags related to metadata processing'''
parser.add_argument("-W", default='error', parser.add_argument("-W", default='error',
help="force errors to be warnings, or ignore") help=_("force errors to be warnings, or ignore"))

View File

@ -26,6 +26,7 @@ import hashlib
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from . import metadata from . import metadata
from .common import FDroidPopen, SdkToolsPopen from .common import FDroidPopen, SdkToolsPopen
@ -43,7 +44,7 @@ def main():
parser = ArgumentParser(usage="%(prog)s [options] " parser = ArgumentParser(usage="%(prog)s [options] "
"[APPID[:VERCODE] [APPID[:VERCODE] ...]]") "[APPID[:VERCODE] [APPID[:VERCODE] ...]]")
common.setup_global_opts(parser) 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) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -51,27 +52,27 @@ def main():
config = common.read_config(options) config = common.read_config(options)
if not ('jarsigner' in config and 'keytool' in config): 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) sys.exit(1)
log_dir = 'logs' log_dir = 'logs'
if not os.path.isdir(log_dir): if not os.path.isdir(log_dir):
logging.info("Creating log directory") logging.info(_("Creating log directory"))
os.makedirs(log_dir) os.makedirs(log_dir)
tmp_dir = 'tmp' tmp_dir = 'tmp'
if not os.path.isdir(tmp_dir): if not os.path.isdir(tmp_dir):
logging.info("Creating temporary directory") logging.info(_("Creating temporary directory"))
os.makedirs(tmp_dir) os.makedirs(tmp_dir)
output_dir = 'repo' output_dir = 'repo'
if not os.path.isdir(output_dir): if not os.path.isdir(output_dir):
logging.info("Creating output directory") logging.info(_("Creating output directory"))
os.makedirs(output_dir) os.makedirs(output_dir)
unsigned_dir = 'unsigned' unsigned_dir = 'unsigned'
if not os.path.isdir(unsigned_dir): 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) sys.exit(1)
if not os.path.exists(config['keystore']): if not os.path.exists(config['keystore']):
@ -95,7 +96,7 @@ def main():
m.update(appid.encode('utf-8')) m.update(appid.encode('utf-8'))
keyalias = m.hexdigest()[:8] keyalias = m.hexdigest()[:8]
if keyalias in allaliases: 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) sys.exit(1)
allaliases.append(keyalias) allaliases.append(keyalias)
logging.info("{0} apps, {0} key aliases".format(len(allapps), logging.info("{0} apps, {0} key aliases".format(len(allapps),
@ -208,13 +209,13 @@ def main():
'SHA1withRSA', '-digestalg', 'SHA1', 'SHA1withRSA', '-digestalg', 'SHA1',
apkfile, keyalias], envs=env_vars) apkfile, keyalias], envs=env_vars)
if p.returncode != 0: if p.returncode != 0:
raise BuildException("Failed to sign application") raise BuildException(_("Failed to sign application"))
# Zipalign it... # Zipalign it...
p = SdkToolsPopen(['zipalign', '-v', '4', apkfile, p = SdkToolsPopen(['zipalign', '-v', '4', apkfile,
os.path.join(output_dir, apkfilename)]) os.path.join(output_dir, apkfilename)])
if p.returncode != 0: if p.returncode != 0:
raise BuildException("Failed to align application") raise BuildException(_("Failed to align application"))
os.remove(apkfile) os.remove(apkfile)
# Move the source tarball into the output directory... # Move the source tarball into the output directory...

View File

@ -22,6 +22,7 @@ import os
import logging import logging
import io import io
from . import _
from . import common from . import common
from . import metadata from . import metadata
@ -51,10 +52,10 @@ def main():
parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]") parser = ArgumentParser(usage="%(prog)s [options] [APPID [APPID ...]]")
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("-l", "--list", action="store_true", default=False, 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, parser.add_argument("-t", "--to", default=None,
help="Rewrite to a specific format: " + ', '.join(supported)) help=_("Rewrite to a specific format: ") + ', '.join(supported))
parser.add_argument("appid", nargs='*', help="app-id in the form APPID") parser.add_argument("appid", nargs='*', help=_("applicationId in the form APPID"))
metadata.add_metadata_arguments(parser) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -66,21 +67,21 @@ def main():
apps = common.read_app_args(options.appid, allapps, False) apps = common.read_app_args(options.appid, allapps, False)
if options.list and options.to is not None: 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: 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(): for appid, app in apps.items():
path = app.metadatapath path = app.metadatapath
base, ext = common.get_extension(path) base, ext = common.get_extension(path)
if not options.to and ext not in supported: 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 continue
elif options.to is not None: 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: else:
logging.info("rewriting '%s'" % (appid)) logging.info(_("rewriting '%s'") % (appid))
to_ext = ext to_ext = ext
if options.to is not None: if options.to is not None:
@ -107,7 +108,7 @@ def main():
if ext != to_ext: if ext != to_ext:
os.remove(path) os.remove(path)
logging.debug("Finished.") logging.debug(_("Finished"))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -22,6 +22,7 @@ import traceback
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from . import metadata from . import metadata
from .exception import BuildException, VCSException 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) return any(command.match(line) for command in gradle_compile_commands)
# Iterate through all files in the source code # 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 # It's topdown, so checking the basename is enough
for ignoredir in ('.hg', '.git', '.svn', '.bzr'): for ignoredir in ('.hg', '.git', '.svn', '.bzr'):
if ignoredir in dirnames: if ignoredir in dirs:
dirnames.remove(ignoredir) dirs.remove(ignoredir)
for curfile in filenames: for curfile in files:
if curfile in ['.DS_Store']: if curfile in ['.DS_Store']:
continue continue
# Path (relative) to the file # Path (relative) to the file
filepath = os.path.join(dirpath, curfile) filepath = os.path.join(root, curfile)
if os.path.islink(filepath): if os.path.islink(filepath):
continue continue
@ -256,7 +257,7 @@ def main():
# Parse command line... # Parse command line...
parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]")
common.setup_global_opts(parser) 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) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -329,8 +330,8 @@ def main():
appid, traceback.format_exc())) appid, traceback.format_exc()))
probcount += 1 probcount += 1
logging.info("Finished:") logging.info(_("Finished"))
print("%d problems found" % probcount) print(_("%d problems found") % probcount)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -29,6 +29,7 @@ from argparse import ArgumentParser
import logging import logging
import shutil import shutil
from . import _
from . import common from . import common
from .exception import FDroidException from .exception import FDroidException
@ -154,7 +155,7 @@ def update_awsbucket_libcloud(repo_section):
if obj.name.startswith(upload_dir + '/'): if obj.name.startswith(upload_dir + '/'):
objs[obj.name] = obj 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: for name in files:
upload = False upload = False
file_to_upload = os.path.join(root, name) 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='.'): def _get_size(start_path='.'):
'''get size of all files in a dir https://stackoverflow.com/a/1392549''' '''get size of all files in a dir https://stackoverflow.com/a/1392549'''
total_size = 0 total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path): for root, dirs, files in os.walk(start_path):
for f in filenames: for f in files:
fp = os.path.join(dirpath, f) fp = os.path.join(root, f)
total_size += os.path.getsize(fp) total_size += os.path.getsize(fp)
return total_size return total_size
@ -577,19 +578,19 @@ def main():
# Parse command line... # Parse command line...
parser = ArgumentParser() parser = ArgumentParser()
common.setup_global_opts(parser) 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, 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, 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, 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() options = parser.parse_args()
config = common.read_config(options) config = common.read_config(options)
if options.command != 'init' and options.command != 'update': 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) sys.exit(1)
if config.get('nonstandardwebroot') is True: if config.get('nonstandardwebroot') is True:
@ -605,7 +606,7 @@ def main():
elif len(s) == 2: elif len(s) == 2:
host, fdroiddir = s host, fdroiddir = s
else: else:
logging.error('Malformed serverwebroot line: ' + serverwebroot) logging.error(_('Malformed serverwebroot line:') + ' ' + serverwebroot)
sys.exit(1) sys.exit(1)
repobase = os.path.basename(fdroiddir) repobase = os.path.basename(fdroiddir)
if standardwebroot and repobase != 'fdroid': if standardwebroot and repobase != 'fdroid':
@ -624,7 +625,7 @@ def main():
if local_copy_dir is not None: if local_copy_dir is not None:
fdroiddir = local_copy_dir.rstrip('/') fdroiddir = local_copy_dir.rstrip('/')
if os.path.exists(fdroiddir) and not os.path.isdir(fdroiddir): 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) sys.exit(1)
if not os.path.exists(os.path.dirname(fdroiddir)): if not os.path.exists(os.path.dirname(fdroiddir)):
logging.error('The root dir for local_copy_dir "' logging.error('The root dir for local_copy_dir "'
@ -632,7 +633,7 @@ def main():
+ '" does not exist!') + '" does not exist!')
sys.exit(1) sys.exit(1)
if not os.path.isabs(fdroiddir): 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) sys.exit(1)
repobase = os.path.basename(fdroiddir) repobase = os.path.basename(fdroiddir)
if standardwebroot and repobase != 'fdroid': if standardwebroot and repobase != 'fdroid':
@ -652,8 +653,8 @@ def main():
and not config.get('binary_transparency_remote') \ and not config.get('binary_transparency_remote') \
and not config.get('virustotal_apikey') \ and not config.get('virustotal_apikey') \
and local_copy_dir is None: and local_copy_dir is None:
logging.warn('No option set! Edit your config.py to set at least one among:\n' logging.warn(_('No option set! Edit your config.py to set at least one of these:')
+ 'serverwebroot, servergitmirrors, local_copy_dir, awsbucket, virustotal_apikey, androidobservatory, or binary_transparency_remote') + '\nserverwebroot, servergitmirrors, local_copy_dir, awsbucket, virustotal_apikey, androidobservatory, or binary_transparency_remote')
sys.exit(1) sys.exit(1)
repo_sections = ['repo'] repo_sections = ['repo']

View File

@ -22,6 +22,7 @@ import os
import sys import sys
import logging import logging
from . import _
from . import common from . import common
from . import net from . import net
from .exception import FDroidException from .exception import FDroidException
@ -52,7 +53,7 @@ def extract(config, options):
os.mkdir(tmp_dir) os.mkdir(tmp_dir)
if not options.APK or len(options.APK) <= 0: if not options.APK or len(options.APK) <= 0:
logging.critical('no APK supplied') logging.critical(_('no APK supplied'))
sys.exit(1) sys.exit(1)
# iterate over supplied APKs downlaod and extract them... # iterate over supplied APKs downlaod and extract them...
@ -61,21 +62,21 @@ def extract(config, options):
try: try:
if os.path.isfile(apk): if os.path.isfile(apk):
sigdir = extract_signature(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): elif httpre.match(apk):
if apk.startswith('https') or options.no_check_https: if apk.startswith('https') or options.no_check_https:
try: try:
tmp_apk = os.path.join(tmp_dir, 'signed.apk') tmp_apk = os.path.join(tmp_dir, 'signed.apk')
net.download_file(apk, tmp_apk) net.download_file(apk, tmp_apk)
sigdir = extract_signature(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: finally:
if tmp_apk and os.path.exists(tmp_apk): if tmp_apk and os.path.exists(tmp_apk):
os.remove(tmp_apk) os.remove(tmp_apk)
else: 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: 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: if e.detail:
logging.debug(e.detail) logging.debug(e.detail)
@ -88,7 +89,7 @@ def main():
parser = ArgumentParser(usage="%(prog)s [options] APK [APK...]") parser = ArgumentParser(usage="%(prog)s [options] APK [APK...]")
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("APK", nargs='*', 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) parser.add_argument("--no-check-https", action="store_true", default=False)
options = parser.parse_args() options = parser.parse_args()

View File

@ -21,6 +21,7 @@ import zipfile
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from .exception import FDroidException from .exception import FDroidException
@ -87,7 +88,7 @@ def main():
if 'jarsigner' not in config: if 'jarsigner' not in config:
raise FDroidException( 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'] repodirs = ['repo']
if config['archive_older'] != 0: if config['archive_older'] != 0:
@ -114,7 +115,7 @@ def main():
signed += 1 signed += 1
if signed == 0: if signed == 0:
logging.info("Nothing to do") logging.info(_("Nothing to do"))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -30,6 +30,7 @@ import logging
import subprocess import subprocess
from collections import Counter from collections import Counter
from . import _
from . import common from . import common
from . import metadata from . import metadata
@ -61,12 +62,12 @@ def main():
parser = ArgumentParser() parser = ArgumentParser()
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("-d", "--download", action="store_true", default=False, 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, parser.add_argument("--recalc", action="store_true", default=False,
help="Recalculate aggregate stats - use when changes " help=_("Recalculate aggregate stats - use when changes "
"have been made that would invalidate old cached data.") "have been made that would invalidate old cached data."))
parser.add_argument("--nologs", action="store_true", default=False, 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) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -171,10 +172,10 @@ def main():
uri = match.group('uri') uri = match.group('uri')
if not uri.endswith('.apk'): if not uri.endswith('.apk'):
continue continue
_, apkname = os.path.split(uri) _ignored, apkname = os.path.split(uri)
app = knownapks.getapp(apkname) app = knownapks.getapp(apkname)
if app: if app:
appid, _ = app appid, _ignored = app
today['apps'][appid] += 1 today['apps'][appid] += 1
# Strip the '.apk' from apkname # Strip the '.apk' from apkname
appver = apkname[:-4] appver = apkname[:-4]
@ -298,7 +299,7 @@ def main():
for apk in unknownapks: for apk in unknownapks:
logging.info(apk) logging.info(apk)
logging.info("Finished.") logging.info(_("Finished"))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -37,6 +37,7 @@ from binascii import hexlify
from PIL import Image from PIL import Image
import logging import logging
from . import _
from . import common from . import common
from . import index from . import index
from . import metadata from . import metadata
@ -1697,34 +1698,34 @@ def main():
parser = ArgumentParser() parser = ArgumentParser()
common.setup_global_opts(parser) common.setup_global_opts(parser)
parser.add_argument("--create-key", action="store_true", default=False, 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, 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, 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, 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", 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, 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", 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") "is /etc/alternatives/editor")
parser.add_argument("-w", "--wiki", default=False, action="store_true", 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, 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, 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, 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, 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, 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, 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) metadata.add_metadata_arguments(parser)
options = parser.parse_args() options = parser.parse_args()
metadata.warnings_action = options.W metadata.warnings_action = options.W
@ -1899,7 +1900,7 @@ def main():
if options.wiki: if options.wiki:
update_wiki(apps, sortedids, apks + archapks) update_wiki(apps, sortedids, apks + archapks)
logging.info("Finished.") logging.info(_("Finished"))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -23,6 +23,7 @@ import requests
from argparse import ArgumentParser from argparse import ArgumentParser
import logging import logging
from . import _
from . import common from . import common
from . import net from . import net
from .exception import FDroidException from .exception import FDroidException
@ -38,19 +39,19 @@ def main():
# Parse command line... # Parse command line...
parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]") parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]")
common.setup_global_opts(parser) 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() options = parser.parse_args()
config = common.read_config(options) config = common.read_config(options)
tmp_dir = 'tmp' tmp_dir = 'tmp'
if not os.path.isdir(tmp_dir): if not os.path.isdir(tmp_dir):
logging.info("Creating temporary directory") logging.info(_("Creating temporary directory"))
os.makedirs(tmp_dir) os.makedirs(tmp_dir)
unsigned_dir = 'unsigned' unsigned_dir = 'unsigned'
if not os.path.isdir(unsigned_dir): 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) sys.exit(0)
verified = 0 verified = 0
@ -83,7 +84,7 @@ def main():
try: try:
net.download_file(url.replace('/repo', '/archive'), dldir=tmp_dir) net.download_file(url.replace('/repo', '/archive'), dldir=tmp_dir)
except requests.exceptions.HTTPError as e: 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( compare_result = common.verify_apks(
remoteapk, remoteapk,
@ -99,7 +100,7 @@ def main():
logging.info("...NOT verified - {0}".format(e)) logging.info("...NOT verified - {0}".format(e))
notverified += 1 notverified += 1
logging.info("Finished") logging.info(_("Finished"))
logging.info("{0} successfully verified".format(verified)) logging.info("{0} successfully verified".format(verified))
logging.info("{0} NOT verified".format(notverified)) logging.info("{0} NOT verified".format(notverified))

View File

@ -17,6 +17,7 @@ setup(name='fdroidserver',
author='The F-Droid Project', author='The F-Droid Project',
author_email='team@f-droid.org', author_email='team@f-droid.org',
url='https://f-droid.org', url='https://f-droid.org',
license='AGPL-3.0',
packages=['fdroidserver', 'fdroidserver.asynchronousfilereader'], packages=['fdroidserver', 'fdroidserver.asynchronousfilereader'],
scripts=['fdroid', 'fd-commit', 'makebuildserver'], scripts=['fdroid', 'fd-commit', 'makebuildserver'],
data_files=[ data_files=[
@ -29,6 +30,7 @@ setup(name='fdroidserver',
'examples/public-read-only-s3-bucket-policy.json', 'examples/public-read-only-s3-bucket-policy.json',
'examples/template.yml']), 'examples/template.yml']),
], ],
python_requires='>=3.4',
install_requires=[ install_requires=[
'clint', 'clint',
'GitPython', 'GitPython',

View File

@ -8,11 +8,13 @@ import logging
import optparse import optparse
import os import os
import shutil import shutil
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
import yaml import yaml
from binascii import unhexlify from binascii import unhexlify
from distutils.version import LooseVersion
localmodule = os.path.realpath( localmodule = os.path.realpath(
os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..')) 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.assertFalse(os.path.exists(os.path.join('archive', apkName)))
self.assertTrue(os.path.exists(os.path.join('repo', 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 # this test only works on systems with fully updated Java/jarsigner
# that has MD5 listed in jdk.jar.disabledAlgorithms in java.security # 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', skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
knownapks, knownapks,
allow_disabled_algorithms=False, allow_disabled_algorithms=False,