synology_apm_repo.sdk.dedup.dedup_file module¶
DedupFile: the sole cross-layer read contract.
Any workload — VM disk image, FS file, SaaS raw object — ultimately
reduces to a (stream_id, session_id, comp_offset) triple plus a size.
Once you have that, everything above this module (catalog, units, CLI,
TUI) only ever calls read()/stream()/export_to() — never
touches a bucket, chunk, or composition record directly. ByteRangeView
is the second shared primitive: FS’s content_dedup_id + file_size
and SaaS’s object_table.(offset, length) are the same concept (a named
sub-range of a bigger dedup file), so they share this one class instead of
each workload reinventing it.
Performance-critical property: every read()/extents() call
resolves its starting chunk-map record via CompositionRecord.entries’s
binary search — over already-known page boundaries, page-sized reads for
anything not yet cached — never a linear, one-entry-at-a-time scan.
- synology_apm_repo.sdk.dedup.dedup_file.DEFAULT_STREAM_BLOCK = 8388608¶
Shared by every real
stream_via_readcaller across this project’s ownContentSourceimplementers (DedupFile,ByteRangeView, and — one layer up, inunits/—VirtualDiskContentSource/DissectFileContentSource/_LocalFileContentSource) as their ownstream()’s default block size, so the literal is defined once instead of independently redeclared per module.
- async synology_apm_repo.sdk.dedup.dedup_file.stream_via_read(source, block=8388608)¶
Yield
(offset, bytes)blocks front-to-back via repeatedsource.read(offset, block)calls — just a loop, so any implementer whose ownread()already handles its internal chunking/extents/fragments (DedupFile’s own bucket-merged reads via_fill_data_extent,VirtualDiskContentSource’s fragment stitching, …) gets a correctstream()for free, with no separate bulk-read path to keep in sync.ContentSourceis deliberately a structuralProtocol, not an ABC, so implementers don’t need a common base class — but nothing then forced them to share this identical loop either; this closes that gap without adding one. Not used by everyContentSourceimplementer:LazyArtifact(assembled.eml/.icscontent) has its own, differentstream()that slices an already-materialized in-memory buffer instead, since its ownsizeisNoneuntil that buffer is built.Raises
ValueErrorifsource.sizeisNone— every real caller here always has a known size by the time streaming starts.
- synology_apm_repo.sdk.dedup.dedup_file.clamp_read_length(offset, length, size)¶
Resolve a
read(offset, length)request against a knownsize: fills in the default length (offsettosize) and clamps a request extending pastsizedown to what’s actually there. EveryContentSourceimplementer shares this contract: reading past the end returns fewer bytes, never an error — only a negativeoffset/lengthis left for the caller to reject before calling this.
- class synology_apm_repo.sdk.dedup.dedup_file.ExtentKind(value)¶
Bases:
EnumA file, as seen through
DedupFile.extents(), is a sequence of these three kinds.ZERO(an explicitChunkMapKind.ZEROchunk-map record) andHOLE(no record at all, i.e. a gap between records) are deliberately distinct even though both read back as zero bytes — aHOLEis a real sparse gap worth preserving as one on export; aZEROrecord is zero data explicitly recorded as such, not merely absent.- DATA = 1¶
- ZERO = 2¶
- HOLE = 3¶
- class synology_apm_repo.sdk.dedup.dedup_file.Extent(offset, length, kind, addr=None, map_num=0, repeat=0)¶
Bases:
objectOne contiguous span of a
DedupFile.addr/map_num/repeatare populated only forExtentKind.DATA— the template starting address and its1 + repeatrepetitions the span’s chunks are drawn from (ChunkAddress.advance’s carry semantics).- kind: ExtentKind¶
- addr: ChunkAddress | None = None¶
- class synology_apm_repo.sdk.dedup.dedup_file.ExportResult(bytes_written, logical_size, holes, zeros)¶
Bases:
objectThe outcome of one
export_to()call.- Variables:
- class synology_apm_repo.sdk.dedup.dedup_file.DedupFile(comp_reader, pool, comp_offset, *, size=None)¶
Bases:
objectA logical, byte-addressable file backed by one composition record.
sizeis supplied by the caller (file_meta.file_size,object_table.file_size, …) — this layer never infers it from the composition record’s own coverage, since a record’s last byte is not necessarily the file’s declared end (trailingHOLE).- property pool: Pool¶
The
Poolthis file’s chunks resolve through — needed byexport_scheduler.py’s bucket-major planning, which schedules against a sharedPool/BucketReaderCacherather than one read at a time.
- async cached_record()¶
This file’s own
CompositionRecord, fetched once and cached for the life of thisDedupFile— a cold fetch on the first call, the cached instance on every one after.Exposed (not fully private) because
CompositionRecordis a real, shared unit with this class rather than an implementation detail confined to it:units/device_pcps.pyandunits/verify_reachable.pyboth need direct access to it (the latter also viaseed_record(), to share one already-resolved record across severalDedupFile``s that address the same composition) — one documented accessor here instead of each reaching into ``._recordon its own.
- seed_record(record)¶
Sets this file’s own cached
CompositionRecorddirectly, skipping the fetchcached_record()would otherwise make — for a caller (units/verify_reachable.py) that already resolved the same composition’s record via a differentDedupFilesharing the same key, and wants everyDedupFileaddressing it to reuse that one instance rather than each re-fetching its own copy.
- async read(offset=0, length=None)¶
Read
lengthbytes starting atoffset(default: fromoffsettosize).ZERO/HOLEextents read back as zero bytes for free (bytearray’s own zero-fill). A request extending pastsizeis clamped to the bytes that actually exist (seeclamp_read_length), never zero-padded past it.- Raises:
ValueError –
offsetorlengthis negative.
- stream(block=8388608)¶
Yield
(offset, bytes)blocks front-to-back — just aread(offset, block)loop, sinceread()already handles its own chunking, so it inherits_fill_data_extent’s own bucket-merged reads for free; no separate bulk-read path to keep in sync.
- async export_to(dst, *, sparse=True, progress=None, window_entries=None, max_concurrent_opens=None, max_concurrent_reads=1, export_cache=None, dst_offset=0, create=True, executor=None)¶
Write this file to
dst.sparse=True(the default) never writesZERO/HOLEbytes at all — the output file reads back as zero there via the filesystem’s own sparse-hole support, and the two kinds are indistinguishable to a reader either way; usesparse=Falsewhen the caller needs the on-disk allocation to actually match the logical size. Seeexport_scheduler.export_tofor whatwindow_entries/max_concurrent_opens/max_concurrent_reads/export_cache/dst_offset/create/executordo.No
verify_map_crcoption here (or anywhere in the export path) — chunk-map CRC validation isverify’s job specifically (synology-apm-repo-cli verify --level full), not export’s: FULL already does an equally thorough, equally costly check, so export re-running it would only double that cost for no new information.
- view(offset, length)¶
- class synology_apm_repo.sdk.dedup.dedup_file.ByteRangeView(base, offset, length)¶
Bases:
objectA named sub-range of a
DedupFile— the same read-contract shape (size/read/stream/export_to), coordinates translated, for a workload whose “file” is really just an offset+length window into a bigger dedup file rather than its own composition record.- property base: DedupFile¶
The
DedupFilethis view windows into — needed byexport_scheduler.py’s bucket-major planning, which schedules against the base file’s ownDedupFile.pool.
- async read(offset=0, length=None)¶
See
units.base.ContentSource.read’s EOF contract — a request extending past this view’s ownsizeis clamped, never an error.
- stream(block=8388608)¶
- async export_to(dst, *, sparse=True, progress=None, window_entries=None, max_concurrent_opens=None, max_concurrent_reads=1, export_cache=None, dst_offset=0, create=True, executor=None)¶
Same as
DedupFile.export_to, windowed to this view’s own range.