11import hashlib
22import io
3+ import logging
34import zlib
4- from collections .abc import Iterable
55from dataclasses import dataclass
6- from typing import IO , Any , Optional
6+ from typing import IO , Any
77
8- from ratarmountcore . utils import RatarmountError
8+ logger = logging . getLogger ( __name__ )
99
1010
1111@dataclass (frozen = True )
12- class HashAlgorithmSpec :
13- name : str
12+ class HashAlgorithm :
1413 sparse : bool
1514
1615
@@ -25,49 +24,22 @@ def hexdigest(self) -> str:
2524 return f"{ self ._value & 0xFFFFFFFF :08x} "
2625
2726
28- def _normalize_algorithm_name (name : str ) -> str :
29- return name .strip ().lower ().replace ('-' , '_' )
30-
31-
32- def _discover_hashlib_algorithms () -> set [str ]:
27+ def _build_hash_registry () -> dict [str , HashAlgorithm ]:
3328 # shake_* require an explicit output length for digest() / hexdigest() and therefore do not fit this interface.
34- discovered : set [str ] = set ()
35- for name in hashlib .algorithms_available :
36- normalized = _normalize_algorithm_name (name )
37- if not normalized or normalized .startswith ('shake_' ):
38- continue
39- discovered .add (normalized )
40- return discovered
41-
42-
43- def _build_hash_registry () -> dict [str , HashAlgorithmSpec ]:
44- registry : dict [str , HashAlgorithmSpec ] = {
45- name : HashAlgorithmSpec (name = name , sparse = False ) for name in sorted (_discover_hashlib_algorithms ())
29+ hashlib_names = {name for name in hashlib .algorithms_available if name and not name .startswith ('shake' )}
30+ registry : dict [str , HashAlgorithm ] = {
31+ # Replace sha3_512 to sha3-512 for readability in the --hashes CLI option and the user.hash.sha3-512 key.
32+ name .replace ('_' , '-' ): HashAlgorithm (sparse = False )
33+ for name in sorted (hashlib_names )
4634 }
47- registry ['crc32' ] = HashAlgorithmSpec ( name = 'crc32' , sparse = False )
48- registry ['smplayer' ] = HashAlgorithmSpec ( name = 'smplayer' , sparse = True )
35+ registry ['crc32' ] = HashAlgorithm ( sparse = False )
36+ registry ['smplayer' ] = HashAlgorithm ( sparse = True )
4937 return registry
5038
5139
5240HASH_REGISTRY = _build_hash_registry ()
5341
5442
55- def parse_hash_algorithms (algorithms : Optional [Iterable [str ]]) -> list [str ]:
56- if not algorithms :
57- return []
58-
59- normalized = [
60- _normalize_algorithm_name (part )
61- for algorithm in algorithms
62- for part in algorithm .split (',' )
63- if part .strip ()
64- ]
65- unknown = [algorithm for algorithm in normalized if algorithm not in HASH_REGISTRY ]
66- if unknown :
67- raise RatarmountError (f"Unsupported hash algorithm(s): { ', ' .join (unknown )} " )
68- return list (dict .fromkeys (normalized ))
69-
70-
7143def _zero_pad (data : bytes , size : int ) -> bytes :
7244 return data [:size ] + b'\0 ' * max (0 , size - len (data ))
7345
@@ -111,21 +83,23 @@ def compute_hashes(fileObject: IO[bytes], fileSize: int, algorithms: list[str])
11183 hashers : dict [str , Any ] = {}
11284 sparseAlgorithms : list [str ] = []
11385 for algorithm in algorithms :
114- spec = HASH_REGISTRY .get (algorithm , None )
115- if spec is None :
116- raise RatarmountError (f"Unsupported hash algorithm(s): { algorithm } " )
86+ if not algorithm :
87+ continue
88+ specification = HASH_REGISTRY .get (algorithm , None )
89+ if specification is None :
90+ logger .warning ("Unsupported hash algorithm: %s" , algorithm )
91+ continue
11792
118- if spec .sparse :
93+ if specification .sparse :
11994 sparseAlgorithms .append (algorithm )
95+ elif algorithm == 'crc32' :
96+ hashers [algorithm ] = _CRC32Hasher ()
12097 else :
121- # Create hasher objects for each algorithm.
122- if algorithm == 'crc32' :
123- hashers [algorithm ] = _CRC32Hasher ()
124- continue
12598 try :
99+ # hashlib.new seems to be stable against _ and - substitutions and probably also case.
126100 hashers [algorithm ] = hashlib .new (algorithm )
127- except ValueError as exception :
128- raise RatarmountError ( f "Unsupported hash algorithm(s): { algorithm } " ) from exception
101+ except ValueError :
102+ logger . warning ( "Unsupported hash: %s" , algorithm , exc_info = logger . isEnabledFor ( logging . DEBUG ))
129103
130104 # Read and hash in chunks of 1 MiB.
131105 if hashers :
0 commit comments