10 from argparse import ArgumentParser
21 def _run_wget(path, urls):
25 verbose = '--no-verbose'
28 urls_file = '.fdroid-mirror-wget-input-file'
29 with open(urls_file, 'w') as fp:
31 fp.write(url.split('?')[0] + '\n') # wget puts query string in the filename
32 subprocess.call(['wget', verbose, '--continue', '--user-agent="fdroid mirror"',
33 '--input-file=' + urls_file])
40 parser = ArgumentParser(usage=_("%(prog)s [options] url"))
41 common.setup_global_opts(parser)
42 parser.add_argument("url", nargs='?',
43 help=_('Base URL to mirror, can include the index signing key '
44 + 'using the query string: ?fingerprint='))
45 parser.add_argument("--archive", action='store_true', default=False,
46 help=_("Also mirror the full archive section"))
47 parser.add_argument("--output-dir", default=None,
48 help=_("The directory to write the mirror to"))
49 options = parser.parse_args()
51 if options.url is None:
52 logging.error(_('A URL is required as an argument!') + '\n')
56 scheme, hostname, path, params, query, fragment = urllib.parse.urlparse(options.url)
57 fingerprint = urllib.parse.parse_qs(query).get('fingerprint')
59 def _append_to_url_path(*args):
60 '''Append the list of path components to URL, keeping the rest the same'''
61 newpath = posixpath.join(path, *args)
62 return urllib.parse.urlunparse((scheme, hostname, newpath, params, query, fragment))
65 config = common.read_config(options)
66 if not ('jarsigner' in config or 'apksigner' in config):
67 logging.error(_('Java JDK not found! Install in standard location or set java_paths!'))
70 def _get_index(section, etag=None):
71 url = _append_to_url_path(section)
72 return index.download_repo_index(url, etag=etag)
74 def _get_index(section, etag=None):
79 url = _append_to_url_path(section, 'index-v1.jar')
80 content, etag = net.http_get(url)
81 with zipfile.ZipFile(io.BytesIO(content)) as zip:
82 jsoncontents = zip.open('index-v1.json').read()
83 data = json.loads(jsoncontents.decode('utf-8'))
88 ip = ipaddress.ip_address(hostname)
91 if hostname == 'f-droid.org' \
92 or (ip is not None and hostname in socket.gethostbyname_ex('f-droid.org')[2]):
93 print(_('ERROR: this command should never be used to mirror f-droid.org!\n'
94 'A full mirror of f-droid.org requires more than 200GB.'))
97 path = path.rstrip('/')
98 if path.endswith('repo') or path.endswith('archive'):
99 logging.warning(_('Do not include "{path}" in URL!')
100 .format(path=path.split('/')[-1]))
101 elif not path.endswith('fdroid'):
102 logging.warning(_('{url} does not end with "fdroid", check the URL path!')
103 .format(url=options.url))
105 icondirs = ['icons', ]
106 for density in update.screen_densities:
107 icondirs.append('icons-' + density)
109 if options.output_dir:
110 basedir = options.output_dir
112 basedir = os.path.join(os.getcwd(), hostname, path.strip('/'))
113 os.makedirs(basedir, exist_ok=True)
116 sections = ('repo', 'archive')
118 sections = ('repo', )
120 for section in sections:
121 sectiondir = os.path.join(basedir, section)
123 data, etag = _get_index(section)
125 os.makedirs(sectiondir, exist_ok=True)
127 for icondir in icondirs:
128 os.makedirs(os.path.join(sectiondir, icondir), exist_ok=True)
131 for packageName, packageList in data['packages'].items():
132 for package in packageList:
134 for k in ('apkName', 'srcname'):
136 to_fetch.append(package[k])
138 logging.error(_('{appid} is missing {name}')
139 .format(appid=package['packageName'], name=k))
141 if not os.path.exists(f) \
142 or (f.endswith('.apk') and os.path.getsize(f) != package['size']):
143 urls.append(_append_to_url_path(section, f))
144 urls.append(_append_to_url_path(section, f + '.asc'))
146 for app in data['apps']:
147 localized = app.get('localized')
149 for locale, d in localized.items():
150 for k in update.GRAPHIC_NAMES:
153 urls.append(_append_to_url_path(section, app['packageName'], locale, f))
154 for k in update.SCREENSHOT_DIRS:
158 urls.append(_append_to_url_path(section, app['packageName'], locale, k, f))
160 _run_wget(sectiondir, urls)
163 for app in data['apps']:
164 if 'icon' not in app:
165 logging.error(_('no "icon" in {appid}').format(appid=app['packageName']))
168 for icondir in icondirs:
169 url = _append_to_url_path(section, icondir, icon)
170 if icondir not in urls:
172 urls[icondir].append(url)
174 for icondir in icondirs:
175 _run_wget(os.path.join(basedir, section, icondir), urls[icondir])
178 if __name__ == "__main__":