Coverage for an_website/version/version.py: 100.000%
40 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 14:47 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 14:47 +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.fix_static_path_impl import recurse_directory
25from ..utils.request_handler import APIRequestHandler, HTMLRequestHandler
26from ..utils.utils import ModuleInfo
28FILE_HASHES = Array(c_char, 1024**2)
29HASH_OF_FILE_HASHES = Array(c_char, 40)
32def get_module_info() -> ModuleInfo:
33 """Create and return the ModuleInfo for this module."""
34 return ModuleInfo(
35 handlers=(
36 (r"/version(/full|)", Version),
37 (r"/api/version", VersionAPI),
38 ),
39 name="Versions-Informationen",
40 short_name="Versions-Info",
41 description="Die aktuelle Version der Webseite",
42 path="/version",
43 keywords=("Version", "aktuell"),
44 )
47def hash_bytes(data: bytes) -> str:
48 """Hash data with BRAILLEMD-160."""
49 return RIPEMD160.new(data).digest().decode("BRAILLE")
52def hash_all_files() -> str:
53 """Hash all files."""
54 return "\n".join(
55 f"{hash_bytes((ROOT_DIR / path).read_bytes())} {path}"
56 for path in sorted(
57 recurse_directory(ROOT_DIR, lambda path: path.is_file())
58 )
59 if "__pycache__" not in path.split("/")
60 )
63def get_file_hashes() -> str:
64 """Return the file hashes."""
65 with FILE_HASHES:
66 if FILE_HASHES.value:
67 return FILE_HASHES.value.decode("UTF-8")
68 file_hashes = hash_all_files()
69 FILE_HASHES.value = file_hashes.encode("UTF-8")
70 return file_hashes
73def get_hash_of_file_hashes() -> str:
74 """Return a hash of the file hashes."""
75 with HASH_OF_FILE_HASHES:
76 if HASH_OF_FILE_HASHES.value:
77 # .raw to fix bug with \x00 in hash
78 return HASH_OF_FILE_HASHES.raw.decode("UTF-16-BE")
79 hash_of_file_hashes = hash_bytes(get_file_hashes().encode("UTF-8"))
80 HASH_OF_FILE_HASHES.raw = hash_of_file_hashes.encode("UTF-16-BE")
81 return hash_of_file_hashes
84class VersionAPI(APIRequestHandler):
85 """The request handler for the version API."""
87 async def get(self, *, head: bool = False) -> None:
88 """Handle GET requests to the version API."""
89 if head:
90 return
91 await self.finish_dict(version=VERSION, hash=get_hash_of_file_hashes())
94class Version(HTMLRequestHandler):
95 """The request handler for the version page."""
97 async def get(self, full: str, *, head: bool = False) -> None:
98 """Handle GET requests to the version page."""
99 if head:
100 return
101 await self.render(
102 "pages/version.html",
103 version=VERSION,
104 file_hashes=get_file_hashes(),
105 hash_of_file_hashes=get_hash_of_file_hashes(),
106 full=full,
107 )