0
# Context Classes
1
2
Storage backend implementations that handle block-level read, program, erase, and sync operations for different storage targets. Context classes provide the abstraction layer between LittleFS and physical storage, enabling support for memory buffers, disk devices, and custom storage implementations.
3
4
## Capabilities
5
6
### UserContext Class
7
8
Basic memory-based context implementation that stores filesystem data in a Python bytearray. This is the default context used for most applications and provides a simple in-memory filesystem suitable for creating filesystem images.
9
10
```python { .api }
11
class UserContext:
12
def __init__(self, buffsize: int) -> None:
13
"""
14
Initialize memory-based storage context.
15
16
Parameters:
17
- buffsize: int, size of memory buffer in bytes
18
19
The buffer is initialized with 0xFF bytes (erased flash state).
20
"""
21
22
def read(self, cfg: LFSConfig, block: int, off: int, size: int) -> bytearray:
23
"""
24
Read data from storage.
25
26
Parameters:
27
- cfg: LFSConfig, filesystem configuration
28
- block: int, block number to read from
29
- off: int, offset within block
30
- size: int, number of bytes to read
31
32
Returns:
33
bytearray: Data read from storage
34
"""
35
36
def prog(self, cfg: LFSConfig, block: int, off: int, data: bytes) -> int:
37
"""
38
Program (write) data to storage.
39
40
Parameters:
41
- cfg: LFSConfig, filesystem configuration
42
- block: int, block number to write to
43
- off: int, offset within block
44
- data: bytes, data to write
45
46
Returns:
47
int: 0 on success
48
"""
49
50
def erase(self, cfg: LFSConfig, block: int) -> int:
51
"""
52
Erase a block (set all bytes to 0xFF).
53
54
Parameters:
55
- cfg: LFSConfig, filesystem configuration
56
- block: int, block number to erase
57
58
Returns:
59
int: 0 on success
60
"""
61
62
def sync(self, cfg: LFSConfig) -> int:
63
"""
64
Sync cached data to storage.
65
66
Parameters:
67
- cfg: LFSConfig, filesystem configuration
68
69
Returns:
70
int: 0 on success
71
72
For memory context, this is a no-op since data is immediately available.
73
"""
74
75
@property
76
def buffer(self) -> bytearray:
77
"""Access to the underlying memory buffer."""
78
```
79
80
### UserContextWinDisk Class
81
82
Windows disk-based context implementation that provides direct access to Windows disk devices. This context enables LittleFS operations on physical drives, partitions, or disk image files using Windows Win32 APIs.
83
84
```python { .api }
85
class UserContextWinDisk(UserContext):
86
def __init__(self, disk_path: str) -> None:
87
"""
88
Initialize Windows disk-based storage context.
89
90
Parameters:
91
- disk_path: str, Windows device path (e.g., '\\\\.\\PhysicalDrive0', '\\\\.\\C:', 'image.bin')
92
93
Requires:
94
- Windows platform
95
- pywin32 package installed
96
- Appropriate permissions for disk access
97
98
Raises:
99
ImportError: If pywin32 is not available
100
IOError: If disk cannot be opened
101
"""
102
103
def read(self, cfg: LFSConfig, block: int, off: int, size: int) -> bytearray:
104
"""
105
Read data from Windows disk device.
106
107
Uses Win32 SetFilePointer and ReadFile APIs for direct disk access.
108
"""
109
110
def prog(self, cfg: LFSConfig, block: int, off: int, data: bytes) -> int:
111
"""
112
Program data to Windows disk device.
113
114
Uses Win32 SetFilePointer and WriteFile APIs for direct disk access.
115
"""
116
117
def erase(self, cfg: LFSConfig, block: int) -> int:
118
"""
119
Erase block on Windows disk device by writing 0xFF bytes.
120
121
Uses Win32 SetFilePointer and WriteFile APIs.
122
"""
123
124
def sync(self, cfg: LFSConfig) -> int:
125
"""
126
Sync data to Windows disk device.
127
128
Uses Win32 FlushFileBuffers API to ensure data is written to storage.
129
"""
130
131
def __del__(self):
132
"""Cleanup Windows handle on destruction."""
133
```
134
135
## Context Integration
136
137
Context objects are integrated with LFSConfig and automatically used by filesystem operations:
138
139
```python { .api }
140
class LFSConfig:
141
@property
142
def user_context(self) -> UserContext:
143
"""Access to the associated user context."""
144
145
# Context is automatically used for all filesystem operations
146
# through callback functions registered with the C library
147
```
148
149
## Usage Examples
150
151
### Memory Context Usage
152
153
```python
154
from littlefs import LittleFS, UserContext
155
156
# Create custom-sized memory context
157
context = UserContext(buffsize=2*1024*1024) # 2MB buffer
158
159
# Use with LittleFS
160
fs = LittleFS(context=context, block_size=4096, block_count=512)
161
162
# Perform filesystem operations
163
with fs.open('test.txt', 'w') as f:
164
f.write('Hello, World!')
165
166
# Access raw buffer data
167
raw_data = context.buffer
168
print(f"Buffer size: {len(raw_data)} bytes")
169
170
# Export filesystem image
171
with open('filesystem.bin', 'wb') as f:
172
f.write(raw_data)
173
```
174
175
### Windows Disk Context Usage
176
177
```python
178
from littlefs import LittleFS, UserContextWinDisk
179
180
# Access physical drive (requires admin privileges)
181
try:
182
disk_context = UserContextWinDisk('\\\\.\\PhysicalDrive1')
183
fs = LittleFS(context=disk_context, block_size=4096, mount=False)
184
185
# Format the drive with LittleFS
186
fs.format()
187
fs.mount()
188
189
# Use filesystem normally
190
fs.mkdir('data')
191
with fs.open('data/readme.txt', 'w') as f:
192
f.write('LittleFS on physical drive!')
193
194
except ImportError:
195
print("pywin32 not available")
196
except IOError as e:
197
print(f"Cannot access disk: {e}")
198
```
199
200
### Disk Image File Context
201
202
```python
203
# Create disk image file first
204
with open('disk_image.bin', 'wb') as f:
205
f.write(b'\\xff' * (1024 * 1024)) # 1MB blank image
206
207
# Use Windows context with file path
208
disk_context = UserContextWinDisk('disk_image.bin')
209
fs = LittleFS(context=disk_context, block_size=512, block_count=2048)
210
211
# Use filesystem
212
with fs.open('config.json', 'w') as f:
213
f.write('{"version": "1.0"}')
214
215
# Changes are written directly to disk_image.bin
216
fs.unmount()
217
```
218
219
### Custom Context Implementation
220
221
```python
222
import logging
223
224
class LoggingContext(UserContext):
225
"""Context that logs all operations for debugging."""
226
227
def __init__(self, buffsize: int):
228
super().__init__(buffsize)
229
self.logger = logging.getLogger(__name__)
230
231
def read(self, cfg, block, off, size):
232
self.logger.debug(f"READ: block={block}, off={off}, size={size}")
233
return super().read(cfg, block, off, size)
234
235
def prog(self, cfg, block, off, data):
236
self.logger.debug(f"PROG: block={block}, off={off}, size={len(data)}")
237
return super().prog(cfg, block, off, data)
238
239
def erase(self, cfg, block):
240
self.logger.debug(f"ERASE: block={block}")
241
return super().erase(cfg, block)
242
243
def sync(self, cfg):
244
self.logger.debug("SYNC")
245
return super().sync(cfg)
246
247
# Use custom context
248
logging.basicConfig(level=logging.DEBUG)
249
ctx = LoggingContext(64*1024) # 64KB with logging
250
fs = LittleFS(context=ctx, block_size=512, block_count=128)
251
252
# All operations will be logged
253
with fs.open('debug.txt', 'w') as f:
254
f.write('This will generate read/prog/erase logs')
255
```
256
257
### Context Buffer Analysis
258
259
```python
260
# Analyze raw filesystem data
261
fs = LittleFS(block_size=512, block_count=256)
262
263
# Create some files
264
with fs.open('file1.txt', 'w') as f:
265
f.write('Content of file 1')
266
267
with fs.open('file2.txt', 'w') as f:
268
f.write('Content of file 2')
269
270
# Examine raw buffer
271
buffer = fs.context.buffer
272
print(f"Total buffer size: {len(buffer)} bytes")
273
274
# Find used vs unused space
275
used_blocks = fs.used_block_count
276
total_blocks = fs.block_count
277
block_size = 512
278
279
print(f"Used blocks: {used_blocks}/{total_blocks}")
280
print(f"Used space: {used_blocks * block_size} bytes")
281
print(f"Free space: {(total_blocks - used_blocks) * block_size} bytes")
282
283
# Look for filesystem signatures (first few bytes often contain metadata)
284
header = buffer[:64]
285
print(f"Filesystem header: {header[:16].hex()}")
286
287
# Find erased regions (0xFF bytes indicate unused flash)
288
erased_start = None
289
for i, byte in enumerate(buffer):
290
if byte == 0xff:
291
if erased_start is None:
292
erased_start = i
293
else:
294
if erased_start is not None:
295
size = i - erased_start
296
if size > 512: # Only report significant erased regions
297
print(f"Erased region: {erased_start}-{i} ({size} bytes)")
298
erased_start = None
299
```
300
301
### Performance Context
302
303
```python
304
import time
305
306
class PerformanceContext(UserContext):
307
"""Context that measures performance metrics."""
308
309
def __init__(self, buffsize: int):
310
super().__init__(buffsize)
311
self.read_count = 0
312
self.write_count = 0
313
self.erase_count = 0
314
self.read_bytes = 0
315
self.write_bytes = 0
316
self.read_time = 0.0
317
self.write_time = 0.0
318
319
def read(self, cfg, block, off, size):
320
start = time.time()
321
result = super().read(cfg, block, off, size)
322
self.read_time += time.time() - start
323
self.read_count += 1
324
self.read_bytes += size
325
return result
326
327
def prog(self, cfg, block, off, data):
328
start = time.time()
329
result = super().prog(cfg, block, off, data)
330
self.write_time += time.time() - start
331
self.write_count += 1
332
self.write_bytes += len(data)
333
return result
334
335
def erase(self, cfg, block):
336
self.erase_count += 1
337
return super().erase(cfg, block)
338
339
def get_stats(self):
340
return {
341
'reads': self.read_count,
342
'writes': self.write_count,
343
'erases': self.erase_count,
344
'read_bytes': self.read_bytes,
345
'write_bytes': self.write_bytes,
346
'read_time': self.read_time,
347
'write_time': self.write_time,
348
'read_throughput': self.read_bytes / self.read_time if self.read_time > 0 else 0,
349
'write_throughput': self.write_bytes / self.write_time if self.write_time > 0 else 0
350
}
351
352
# Measure filesystem performance
353
perf_ctx = PerformanceContext(512*1024) # 512KB
354
fs = LittleFS(context=perf_ctx, block_size=4096, block_count=128)
355
356
# Perform operations
357
start_time = time.time()
358
359
for i in range(100):
360
with fs.open(f'file_{i:03d}.txt', 'w') as f:
361
f.write(f'Content for file {i}\\n' * 10)
362
363
total_time = time.time() - start_time
364
365
# Report performance metrics
366
stats = perf_ctx.get_stats()
367
print(f"Total time: {total_time:.3f}s")
368
print(f"Operations: {stats['reads']} reads, {stats['writes']} writes, {stats['erases']} erases")
369
print(f"Data: {stats['read_bytes']} bytes read, {stats['write_bytes']} bytes written")
370
print(f"Throughput: {stats['read_throughput']:.0f} B/s read, {stats['write_throughput']:.0f} B/s write")
371
print(f"Average: {100/total_time:.1f} files/second")
372
```