compressor.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. # -*- coding: utf-8 -*-
  2. """
  3. Basis-Kompressor-Interface.
  4. Definiert die abstrakte Schnittstelle für alle
  5. Komprimierungsimplementierungen.
  6. """
  7. from __future__ import annotations
  8. from abc import ABC, abstractmethod
  9. from dataclasses import dataclass, field
  10. from datetime import datetime
  11. from enum import IntEnum
  12. from typing import Any
  13. class CompressionLevel(IntEnum):
  14. """Komprimierungsstufen."""
  15. NONE = 0 # Keine Komprimierung
  16. FASTEST = 1 # Schnellste Komprimierung
  17. FAST = 3 # Schnell
  18. DEFAULT = 6 # Standard
  19. BEST = 9 # Beste Komprimierung (langsamste)
  20. @dataclass
  21. class CompressionStats:
  22. """Statistiken über Komprimierungsoperationen."""
  23. total_compressed: int = 0 # Anzahl Komprimierungen
  24. total_decompressed: int = 0 # Anzahl Dekomprimierungen
  25. bytes_in: int = 0 # Eingabe-Bytes insgesamt
  26. bytes_out: int = 0 # Ausgabe-Bytes insgesamt
  27. compression_ratio: float = 0.0 # Durchschnittliches Verhältnis
  28. total_time_ms: float = 0.0 # Gesamtzeit in ms
  29. errors: int = 0 # Fehler
  30. last_operation: datetime | None = None
  31. def update_compression(
  32. self,
  33. input_size: int,
  34. output_size: int,
  35. time_ms: float,
  36. ) -> None:
  37. """Aktualisiert Statistik nach Komprimierung."""
  38. self.total_compressed += 1
  39. self.bytes_in += input_size
  40. self.bytes_out += output_size
  41. self.total_time_ms += time_ms
  42. self.last_operation = datetime.now()
  43. if self.bytes_in > 0:
  44. self.compression_ratio = self.bytes_out / self.bytes_in
  45. def update_decompression(
  46. self,
  47. input_size: int,
  48. output_size: int,
  49. time_ms: float,
  50. ) -> None:
  51. """Aktualisiert Statistik nach Dekomprimierung."""
  52. self.total_decompressed += 1
  53. self.total_time_ms += time_ms
  54. self.last_operation = datetime.now()
  55. def to_dict(self) -> dict[str, Any]:
  56. """Konvertiert zu Dictionary."""
  57. return {
  58. "total_compressed": self.total_compressed,
  59. "total_decompressed": self.total_decompressed,
  60. "bytes_in": self.bytes_in,
  61. "bytes_out": self.bytes_out,
  62. "compression_ratio": round(self.compression_ratio, 4),
  63. "total_time_ms": round(self.total_time_ms, 2),
  64. "errors": self.errors,
  65. "space_saved_percent": round(
  66. (1 - self.compression_ratio) * 100, 2
  67. ) if self.compression_ratio > 0 else 0,
  68. }
  69. class Compressor(ABC):
  70. """
  71. Abstrakte Basis-Klasse für Kompressoren.
  72. Definiert die Schnittstelle die alle konkreten
  73. Komprimierungsimplementierungen erfüllen müssen.
  74. Beispiel:
  75. compressor = ZlibCompressor(level=CompressionLevel.BEST)
  76. compressed = compressor.compress(data)
  77. original = compressor.decompress(compressed)
  78. """
  79. def __init__(
  80. self,
  81. level: CompressionLevel = CompressionLevel.DEFAULT,
  82. ) -> None:
  83. """
  84. Initialisiert den Kompressor.
  85. Args:
  86. level: Komprimierungsstufe
  87. """
  88. self.level = level
  89. self.stats = CompressionStats()
  90. @property
  91. @abstractmethod
  92. def name(self) -> str:
  93. """Name des Komprimierungsalgorithmus."""
  94. pass
  95. @property
  96. @abstractmethod
  97. def content_type(self) -> str:
  98. """Content-Type für komprimierte Daten."""
  99. pass
  100. @property
  101. def min_size(self) -> int:
  102. """
  103. Minimale Größe für Komprimierung.
  104. Daten unter dieser Größe werden nicht komprimiert,
  105. da der Overhead größer als der Nutzen sein kann.
  106. """
  107. return 100 # 100 Bytes Standard
  108. def should_compress(self, data: bytes) -> bool:
  109. """
  110. Prüft ob Daten komprimiert werden sollten.
  111. Args:
  112. data: Zu prüfende Daten
  113. Returns:
  114. True wenn Komprimierung sinnvoll
  115. """
  116. return len(data) >= self.min_size
  117. @abstractmethod
  118. def compress(self, data: bytes) -> bytes:
  119. """
  120. Komprimiert Daten.
  121. Args:
  122. data: Unkomprimierte Daten
  123. Returns:
  124. Komprimierte Daten
  125. """
  126. pass
  127. @abstractmethod
  128. def decompress(self, data: bytes) -> bytes:
  129. """
  130. Dekomprimiert Daten.
  131. Args:
  132. data: Komprimierte Daten
  133. Returns:
  134. Ursprüngliche Daten
  135. """
  136. pass
  137. def compress_if_beneficial(self, data: bytes) -> tuple[bytes, bool]:
  138. """
  139. Komprimiert nur wenn es sich lohnt.
  140. Args:
  141. data: Zu komprimierende Daten
  142. Returns:
  143. Tuple (Daten, ob_komprimiert)
  144. """
  145. if not self.should_compress(data):
  146. return data, False
  147. compressed = self.compress(data)
  148. # Nur verwenden wenn kleiner
  149. if len(compressed) < len(data):
  150. return compressed, True
  151. return data, False
  152. def get_stats(self) -> CompressionStats:
  153. """Gibt Komprimierungsstatistiken zurück."""
  154. return self.stats
  155. def reset_stats(self) -> None:
  156. """Setzt Statistiken zurück."""
  157. self.stats = CompressionStats()
  158. def __repr__(self) -> str:
  159. return f"{self.__class__.__name__}(level={self.level.name})"
  160. class CompressorError(Exception):
  161. """Fehler bei Komprimierung/Dekomprimierung."""
  162. pass
  163. class CompressionError(CompressorError):
  164. """Fehler bei Komprimierung."""
  165. pass
  166. class DecompressionError(CompressorError):
  167. """Fehler bei Dekomprimierung."""
  168. pass