televault 0.1.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
televault/__init__.py CHANGED
@@ -13,4 +13,4 @@ __version__ = "1.0.0"
13
13
  __author__ = "Yahya Toubali"
14
14
  __email__ = "yahya@yahyatoubali.me"
15
15
  __license__ = "MIT"
16
- __url__ = "https://github.com/yahyatoubali/televault"
16
+ __url__ = "https://github.com/yahyatoubali/televault"
televault/chunker.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """File chunking utilities for TeleVault."""
2
2
 
3
3
  import os
4
- from pathlib import Path
5
- from typing import Iterator, BinaryIO
4
+ from collections.abc import Iterator
6
5
  from dataclasses import dataclass
6
+ from pathlib import Path
7
7
 
8
8
  import blake3
9
9
 
@@ -16,12 +16,12 @@ MAX_CHUNK_SIZE = 2000 * 1024 * 1024 # ~2GB (with margin)
16
16
  @dataclass
17
17
  class Chunk:
18
18
  """A chunk of file data ready for upload."""
19
-
19
+
20
20
  index: int
21
21
  data: bytes
22
22
  hash: str
23
23
  size: int
24
-
24
+
25
25
  @property
26
26
  def filename(self) -> str:
27
27
  """Generate chunk filename."""
@@ -53,20 +53,20 @@ def iter_chunks(
53
53
  ) -> Iterator[Chunk]:
54
54
  """
55
55
  Split a file into chunks.
56
-
56
+
57
57
  Yields Chunk objects with index, data, hash, and size.
58
58
  Memory-efficient: only one chunk in memory at a time.
59
59
  """
60
60
  if chunk_size > MAX_CHUNK_SIZE:
61
61
  raise ValueError(f"Chunk size {chunk_size} exceeds max {MAX_CHUNK_SIZE}")
62
-
62
+
63
63
  with open(file_path, "rb") as f:
64
64
  index = 0
65
65
  while True:
66
66
  data = f.read(chunk_size)
67
67
  if not data:
68
68
  break
69
-
69
+
70
70
  yield Chunk(
71
71
  index=index,
72
72
  data=data,
@@ -94,7 +94,7 @@ def read_chunk(
94
94
  data = f.read(chunk_size)
95
95
  if not data:
96
96
  raise ValueError(f"Chunk {index} is empty or out of range")
97
-
97
+
98
98
  return Chunk(
99
99
  index=index,
100
100
  data=data,
@@ -106,37 +106,39 @@ def read_chunk(
106
106
  class ChunkWriter:
107
107
  """
108
108
  Reassemble chunks into a file.
109
-
109
+
110
110
  Handles out-of-order chunks by writing to correct positions.
111
111
  """
112
-
113
- def __init__(self, output_path: str | Path, total_size: int, chunk_size: int = DEFAULT_CHUNK_SIZE):
112
+
113
+ def __init__(
114
+ self, output_path: str | Path, total_size: int, chunk_size: int = DEFAULT_CHUNK_SIZE
115
+ ):
114
116
  self.output_path = Path(output_path)
115
117
  self.total_size = total_size
116
118
  self.chunk_size = chunk_size
117
119
  self.written_chunks: set[int] = set()
118
-
120
+
119
121
  # Pre-allocate file
120
122
  self.output_path.parent.mkdir(parents=True, exist_ok=True)
121
123
  with open(self.output_path, "wb") as f:
122
124
  f.truncate(total_size)
123
-
125
+
124
126
  def write_chunk(self, chunk: Chunk) -> None:
125
127
  """Write a chunk to the correct position."""
126
128
  if chunk.index in self.written_chunks:
127
129
  return # Already written
128
-
130
+
129
131
  offset = chunk.index * self.chunk_size
130
132
  with open(self.output_path, "r+b") as f:
131
133
  f.seek(offset)
132
134
  f.write(chunk.data)
133
-
135
+
134
136
  self.written_chunks.add(chunk.index)
135
-
137
+
136
138
  def is_complete(self, expected_chunks: int) -> bool:
137
139
  """Check if all chunks have been written."""
138
140
  return len(self.written_chunks) == expected_chunks
139
-
141
+
140
142
  def missing_chunks(self, expected_chunks: int) -> list[int]:
141
143
  """Get list of missing chunk indices."""
142
144
  return [i for i in range(expected_chunks) if i not in self.written_chunks]
@@ -145,26 +147,26 @@ class ChunkWriter:
145
147
  class ChunkBuffer:
146
148
  """
147
149
  Buffer for streaming chunk creation.
148
-
150
+
149
151
  Useful when reading from a stream (network, compression, encryption)
150
152
  rather than a file.
151
153
  """
152
-
154
+
153
155
  def __init__(self, chunk_size: int = DEFAULT_CHUNK_SIZE):
154
156
  self.chunk_size = chunk_size
155
157
  self.buffer = bytearray()
156
158
  self.index = 0
157
-
159
+
158
160
  def write(self, data: bytes) -> Iterator[Chunk]:
159
161
  """
160
162
  Write data to buffer, yielding complete chunks.
161
163
  """
162
164
  self.buffer.extend(data)
163
-
165
+
164
166
  while len(self.buffer) >= self.chunk_size:
165
- chunk_data = bytes(self.buffer[:self.chunk_size])
166
- self.buffer = self.buffer[self.chunk_size:]
167
-
167
+ chunk_data = bytes(self.buffer[: self.chunk_size])
168
+ self.buffer = self.buffer[self.chunk_size :]
169
+
168
170
  yield Chunk(
169
171
  index=self.index,
170
172
  data=chunk_data,
@@ -172,15 +174,15 @@ class ChunkBuffer:
172
174
  size=len(chunk_data),
173
175
  )
174
176
  self.index += 1
175
-
177
+
176
178
  def flush(self) -> Chunk | None:
177
179
  """Flush remaining data as final chunk."""
178
180
  if not self.buffer:
179
181
  return None
180
-
182
+
181
183
  chunk_data = bytes(self.buffer)
182
184
  self.buffer.clear()
183
-
185
+
184
186
  return Chunk(
185
187
  index=self.index,
186
188
  data=chunk_data,