Class BlobAchievementScreenshotStore
Stores the frame captured at unlock time as a blob, under
Achievements/{player}/{achievement}.png, and hands the unlock record that key.
Implements
Inherited Members
Namespace: Serenity.Achievements.Infrastructure.Stores
Assembly: Serenity.Achievements.Infrastructure.dll
Syntax
public sealed class BlobAchievementScreenshotStore : IAchievementScreenshotStore
Remarks
Engine-free: everything Unity-specific stops at the capturer that produced the bytes.
No key reaches the blob store unvalidated. Keys arrive from unlock records, which the project's store may have loaded from a file a player can edit, so every one is parsed by AchievementScreenshotKey and the resolved path is re-checked against the anchor before any read, write or delete. A key that fails either check is refused and reported once — never followed.
IBlobStore is addressed by key and cannot enumerate one, so "delete everything this player has" is answered from an index blob this store maintains beside the screenshots — one relative key per line. Rebuilding that list by asking the store about every declared achievement would miss the screenshots of achievements a later build removed, which are exactly the ones a reset has to clear. The index is read-modify-written under an AsyncMutex: two unlocks in the same frame are two concurrent saves, and an unserialized read-modify-write loses one of them, orphaning a PNG no reset can find.
The index entry is written before the PNG, and removed best-effort if the PNG write then fails. The two orderings fail differently and only one is safe: an index naming a blob that is not there costs a delete that finds nothing, while a blob no index names is an orphan on the player's disk that no reset will ever remove.
Nothing here throws at the caller except cancellation the caller asked for: a reset that failed to delete a file must not abort the reset, and an unlock must not fail because its screenshot could not be written. A failed write reports no key, so the record simply carries no screenshot.
Constructors
BlobAchievementScreenshotStore(IBlobStore, string, ILogService)
Initializes a new instance over an already-resolved blob store.
Declaration
public BlobAchievementScreenshotStore(IBlobStore blobStore, string rootDirectory, ILogService logService = null)
Parameters
| Type | Name | Description |
|---|---|---|
| IBlobStore | blobStore | The blob store the bytes are written to and read back from. |
| string | rootDirectory | The directory the backend anchors its keys under. May be blank when the backend keys are already relative. |
| ILogService | logService | Service used to report a failed or refused operation. May be |
Exceptions
| Type | Condition |
|---|---|
| ArgumentNullException | Thrown when |
BlobAchievementScreenshotStore(Func<IBlobStore>, string, ILogService)
Initializes a new instance over a blob store that may not be installed yet.
Declaration
public BlobAchievementScreenshotStore(Func<IBlobStore> resolveBlobStore, string rootDirectory, ILogService logService = null)
Parameters
| Type | Name | Description |
|---|---|---|
| Func<IBlobStore> | resolveBlobStore | Resolves the blob store, retried until it answers. |
| string | rootDirectory | The directory the backend anchors its keys under. |
| ILogService | logService | Service used to report a failed or refused operation. May be |
Remarks
The resolver is re-asked on every operation rather than captured once, so a project whose pipeline profile installs achievements before persistence still stores screenshots from the moment the backend arrives — a captured null would switch them off for the whole session.
Exceptions
| Type | Condition |
|---|---|
| ArgumentNullException | Thrown when |
Methods
DeleteAllAsync(PlayerId, CancellationToken)
Declaration
public Task DeleteAllAsync(PlayerId player, CancellationToken cancellationToken)
Parameters
| Type | Name | Description |
|---|---|---|
| PlayerId | player | |
| CancellationToken | cancellationToken |
Returns
| Type | Description |
|---|---|
| Task |
SaveAsync(PlayerId, AchievementId, byte[], CancellationToken)
Declaration
public Task<string> SaveAsync(PlayerId player, AchievementId achievement, byte[] pngBytes, CancellationToken cancellationToken)
Parameters
| Type | Name | Description |
|---|---|---|
| PlayerId | player | |
| AchievementId | achievement | |
| byte[] | pngBytes | |
| CancellationToken | cancellationToken |
Returns
| Type | Description |
|---|---|
| Task<string> |
TryReadAsync(string, CancellationToken)
Reads back the bytes stored under a key, or null when nothing could be read.
Declaration
public Task<byte[]> TryReadAsync(string storageKey, CancellationToken cancellationToken)
Parameters
| Type | Name | Description |
|---|---|---|
| string | storageKey | The key an unlock record carries. |
| CancellationToken | cancellationToken | Token used to cancel the operation. |
Returns
| Type | Description |
|---|---|
| Task<byte[]> |