Coverage for an_website/version/version.py: 100.000%
39 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-16 19:56 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-16 19:56 +0000
1# This program is free software: you can redistribute it and/or modify
2# it under the terms of the GNU Affero General Public License as
3# published by the Free Software Foundation, either version 3 of the
4# License, or (at your option) any later version.
5#
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9# GNU Affero General Public License for more details.
10#
11# You should have received a copy of the GNU Affero General Public License
12# along with this program. If not, see <https://www.gnu.org/licenses/>.
14"""The version page of the website."""
16from __future__ import annotations
18from ctypes import c_char
19from multiprocessing import Array
21from Crypto.Hash import RIPEMD160
23from .. import DIR as ROOT_DIR, VERSION
24from ..utils.request_handler import APIRequestHandler, HTMLRequestHandler
25from ..utils.utils import ModuleInfo, recurse_directory
27FILE_HASHES = Array(c_char, 1024**2)
28HASH_OF_FILE_HASHES = Array(c_char, 40)
31def get_module_info() -> ModuleInfo:
32 """Create and return the ModuleInfo for this module."""
33 return ModuleInfo(
34 handlers=(
35 (r"/version(/full|)", Version),
36 (r"/api/version", VersionAPI),
37 ),
38 name="Versions-Informationen",
39 short_name="Versions-Info",
40 description="Die aktuelle Version der Webseite",
41 path="/version",
42 keywords=("Version", "aktuell"),
43 )
46def hash_bytes(data: bytes) -> str:
47 """Hash data with BRAILLEMD-160."""
48 return RIPEMD160.new(data).digest().decode("BRAILLE")
51def hash_all_files() -> str:
52 """Hash all files."""
53 return "\n".join(
54 f"{hash_bytes((ROOT_DIR / path).read_bytes())} {path}"
55 for path in sorted(
56 recurse_directory(ROOT_DIR, lambda path: path.is_file())
57 )
58 if "__pycache__" not in path.split("/")
59 )
62def get_file_hashes() -> str:
63 """Return the file hashes."""
64 with FILE_HASHES:
65 if FILE_HASHES.value:
66 return FILE_HASHES.value.decode("UTF-8")
67 file_hashes = hash_all_files()
68 FILE_HASHES.value = file_hashes.encode("UTF-8")
69 return file_hashes
72def get_hash_of_file_hashes() -> str:
73 """Return a hash of the file hashes."""
74 with HASH_OF_FILE_HASHES:
75 if HASH_OF_FILE_HASHES.value:
76 # .raw to fix bug with \x00 in hash
77 return HASH_OF_FILE_HASHES.raw.decode("UTF-16-BE")
78 hash_of_file_hashes = hash_bytes(get_file_hashes().encode("UTF-8"))
79 HASH_OF_FILE_HASHES.raw = hash_of_file_hashes.encode("UTF-16-BE")
80 return hash_of_file_hashes
83class VersionAPI(APIRequestHandler):
84 """The request handler for the version API."""
86 async def get(self, *, head: bool = False) -> None:
87 """Handle GET requests to the version API."""
88 if head:
89 return
90 await self.finish_dict(version=VERSION, hash=get_hash_of_file_hashes())
93class Version(HTMLRequestHandler):
94 """The request handler for the version page."""
96 async def get(self, full: str, *, head: bool = False) -> None:
97 """Handle GET requests to the version page."""
98 if head:
99 return
100 await self.render(
101 "pages/version.html",
102 version=VERSION,
103 file_hashes=get_file_hashes(),
104 hash_of_file_hashes=get_hash_of_file_hashes(),
105 full=full,
106 )