Skip to content

Commit c37212d

Browse files
committed
refactoring again
1 parent 3cf53c9 commit c37212d

21 files changed

Lines changed: 1430 additions & 1042 deletions

src/blkcache/backend.py

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"""
2+
nbdkit Python backend integration for block-level device caching.
3+
4+
This module serves as the bridge between nbdkit and our file abstraction layer.
5+
It handles the "outside" config (nbdkit parameters) while delegating file
6+
operations to the composed file chain.
7+
"""
8+
9+
import logging
10+
from pathlib import Path
11+
from typing import Dict, Any
12+
13+
# Import file detection
14+
from blkcache.file import detect
15+
from blkcache.file.device import DEFAULT_SECTOR_SIZE
16+
17+
log = logging.getLogger(__name__)
18+
19+
20+
class HandleManager:
21+
"""Manages file handles and their lifecycle for nbdkit backend."""
22+
23+
def __init__(self):
24+
self._handles: Dict[int, Any] = {} # handle_id -> File instance
25+
self._next_handle = 1
26+
self._files = [] # Keep references to context managers
27+
28+
def open_file(self, path: Path, mode: str) -> int:
29+
"""Open a file and return a handle ID."""
30+
file_cls = detect(path)
31+
file_instance = file_cls(path, mode)
32+
33+
# Enter the context manager and keep reference
34+
opened_file = file_instance.__enter__()
35+
self._files.append((file_instance, opened_file))
36+
37+
# Assign handle and store
38+
handle = self._next_handle
39+
self._handles[handle] = opened_file
40+
self._next_handle += 1
41+
42+
log.debug("Opened file %s as handle %d", path, handle)
43+
return handle
44+
45+
def get_file(self, handle: int):
46+
"""Get the file instance for a handle."""
47+
if handle not in self._handles:
48+
raise ValueError(f"Invalid handle: {handle}")
49+
return self._handles[handle]
50+
51+
def close_file(self, handle: int) -> None:
52+
"""Close a specific file handle."""
53+
if handle in self._handles:
54+
file_instance = self._handles[handle]
55+
# Find and close the corresponding context manager
56+
for i, (ctx_mgr, opened_file) in enumerate(self._files):
57+
if opened_file is file_instance:
58+
try:
59+
ctx_mgr.__exit__(None, None, None)
60+
except Exception as e:
61+
log.warning("Error closing file handle %d: %s", handle, e)
62+
self._files.pop(i)
63+
break
64+
65+
del self._handles[handle]
66+
log.debug("Closed handle %d", handle)
67+
68+
def close_all(self) -> None:
69+
"""Close all open files."""
70+
for ctx_mgr, _ in self._files:
71+
try:
72+
ctx_mgr.__exit__(None, None, None)
73+
except Exception as e:
74+
log.warning("Error during cleanup: %s", e)
75+
76+
self._handles.clear()
77+
self._files.clear()
78+
log.debug("Closed all handles")
79+
80+
81+
# Global state
82+
DEV: Path | None = None
83+
CACHE: Path | None = None
84+
SECTOR_SIZE = DEFAULT_SECTOR_SIZE
85+
METADATA = {}
86+
87+
# Handle manager instance
88+
HANDLE_MANAGER = HandleManager()
89+
90+
91+
# These functions are now handled by the BlockCache class
92+
93+
94+
def config(key: str, val: str) -> None:
95+
"""Stores device, cache paths and parses metadata key-value pairs."""
96+
global DEV, CACHE, SECTOR_SIZE, METADATA
97+
98+
if key == "device":
99+
DEV = Path(val)
100+
elif key == "cache":
101+
CACHE = Path(val)
102+
elif key == "sector" or key == "block": # Accept both for compatibility
103+
SECTOR_SIZE = int(val)
104+
elif key == "metadata":
105+
# Parse metadata string in format "key1=value1,key2=value2"
106+
for pair in val.split(","):
107+
if "=" in pair:
108+
k, v = pair.split("=", 1)
109+
METADATA[k.strip()] = v.strip()
110+
else:
111+
# Store unknown keys in metadata
112+
METADATA[key] = val
113+
114+
115+
def config_complete() -> None:
116+
"""Validates required parameters."""
117+
global DEV, CACHE, SECTOR_SIZE, METADATA
118+
119+
if DEV is None:
120+
raise RuntimeError("device= is required")
121+
122+
# For now, just log the config - we'll build file composition later
123+
log.debug("Config: device=%s, cache=%s, sector_size=%d", DEV, CACHE, SECTOR_SIZE)
124+
125+
126+
def open(_readonly: bool) -> int:
127+
"""Opens device and returns handle ID."""
128+
mode = "rb" if _readonly else "r+b"
129+
return HANDLE_MANAGER.open_file(DEV, mode)
130+
131+
132+
def get_size(h: int) -> int:
133+
"""Get file size."""
134+
file_instance = HANDLE_MANAGER.get_file(h)
135+
return file_instance.size()
136+
137+
138+
def pread(h: int, count: int, offset: int) -> bytes:
139+
"""Read data at offset."""
140+
file_instance = HANDLE_MANAGER.get_file(h)
141+
return file_instance.pread(count, offset)
142+
143+
144+
def close(h: int) -> None:
145+
"""Close file handle."""
146+
log.debug("Backend close() called for handle %d", h)
147+
HANDLE_MANAGER.close_file(h)
148+
log.debug("Backend close() completed")
149+
150+
151+
# Optional capability functions - use duck typing
152+
def can_write(h: int) -> bool:
153+
"""Check if file supports writing."""
154+
file_instance = HANDLE_MANAGER.get_file(h)
155+
return "w" in file_instance.mode or "a" in file_instance.mode or "+" in file_instance.mode
156+
157+
158+
def can_flush(h: int) -> bool:
159+
"""Check if file supports flushing."""
160+
file_instance = HANDLE_MANAGER.get_file(h)
161+
return hasattr(file_instance, "flush")
162+
163+
164+
def can_trim(h: int) -> bool:
165+
"""Check if file supports trim operations."""
166+
file_instance = HANDLE_MANAGER.get_file(h)
167+
return hasattr(file_instance, "trim")
168+
169+
170+
def can_zero(h: int) -> bool:
171+
"""Check if file supports zero operations."""
172+
file_instance = HANDLE_MANAGER.get_file(h)
173+
return hasattr(file_instance, "zero")
174+
175+
176+
def can_fast_zero(h: int) -> bool:
177+
"""Check if file supports fast zero operations."""
178+
file_instance = HANDLE_MANAGER.get_file(h)
179+
return hasattr(file_instance, "fast_zero")
180+
181+
182+
def can_extents(h: int) -> bool:
183+
"""Check if file supports extent operations."""
184+
file_instance = HANDLE_MANAGER.get_file(h)
185+
return hasattr(file_instance, "extents")
186+
187+
188+
def is_rotational(h: int) -> bool:
189+
"""Check if underlying storage is rotational."""
190+
file_instance = HANDLE_MANAGER.get_file(h)
191+
return hasattr(file_instance, "is_rotational") and file_instance.is_rotational()
192+
193+
194+
def can_multi_conn(h: int) -> bool:
195+
"""Check if file supports multiple connections."""
196+
# For now, return False for safety
197+
return False

src/blkcache/cache/__init__.py

Whitespace-only changes.

src/blkcache/cache/cache.py

Lines changed: 0 additions & 93 deletions
This file was deleted.

src/blkcache/cache/through.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
import errno
66
from typing import Dict, Any
77

8-
from blkcache.constants import (
9-
DEFAULT_BLOCK_SIZE,
10-
)
11-
from blkcache.device import get_device_size, get_sector_size
12-
from blkcache.diskmap import DiskMap, FORMAT_VERSION
8+
from blkcache.device import get_device_size, get_sector_size, DEFAULT_BLOCK_SIZE
9+
from blkcache.ddrescue import FORMAT_VERSION
1310
from blkcache.cache.cache import Cache
1411

1512

src/blkcache/constants.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)