Skip to content

Commit 34f1e01

Browse files
committed
feat: Enhance blob management by normalizing blob IDs and restoring image paths in SceneManager
1 parent e949a6e commit 34f1e01

3 files changed

Lines changed: 71 additions & 6 deletions

File tree

client/lib/core/services/blob_manager.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,13 @@ class FlutterAppBlobManager implements IBlobManager {
8484
_deleteFilesInDirectory(Directory(_blobsDir));
8585
}
8686

87-
String _makeHashDirName(String blobID) => path.join(_blobsDir, blobID.hashCode.toRadixString(16).padLeft(2, '0'));
87+
String _makeHashDirName(String blobID) {
88+
final normalizedBlobID = blobID.replaceAll('-', '').toLowerCase();
89+
final bucketName = normalizedBlobID.length >= 2
90+
? normalizedBlobID.substring(0, 2)
91+
: normalizedBlobID.padRight(2, '0');
92+
return path.join(_blobsDir, bucketName);
93+
}
8894

8995
Future<void> _deleteFilesInDirectory(Directory directory) async {
9096
Stream<FileSystemEntity> entities = directory.list(recursive: true, followLinks: false);

client/lib/core/services/scene_manager_impl.dart

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,23 @@ class SceneManagerImpl extends ISceneManager {
109109
logger?.e('Failed to find current scene!');
110110
throw KeyNotFoundException(message: 'There was no current scene!');
111111
}
112-
return SceneEntity.fromMap(currentRecord.key, currentRecord.value);
112+
return _restoreSceneImagePath(tx, SceneEntity.fromMap(currentRecord.key, currentRecord.value));
113+
}
114+
115+
Future<SceneEntity> _restoreSceneImagePath(Transaction tx, SceneEntity scene) async {
116+
final imageID = scene.imageID?.trim();
117+
if (imageID == null || imageID.isEmpty) {
118+
return scene;
119+
}
120+
121+
final resolvedImagePath = _blobManager.getPath(imageID);
122+
if (scene.imagePath == resolvedImagePath) {
123+
return scene;
124+
}
125+
126+
final store = stringMapStoreFactory.store(StoreNames.scenes);
127+
await store.record(scene.id).update(tx, {SceneEntity.kImagePathFieldName: resolvedImagePath});
128+
return scene.copyWith(imagePath: resolvedImagePath);
113129
}
114130

115131
Future<String> _createBlobFromFile(String imagePath) async {
@@ -161,7 +177,7 @@ class SceneManagerImpl extends ISceneManager {
161177
} else {
162178
final store = stringMapStoreFactory.store(StoreNames.scenes);
163179
final map = (await store.record(key).get(tx))!;
164-
return SceneEntity.fromMap(key, map);
180+
return _restoreSceneImagePath(tx, SceneEntity.fromMap(key, map));
165181
}
166182
}
167183

@@ -173,7 +189,10 @@ class SceneManagerImpl extends ISceneManager {
173189
} else {
174190
final store = stringMapStoreFactory.store(StoreNames.scenes);
175191
final records = await store.find(tx);
176-
final scenes = records.map((record) => SceneEntity.fromMap(record.key, record.value)).toList();
192+
final scenes = <SceneEntity>[];
193+
for (final record in records) {
194+
scenes.add(await _restoreSceneImagePath(tx, SceneEntity.fromMap(record.key, record.value)));
195+
}
177196
return scenes;
178197
}
179198
}
@@ -229,7 +248,7 @@ class SceneManagerImpl extends ISceneManager {
229248
throw KeyNotFoundException(message: 'Failed to found current scene ID `$newSceneID`');
230249
}
231250
final fromScene = _current;
232-
final toScene = SceneEntity.fromMap(newSceneID, newCurrentRecord);
251+
final toScene = await _restoreSceneImagePath(tx, SceneEntity.fromMap(newSceneID, newCurrentRecord));
233252
_current = toScene;
234253
_globalEventBus.fire(CurrentSceneChangedEvent(fromScene, toScene));
235254
return toScene;
@@ -350,7 +369,7 @@ class SceneManagerImpl extends ISceneManager {
350369
if (cancelToken?.isCancelled == true) {
351370
throw Exception('Operation cancelled');
352371
}
353-
final scene = SceneEntity.fromMap(record.key, record.value);
372+
final scene = await _restoreSceneImagePath(tx, SceneEntity.fromMap(record.key, record.value));
354373
final accessEpoch = scene.lastAccessTime.millisecondsSinceEpoch;
355374
if (accessEpoch > lastAccessEpoch) {
356375
lastAccessEpoch = accessEpoch;

client/test/core/services/scene_manager_impl_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,37 @@ void main() {
101101
expect(persisted.imagePath, isNull);
102102
expect(blobManager.deletedBlobIDs, contains('fake-blob-2'));
103103
});
104+
105+
test('initialize restores image path from image id on next launch', () async {
106+
final db = await databaseFactoryMemory.openDatabase('rehydrate-${databaseId++}.db');
107+
final firstBus = EventBus();
108+
final firstBlobManager = PrefixBlobManager('/container-a');
109+
final firstManager = SceneManagerImpl(FakeGettext(), db, firstBus, firstBlobManager, clock: TestClock());
110+
await firstManager.initialize(StubGroupManager(), StubDeviceManager());
111+
112+
final created = await firstManager.create(name: 'Custom', notes: 'n', imagePath: null);
113+
expect(created.imagePath, isNull);
114+
115+
final imageFile = await _createTempImageFile('rehydrate-image');
116+
addTearDown(() async => imageFile.parent.delete(recursive: true));
117+
118+
final updatedWithImage = await firstManager.update(
119+
id: created.id,
120+
name: created.name,
121+
notes: created.notes,
122+
imagePath: imageFile.path,
123+
);
124+
expect(updatedWithImage.imageID, equals('fake-blob-2'));
125+
126+
final secondBus = EventBus();
127+
final secondBlobManager = PrefixBlobManager('/container-b');
128+
final secondManager = SceneManagerImpl(FakeGettext(), db, secondBus, secondBlobManager, clock: TestClock());
129+
await secondManager.initialize(StubGroupManager(), StubDeviceManager());
130+
131+
final reloaded = await secondManager.single(created.id);
132+
expect(reloaded.imageID, equals('fake-blob-2'));
133+
expect(reloaded.imagePath, equals('/container-b/fake-blob-2'));
134+
});
104135
});
105136
}
106137

@@ -110,3 +141,12 @@ Future<File> _createTempImageFile(String contents) async {
110141
await file.writeAsString(contents);
111142
return file;
112143
}
144+
145+
class PrefixBlobManager extends StubBlobManager {
146+
PrefixBlobManager(this.prefix);
147+
148+
final String prefix;
149+
150+
@override
151+
String getPath(String blobID) => '$prefix/$blobID';
152+
}

0 commit comments

Comments
 (0)