diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 800c2057..0f3ffc83 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -138,7 +138,7 @@ arch_pip_install: - master@fdroid/fdroidserver script: - pacman --sync --sysupgrade --refresh --noconfirm git grep python-pip python-virtualenv tar - - pip install -e . + - pip install -e .[test] - fdroid - fdroid readmeta - fdroid update --help diff --git a/fdroidserver/common.py b/fdroidserver/common.py index a19795a0..84451d70 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -3764,10 +3764,12 @@ def is_repo_file(filename): and not filename.endswith(b'.idsig') \ and not filename.endswith(b'.log.gz') \ and os.path.basename(filename) not in [ + b'index.css', b'index.jar', b'index_unsigned.jar', b'index.xml', b'index.html', + b'index.png', b'index-v1.jar', b'index-v1.json', b'categories.txt', diff --git a/fdroidserver/index.py b/fdroidserver/index.py index fb62014b..f9120bd8 100644 --- a/fdroidserver/index.py +++ b/fdroidserver/index.py @@ -30,6 +30,7 @@ import tempfile import urllib.parse import zipfile import calendar +import qrcode from binascii import hexlify, unhexlify from datetime import datetime, timezone from xml.dom.minidom import Document @@ -129,6 +130,334 @@ def make(apps, apks, repodir, archive): fdroid_signing_key_fingerprints) make_v1(sortedapps, apks, repodir, repodict, requestsdict, fdroid_signing_key_fingerprints) + make_website(sortedapps, repodir, repodict) + + +def _should_file_be_generated(path, magic_string): + if os.path.exists(path): + with open(path) as f: + if magic_string not in f.readline(): # if the magic_string is not in the first line the file should be overwritten + return False + return True + + +def make_website(apps, repodir, repodict): + _, repo_pubkey_fingerprint = extract_pubkey() + repo_pubkey_fingerprint_stripped = repo_pubkey_fingerprint.replace(" ", "") + link = repodict["address"] + link_fingerprinted = "{link}?fingerprint={fingerprint}".format(link=link, fingerprint=repo_pubkey_fingerprint_stripped) + autogenerate_comment = "auto-generated - fdroid index updates will overwrite this file" # do not change this string, as it will break the updates for existing files with older versions of this string + + if not os.path.exists(repodir): + os.makedirs(repodir) + + qrcode.make(link_fingerprinted).save(os.path.join(repodir, "index.png")) + + html_name = 'index.html' + html_file = os.path.join(repodir, html_name) + + if _should_file_be_generated(html_file, autogenerate_comment): + with open(html_file, 'w') as f: + name = repodict["name"] + description = repodict["description"] + icon = repodict["icon"] + f.write(""" + + + + + + + {name} + + + + + + + + + + + + +

+ {name} +

+
+

+ + + QR: test + + + {description} +
+
+ Currently it serves + + {number_of_apps} + + apps. To add it to your F-Droid client, scan the QR code (click it to enlarge) or use this URL: +

+

+ + + {link} + + +

+

+ If you would like to manually verify the fingerprint (SHA-256) of the repository signing key, here it is: +
+ + {fingerprint} + +

