1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-11-09 00:40:11 +01:00
fdroidserver/fdroidserver/mirror.py
Hans-Christoph Steiner c54f9ea4ca mirror: make _run_wget() return to the dir it started in
This kind of function should not change the working environment, especially
since so much of the fdroidserver code assumes the current working directory
is the root of an fdroid repo.

It is used in fdroidserver/mirror.py with absolute paths always, so it
shouldn't change any existing use.  I found this issue by using it in a
plugin.
2021-12-07 10:24:27 +01:00

274 lines
8.7 KiB
Python

#!/usr/bin/env python3
import ipaddress
import logging
import os
import posixpath
import socket
import subprocess
import sys
from argparse import ArgumentParser
import urllib.parse
from . import _
from . import common
from . import index
from . import update
options = None
def _run_wget(path, urls):
if options.verbose:
verbose = '--verbose'
else:
verbose = '--no-verbose'
if not urls:
return
logging.debug(_('Running wget in {path}').format(path=path))
cwd = os.getcwd()
os.makedirs(path, exist_ok=True)
os.chdir(path)
urls_file = '.fdroid-mirror-wget-input-file'
with open(urls_file, 'w') as fp:
for url in urls:
fp.write(url.split('?')[0] + '\n') # wget puts query string in the filename
subprocess.call(
[
'wget',
verbose,
'--continue',
'--user-agent="fdroid mirror"',
'--input-file=' + urls_file,
]
)
os.remove(urls_file)
os.chdir(cwd) # leave the working env the way we found it
def main():
global options
parser = ArgumentParser()
common.setup_global_opts(parser)
parser.add_argument(
"url",
nargs='?',
help=_(
'Base URL to mirror, can include the index signing key '
+ 'using the query string: ?fingerprint='
),
)
parser.add_argument(
"--all",
action='store_true',
default=False,
help=_("Mirror the full repo and archive, all file types."),
)
parser.add_argument(
"--archive",
action='store_true',
default=False,
help=_("Also mirror the full archive section"),
)
parser.add_argument(
"--build-logs",
action='store_true',
default=False,
help=_("Include the build logs in the mirror"),
)
parser.add_argument(
"--pgp-signatures",
action='store_true',
default=False,
help=_("Include the PGP signature .asc files in the mirror"),
)
parser.add_argument(
"--src-tarballs",
action='store_true',
default=False,
help=_("Include the source tarballs in the mirror"),
)
parser.add_argument(
"--output-dir", default=None, help=_("The directory to write the mirror to")
)
options = parser.parse_args()
if options.all:
options.archive = True
options.build_logs = True
options.pgp_signatures = True
options.src_tarballs = True
if options.url is None:
logging.error(_('A URL is required as an argument!') + '\n')
parser.print_help()
sys.exit(1)
scheme, hostname, path, params, query, fragment = urllib.parse.urlparse(options.url)
fingerprint = urllib.parse.parse_qs(query).get('fingerprint')
def _append_to_url_path(*args):
"""Append the list of path components to URL, keeping the rest the same."""
newpath = posixpath.join(path, *args)
return urllib.parse.urlunparse(
(scheme, hostname, newpath, params, query, fragment)
)
if fingerprint:
config = common.read_config(options)
if not ('jarsigner' in config or 'apksigner' in config):
logging.error(
_('Java JDK not found! Install in standard location or set java_paths!')
)
sys.exit(1)
def _get_index(section, etag=None):
url = _append_to_url_path(section)
data, etag = index.download_repo_index(url, etag=etag)
return data, etag, _append_to_url_path(section, 'index-v1.jar')
else:
def _get_index(section, etag=None):
import io
import json
import zipfile
from . import net
url = _append_to_url_path(section, 'index-v1.jar')
content, etag = net.http_get(url)
with zipfile.ZipFile(io.BytesIO(content)) as zip:
jsoncontents = zip.open('index-v1.json').read()
data = json.loads(jsoncontents.decode('utf-8'))
return data, etag, None # no verified index file to return
ip = None
try:
ip = ipaddress.ip_address(hostname)
except ValueError:
pass
if hostname == 'f-droid.org' or (
ip is not None and hostname in socket.gethostbyname_ex('f-droid.org')[2]
):
print(
_(
'ERROR: this command should never be used to mirror f-droid.org!\n'
'A full mirror of f-droid.org requires more than 200GB.'
)
)
sys.exit(1)
path = path.rstrip('/')
if path.endswith('repo') or path.endswith('archive'):
logging.warning(
_('Do not include "{path}" in URL!').format(path=path.split('/')[-1])
)
elif not path.endswith('fdroid'):
logging.warning(
_('{url} does not end with "fdroid", check the URL path!').format(
url=options.url
)
)
icondirs = ['icons']
for density in update.screen_densities:
icondirs.append('icons-' + density)
if options.output_dir:
basedir = options.output_dir
else:
basedir = os.path.join(os.getcwd(), hostname, path.strip('/'))
os.makedirs(basedir, exist_ok=True)
if options.archive:
sections = ('repo', 'archive')
else:
sections = ('repo',)
for section in sections:
sectiondir = os.path.join(basedir, section)
urls = []
data, etag, index_url = _get_index(section)
if index_url:
urls.append(index_url)
os.makedirs(sectiondir, exist_ok=True)
os.chdir(sectiondir)
for icondir in icondirs:
os.makedirs(os.path.join(sectiondir, icondir), exist_ok=True)
for packageName, packageList in data['packages'].items():
for package in packageList:
to_fetch = []
keys = ['apkName']
if options.src_tarballs:
keys.append('srcname')
for k in keys:
if k in package:
to_fetch.append(package[k])
elif k == 'apkName':
logging.error(
_('{appid} is missing {name}').format(
appid=package['packageName'], name=k
)
)
for f in to_fetch:
if not os.path.exists(f) or (
f.endswith('.apk') and os.path.getsize(f) != package['size']
):
urls.append(_append_to_url_path(section, f))
if options.pgp_signatures:
urls.append(_append_to_url_path(section, f + '.asc'))
if options.build_logs and f.endswith('.apk'):
urls.append(
_append_to_url_path(section, f[:-4] + '.log.gz')
)
_run_wget(sectiondir, urls)
for app in data['apps']:
localized = app.get('localized')
if localized:
for locale, d in localized.items():
urls = []
components = (section, app['packageName'], locale)
for k in update.GRAPHIC_NAMES:
f = d.get(k)
if f:
filepath_tuple = components + (f,)
urls.append(_append_to_url_path(*filepath_tuple))
_run_wget(os.path.join(basedir, *components), urls)
for k in update.SCREENSHOT_DIRS:
urls = []
filelist = d.get(k)
if filelist:
components = (section, app['packageName'], locale, k)
for f in filelist:
filepath_tuple = components + (f,)
urls.append(_append_to_url_path(*filepath_tuple))
_run_wget(os.path.join(basedir, *components), urls)
urls = dict()
for app in data['apps']:
if 'icon' not in app:
logging.error(
_('no "icon" in {appid}').format(appid=app['packageName'])
)
continue
icon = app['icon']
for icondir in icondirs:
url = _append_to_url_path(section, icondir, icon)
if icondir not in urls:
urls[icondir] = []
urls[icondir].append(url)
for icondir in icondirs:
_run_wget(os.path.join(basedir, section, icondir), urls[icondir])
if __name__ == "__main__":
main()