@@ -254,6 +254,18 @@ BOOL is_asset_alive(NSMapTable<NSString *, ETCoreMLAsset *> *assets_in_use_map,
254254
255255 return assets;
256256}
257+
258+ NSURL * _Nullable move_to_directory (NSURL *url,
259+ NSURL *directoryURL,
260+ NSFileManager *fileManager,
261+ NSError * __autoreleasing *error) {
262+ NSURL *dstURL = [directoryURL URLByAppendingPathComponent: [NSUUID UUID ].UUIDString];
263+ if (![fileManager moveItemAtURL: url toURL: dstURL error: error]) {
264+ return nil ;
265+ }
266+ return dstURL;
267+ }
268+
257269} // namespace
258270
259271@interface ETCoreMLAssetManager () <NSFileManagerDelegate > {
@@ -299,12 +311,20 @@ - (nullable instancetype)initWithDatabase:(const std::shared_ptr<Database>&)data
299311 if (!managedAssetsDirectoryURL) {
300312 return nil ;
301313 }
302-
314+
303315 NSURL *managedTrashDirectoryURL = ::create_directory_if_needed (trashDirectoryURL, @" models" , fileManager, error);
304316 if (!managedTrashDirectoryURL) {
305317 return nil ;
306318 }
307-
319+
320+ // Remove any existing contents in the staging directory by moving them to the trash directory,
321+ // then recreate a clean staging directory for new use.
322+ move_to_directory ([assetsDirectoryURL URLByAppendingPathComponent: @" staging" ], managedTrashDirectoryURL, fileManager, nil );
323+ NSURL *managedStagingDirectoryURL = ::create_directory_if_needed (assetsDirectoryURL, @" staging" , fileManager, error);
324+ if (!managedStagingDirectoryURL) {
325+ return nil ;
326+ }
327+
308328 // If directory is empty then purge the stores
309329 if (::is_directory_empty (managedAssetsDirectoryURL, fileManager, nil )) {
310330 assetsMetaStore.impl ()->purge (ec);
@@ -315,6 +335,7 @@ - (nullable instancetype)initWithDatabase:(const std::shared_ptr<Database>&)data
315335 _assetsStore = std::move (assetsStore);
316336 _assetsMetaStore = std::move (assetsMetaStore);
317337 _assetsDirectoryURL = managedAssetsDirectoryURL;
338+ _stagingDirectoryURL = managedStagingDirectoryURL;
318339 _trashDirectoryURL = managedTrashDirectoryURL;
319340 _estimatedSizeInBytes = sizeInBytes.value ();
320341 _maxAssetsSizeInBytes = maxAssetsSizeInBytes;
@@ -349,12 +370,18 @@ - (nullable instancetype)initWithDatabaseURL:(NSURL *)databaseURL
349370- (nullable NSURL *)moveURL : (NSURL *)url
350371 toUniqueURLInDirectory : (NSURL *)directoryURL
351372 error : (NSError * __autoreleasing *)error {
352- NSURL *dstURL = [directoryURL URLByAppendingPathComponent: [NSUUID UUID ].UUIDString];
353- if (![self .fileManager moveItemAtURL: url toURL: dstURL error: error]) {
354- return nil ;
373+ return move_to_directory (url, directoryURL, self.fileManager , error);
374+ }
375+
376+ - (void )withTemporaryDirectory : (void (^)(NSURL *directoryURL))block {
377+ NSURL *dstURL = [self .stagingDirectoryURL URLByAppendingPathComponent: [NSUUID UUID ].UUIDString];
378+ block (dstURL);
379+ if (![self .fileManager fileExistsAtPath: dstURL.path]) {
380+ return ;
355381 }
356-
357- return dstURL;
382+
383+ [self moveURL: dstURL toUniqueURLInDirectory: self .trashDirectoryURL error: nil ];
384+ [self cleanupTrashDirectory ];
358385}
359386
360387- (void )cleanupAssetIfNeeded : (ETCoreMLAsset *)asset {
@@ -407,7 +434,7 @@ - (nullable ETCoreMLAsset *)_storeAssetAtURL:(NSURL *)srcURL
407434 return false ;
408435 }
409436
410- // If an asset exists move it
437+ // If an asset exists move it,
411438 [self moveURL: dstURL toUniqueURLInDirectory: self .trashDirectoryURL error: nil ];
412439
413440 // Move the asset to assets directory.
@@ -433,16 +460,25 @@ - (nullable ETCoreMLAsset *)_storeAssetAtURL:(NSURL *)srcURL
433460}
434461
435462- (void )triggerCompaction {
436- if (self.estimatedSizeInBytes < self.maxAssetsSizeInBytes ) {
437- return ;
463+ if (self.estimatedSizeInBytes >= self.maxAssetsSizeInBytes ) {
464+ __weak __typeof (self) weakSelf = self;
465+ dispatch_async (self.syncQueue , ^{
466+ NSError *localError = nil ;
467+ if (![weakSelf _compact: self .maxAssetsSizeInBytes error: &localError]) {
468+ ETCoreMLLogError (localError, " Failed to compact asset store." );
469+ }
470+ });
438471 }
439-
472+
473+ // Always clean the trash directory to ensure a minimal footprint.
474+ // The `trashQueue` is serialized, so only one cleanup will run at a time.
475+ [self cleanupTrashDirectory ];
476+ }
477+
478+ - (void )cleanupTrashDirectory {
440479 __weak __typeof (self) weakSelf = self;
441- dispatch_async (self.syncQueue , ^{
442- NSError *localError = nil ;
443- if (![weakSelf _compact: self .maxAssetsSizeInBytes error: &localError]) {
444- ETCoreMLLogError (localError, " Failed to compact asset store." );
445- }
480+ dispatch_async (self.trashQueue , ^{
481+ [weakSelf removeFilesInTrashDirectory ];
446482 });
447483}
448484
@@ -649,13 +685,7 @@ - (NSUInteger)_compact:(NSUInteger)sizeInBytes error:(NSError * __autoreleasing
649685 identifier);
650686 }
651687 }
652-
653- // Trigger cleanup.
654- __weak __typeof (self) weakSelf = self;
655- dispatch_async (self.trashQueue , ^{
656- [weakSelf removeFilesInTrashDirectory ];
657- });
658-
688+
659689 return _estimatedSizeInBytes;
660690}
661691
@@ -664,7 +694,10 @@ - (NSUInteger)compact:(NSUInteger)sizeInBytes error:(NSError * __autoreleasing *
664694 dispatch_sync (self.syncQueue , ^{
665695 result = [self _compact: sizeInBytes error: error];
666696 });
667-
697+
698+ // Always clean the trash directory to ensure a minimal footprint.
699+ // The `trashQueue` is serialized, so only one cleanup will run at a time.
700+ [self cleanupTrashDirectory ];
668701 return result;
669702}
670703
@@ -724,13 +757,7 @@ - (BOOL)_purge:(NSError * __autoreleasing *)error {
724757
725758 ::set_error_from_error_code (ec, error);
726759 // Trigger cleanup
727- if (status) {
728- __weak __typeof (self) weakSelf = self;
729- dispatch_async (self.trashQueue , ^{
730- [weakSelf removeFilesInTrashDirectory ];
731- });
732- }
733-
760+ [self cleanupTrashDirectory ];
734761 return static_cast <BOOL >(status);
735762}
736763
0 commit comments