12 from argparse import ArgumentParser
13 from urllib.parse import urlparse
26 parser = ArgumentParser(usage=_("%(prog)s [options] url"))
27 common.setup_global_opts(parser)
28 parser.add_argument("url", nargs='?', help=_("Base URL to mirror"))
29 parser.add_argument("--archive", action='store_true', default=False,
30 help=_("Also mirror the full archive section"))
31 parser.add_argument("--output-dir", default=os.getcwd(),
32 help=_("The directory to write the mirror to"))
33 options = parser.parse_args()
35 if options.url is None:
36 logging.error(_('A URL is required as an argument!') + '\n')
41 basedir = options.output_dir
43 url = urlparse(baseurl)
47 ip = ipaddress.ip_address(hostname)
50 if hostname == 'f-droid.org' \
51 or (ip is not None and hostname in socket.gethostbyname_ex('f-droid.org')[2]):
52 print(_('ERROR: this command should never be used to mirror f-droid.org!\n'
53 'A full mirror of f-droid.org requires more than 200GB.'))
56 path = url.path.rstrip('/')
57 if path.endswith('repo') or path.endswith('archive'):
58 logging.error(_('Do not include "{path}" in URL!').format(path=path.split('/')[-1]))
60 elif not path.endswith('fdroid'):
61 logging.warning(_('{url} does not end with "fdroid", check the URL path!')
64 icondirs = ['icons', ]
65 for density in update.screen_densities:
66 icondirs.append('icons-' + density)
69 sections = ('repo', 'archive')
73 for section in sections:
74 sectionurl = baseurl + '/' + section
75 sectiondir = os.path.join(basedir, section)
76 repourl = sectionurl + '/index-v1.jar'
78 content, etag = net.http_get(repourl)
79 with zipfile.ZipFile(io.BytesIO(content)) as zip:
80 jsoncontents = zip.open('index-v1.json').read()
82 os.makedirs(sectiondir, exist_ok=True)
84 for icondir in icondirs:
85 os.makedirs(os.path.join(sectiondir, icondir), exist_ok=True)
87 data = json.loads(jsoncontents.decode('utf-8'))
89 for packageName, packageList in data['packages'].items():
90 for package in packageList:
92 for k in ('apkName', 'srcname'):
94 to_fetch.append(package[k])
96 logging.error(_('{appid} is missing {name}')
97 .format(appid=package['packageName'], name=k))
99 if not os.path.exists(f) \
100 or (f.endswith('.apk') and os.path.getsize(f) != package['size']):
101 url = sectionurl + '/' + f
103 urls += url + '.asc\n'
105 for app in data['apps']:
106 localized = app.get('localized')
108 for locale, d in localized.items():
109 for k in update.GRAPHIC_NAMES:
112 urls += '/'.join((sectionurl, locale, f)) + '\n'
113 for k in update.SCREENSHOT_DIRS:
117 urls += '/'.join((sectionurl, locale, k, f)) + '\n'
119 with open('.rsync-input-file', 'w') as fp:
121 subprocess.call(['wget', '--continue', '--user-agent="fdroid mirror"',
122 '--input-file=.rsync-input-file'])
123 os.remove('.rsync-input-file')
126 for app in data['apps']:
127 if 'icon' not in app:
128 logging.error(_('no "icon" in {appid}').format(appid=app['packageName']))
131 for icondir in icondirs:
132 url = sectionurl + '/' + icondir + '/' + icon
133 if icondir not in urls:
135 urls[icondir] += url + '\n'
137 for icondir in icondirs:
138 os.chdir(os.path.join(basedir, section, icondir))
139 with open('.rsync-input-file', 'w') as fp:
140 fp.write(urls[icondir])
141 subprocess.call(['wget', '--continue', '--input-file=.rsync-input-file'])
142 os.remove('.rsync-input-file')
145 if __name__ == "__main__":