synology_apm_repo.sdk.units.device module

DeviceProvider: VM/PC/PS workloads via copy_meta_file.

The dedup engine’s own smallest recognized unit for these workload types is still a whole disk/volume image (FORMAT-SPEC.md: copy_meta_file-layout) — this provider’s disk-image leaves are exactly that, unchanged, and every existing ref pointing at one keeps working identically. Every dedup disk-image object also gets one additional, purely-additive sibling node — “<name> (filesystem)” — built by units.content.disk_fs (Dissect-framework-based) via units.device_disk_fs, that parses the guest OS’s own filesystem (NTFS/FAT/exFAT/ext2-4/XFS/Btrfs/APFS) inside that same disk image and offers individual guest files as real, browsable/exportable RestorableUnits. When Dissect can’t recognize any filesystem on a given disk (encrypted, unsupported, or genuinely not a filesystem-bearing image), that sibling node is simply absent or shows one diagnostic leaf explaining why — the whole-image node is completely unaffected either way.

Two distinct paths, dispatched directly on ``Version.target_type`` ("VM" vs {"PC", "PS"}), never by probing which files exist — a version’s target_type is already known the moment a Version is constructed (db/copy_target_version.target_type, see catalog/version.py):

  • VM/FS: target.db → its version_table row → device_table (one row per disk device) → object_table (one row per disk image, joined on config_device_id). Handled directly in this module.

  • PC/PS: the landing directory has no target.db, only snapshot_info.json (FORMAT-SPEC.md: copy_meta_file-layout). The disk list instead comes from the destination repository’s own db/copy_target_file (version_id → fid) joined with db/file_meta (fid → path), looked up in db/file_map through the same bridge the VM path uses — the same production code path populates both tables for VM and PC/PS alike. Handled by device_pcps.PcpsDiskTree, a collaborator DeviceProvider delegates to because one physical PC/PS disk can land as several independently-registered fragment objects, not one, and PcpsDiskTree owns both the grouping rule and reassembling a group’s fragments into one disk.

Trap: a PC/PS landing directory exists for every normally-landed version too, just without target.db — so “does copy_meta_file/<dir> exist” can’t distinguish VM from PC/PS. target_type is the only reliable dispatch signal.

class synology_apm_repo.sdk.units.device.DeviceProvider(repo, version)

Bases: object

UnitProvider for one VM/PC/PS workload version.

Build one with create, never DeviceProvider(...) directly — kept as the documented construction path even though it does no I/O (_is_pcps is a pure function of version.target_type, decided once and cached rather than re-derived on every access), so root stays synchronous.

async classmethod create(repo, version)
property repo: DedupRepo

Exposed for PcpsDiskTree/DiskFsSibling, the two collaborators this class delegates to.

property version: Version

Same reason as repo.

property disk_fs: DiskFsSibling

The disk-fs sibling collaborator — exposed so PcpsDiskTree can build a PC/PS disk’s own “(filesystem)” sibling node the same way this class’s own _object_nodes does for a VM disk.

extra_ref(*extra)

Append to this provider’s own version ref rather than nesting str(self._version_ref()) as a new repo_path — the latter bakes a literal # into the middle of the ref string, which NodeRef.parse() (splitting on the first # only) then silently swallows into the version_uid segment on round-trip. Every other provider uses this same flat form via NodeRef.canonical(..., extra=...). Public — also used by PcpsDiskTree.

async close()

Release the sqlite connection(s) this provider opened.

Not merely for tidiness: an aiosqlite connection owns a background worker thread created without daemon=True, so a provider abandoned without closing keeps the interpreter alive forever in threading._shutdown. Repository.close() calls this for every provider it handed out, so ordinary callers never have to.

root()

Pure construction, no I/O — _is_pcps was already resolved in __init__. Carries no degraded/caveat attrs: whether this version’s objects actually resolve is only knowable by querying copy_target_file/file_meta/file_map, which PcpsDiskTree.object_nodes does lazily, per call, not here.

async children(node, offset=0, limit=None)
async unit(node)