+
+ + +""".format(autogenerate_comment=autogenerate_comment, + description=description, + fingerprint=repo_pubkey_fingerprint, + icon=icon, + link=link, + link_fingerprinted=link_fingerprinted, + name=name, + number_of_apps=str(len(apps)))) + + css_file = os.path.join(repodir, "index.css") + if _should_file_be_generated(css_file, autogenerate_comment): + with open(css_file, "w") as f: + # this auto generated comment was not included via .format(), as python seems to have problems with css files in combination with .format() + f.write("""/* auto-generated - fdroid index updates will overwrite this file */ +BODY { + font-family : Arial, Helvetica, Sans-Serif; + color : #0000ee; + background-color : #ffffff; +} +p { + text-align : justify; +} +p.center { + text-align : center; +} +TD { + font-family : Arial, Helvetica, Sans-Serif; + color : #0000ee; +} +body,td { + font-size : 14px; +} +TH { + font-family : Arial, Helvetica, Sans-Serif; + color : #0000ee; + background-color : #F5EAD4; +} +a:link { + color : #bb0000; +} +a:visited { + color : #ff0000; +} +.zitat { + margin-left : 1cm; + margin-right : 1cm; + font-style : italic; +} +#intro { + border-spacing : 1em; + border : 1px solid gray; + border-radius : 0.5em; + box-shadow : 10px 10px 5px #888; + margin : 1.5em; + font-size : .9em; + width : 600px; + max-width : 90%; + display : table; + margin-left : auto; + margin-right : auto; + font-size : .8em; + color : #555555; +} +#intro > p { + margin-top : 0; +} +#intro p:last-child { + margin-bottom : 0; +} +.last { + border-bottom : 1px solid black; + padding-bottom : .5em; + text-align : center; +} +table { + border-collapse : collapse; +} +h2 { + text-align : center; +} +.perms { + font-family : monospace; + font-size : .8em; +} +.repoapplist { + display : table; + border-collapse : collapse; + margin-left : auto; + margin-right : auto; + width : 600px; + max-width : 90%; +} +.approw, appdetailrow { + display : table-row; +} +.appdetailrow { + display : flex; + padding : .5em; +} +.appiconbig, .appdetailblock, .appdetailcell { + display : table-cell +} +.appiconbig { + vertical-align : middle; + text-align : center; +} +.appdetailinner { + width : 100%; +} +.applinkcell { + text-align : center; + float : right; + width : 100%; + margin-bottom : .1em; +} +.paddedlink { + margin : 1em; +} +.approw { + border-spacing : 1em; + border : 1px solid gray; + border-radius : 0.5em; + padding : 0.5em; + margin : 1.5em; +} +.appdetailinner .appdetailrow:first-child { + background-color : #d5d5d5; +} +.appdetailinner .appdetailrow:first-child .appdetailcell { + min-width : 33%; + flex : 1 33%; + text-align : center; +} +.appdetailinner .appdetailrow:first-child .appdetailcell:first-child { + text-align : left; +} +.appdetailinner .appdetailrow:first-child .appdetailcell:last-child { + float : none; + text-align : right; +} +.minor-details { + font-size : .8em; + color : #555555; +} +.boldname { + font-weight : bold; +} +#appcount { + text-align : center; + margin-bottom : .5em; +} +kbd { + padding : 0.1em 0.6em; + border : 1px solid #CCC; + background-color : #F7F7F7; + color : #333; + box-shadow : 0px 1px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 2px #FFF inset; + border-radius : 3px; + display : inline-block; + margin : 0px 0.1em; + text-shadow : 0px 1px 0px #FFF; + white-space : nowrap; +} +div.filterline, div.repoline { + display : table; + margin-left : auto; + margin-right : auto; + margin-bottom : 1em; + vertical-align : middle; + display : table; + font-size : .8em; +} +.filterline form { + display : table-row; +} +.filterline .filtercell { + display : table-cell; + vertical-align : middle; +} +fieldset { + float : left; +} +fieldset select, fieldset input, #reposelect select, #reposelect input { + font-size : .9em; +} +.pager { + display : table; + margin-left : auto; + margin-right : auto; + width : 600px; + max-width : 90%; + padding-top : .6em; +} +/* should correspond to .repoapplist */ +.pagerrow { + display : table-row; +} +.pagercell { + display : table-cell; +} +.pagercell.left { + text-align : left; + padding-right : 1em; +} +.pagercell.middle { + text-align : center; + font-size : .9em; + color : #555; +} +.pagercell.right { + text-align : right; + padding-left : 1em; +} +.anti { + color : peru; +} +.antibold { + color : crimson; +} +#footer { + text-align : center; + margin-top : 1em; + font-size : 11px; + color : #555; +} +#footer img { + vertical-align : middle; +} +@media (max-width: 600px) { + .repoapplist { + display : block; + } + .appdetailinner, .appdetailrow { + display : block; + } + .appdetailcell { + display : block; + float : left; + line-height : 1.5em; + } +}""") def make_v1(apps, packages, repodir, repodict, requestsdict, fdroid_signing_key_fingerprints): diff --git a/setup.py b/setup.py index 669ec33a..3d751489 100755 --- a/setup.py +++ b/setup.py @@ -88,7 +88,10 @@ setup(name='fdroidserver', 'yamllint', ], extras_require={ - 'test': ['pyjks'], + 'test': [ + 'pyjks', + 'html5print' + ], }, classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tests/complete-ci-tests b/tests/complete-ci-tests index 75fe515e..07e0b2a0 100755 --- a/tests/complete-ci-tests +++ b/tests/complete-ci-tests @@ -66,7 +66,7 @@ cd $WORKSPACE rm -rf $WORKSPACE/env pyvenv $WORKSPACE/env . $WORKSPACE/env/bin/activate -pip3 install --quiet -e $WORKSPACE +pip3 install --quiet -e $WORKSPACE[test] python3 setup.py compile_catalog install # make sure translation files were installed diff --git a/tests/index.TestCase b/tests/index.TestCase index 16885dbf..e8f22036 100755 --- a/tests/index.TestCase +++ b/tests/index.TestCase @@ -354,6 +354,47 @@ class IndexTest(unittest.TestCase): 'https://gitlab.com/group/project/-/raw/master/fdroid'], fdroidserver.index.get_mirror_service_urls(url)) + def test_make_website(self): + tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, + dir=self.tmpdir) + os.chdir(tmptestsdir) + os.mkdir('metadata') + os.mkdir('repo') + + repodict = { + 'address': 'https://example.com/fdroid/repo', + 'description': 'This is just a test', + 'icon': 'blahblah', + 'name': 'test', + 'timestamp': datetime.datetime.now(), + 'version': 12, + } + + fdroidserver.common.config['repo_pubkey'] = 'ffffffffffffffffffffffffffffffffff' + + fdroidserver.index.make_website([], "repo", repodict) + self.assertTrue(os.path.exists(os.path.join('repo', 'index.html'))) + self.assertTrue(os.path.exists(os.path.join('repo', 'index.css'))) + self.assertTrue(os.path.exists(os.path.join('repo', 'index.png'))) + + try: + from html5print import CSSBeautifier, HTMLBeautifier + except ImportError: + print('WARNING: skipping rest of test since html5print is missing!') + return + + with open(os.path.join("repo", "index.html")) as f: + html = f.read() + pretty_html = HTMLBeautifier.beautify(html) + self.maxDiff = None + self.assertEquals(html, pretty_html) + + with open(os.path.join("repo", "index.css")) as f: + css = f.read() + pretty_css = CSSBeautifier.beautify(css) + self.maxDiff = None + self.assertEquals(css, pretty_css) + if __name__ == "__main__": os.chdir(os.path.dirname(__file__))