[vlc-commits] [Git][videolan/vlc][master] macosx: library: use snapshots for grouped library updates

Steve Lhomme (@robUx4) gitlab at videolan.org
Fri Sep 18 01:59:12 UTC 2026



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
95f5eb92 by Serhii Bykov at 2026-09-18T01:47:30+00:00
macosx: library: use snapshots for grouped library updates

- - - - -


6 changed files:

- modules/gui/macosx/library/VLCLibraryAbstractGroupingDataSource.h
- modules/gui/macosx/library/VLCLibraryAbstractGroupingDataSource.m
- modules/gui/macosx/library/groups-library/VLCLibraryGroupsDataSource.h
- modules/gui/macosx/library/groups-library/VLCLibraryGroupsDataSource.m
- modules/gui/macosx/library/video-library/VLCLibraryShowsDataSource.h
- modules/gui/macosx/library/video-library/VLCLibraryShowsDataSource.m


Changes:

=====================================
modules/gui/macosx/library/VLCLibraryAbstractGroupingDataSource.h
=====================================
@@ -28,11 +28,12 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+ at class VLCLibraryModel;
 @protocol VLCMediaLibraryItemProtocol;
 
 @interface VLCLibraryAbstractGroupingDataSource : NSObject<VLCLibraryMasterDetailViewTableViewDataSource, VLCLibraryCollectionViewDataSource>
 
- at property (readonly) NSArray<id<VLCMediaLibraryItemProtocol>> *backingArray;
+ at property (readwrite, weak) VLCLibraryModel *libraryModel;
 
 @property (readwrite, weak) NSCollectionView *collectionView;
 @property (readwrite, weak) NSTableView *masterTableView;
@@ -40,9 +41,12 @@ NS_ASSUME_NONNULL_BEGIN
 @property (readwrite, weak, nullable) id<VLCLibraryGroupHeaderDelegate> headerDelegate;
 
 // Required!
+ at property (readonly) NSArray<id<VLCMediaLibraryItemProtocol>> *backingArray;
 @property (readonly, nullable) NSString *dataSourceTypeDisplayString;
 
+- (NSArray<id<VLCMediaLibraryItemProtocol>> *)sourceBackingArray;
 - (void)reloadData;
+- (void)applySnapshotForChangedItemID:(int64_t)libraryID isDeletion:(BOOL)isDeletion;
 
 @end
 


=====================================
modules/gui/macosx/library/VLCLibraryAbstractGroupingDataSource.m
=====================================
@@ -32,9 +32,22 @@
 #import "library/VLCLibraryDataTypes.h"
 #import "library/VLCLibraryRepresentedItem.h"
 
+ at interface VLCLibraryAbstractGroupingDataSource ()
+
+ at property (readwrite, copy) NSArray<id<VLCMediaLibraryItemProtocol>> *backingArraySnapshot;
+
+ at end
+
 @implementation VLCLibraryAbstractGroupingDataSource
 
+#pragma mark - Override points
+
 - (NSArray<id<VLCMediaLibraryItemProtocol>> *)backingArray
+{
+    return self.backingArraySnapshot ?: @[];
+}
+
+- (NSArray<id<VLCMediaLibraryItemProtocol>> *)sourceBackingArray
 {
     [self doesNotRecognizeSelector:_cmd];
     return nil;
@@ -46,8 +59,11 @@
     return VLCMediaLibraryParentGroupTypeUnknown;
 }
 
+#pragma mark - Shared entry points
+
 - (void)reloadData
 {
+    self.backingArraySnapshot = [self sourceBackingArray] ?: @[];
     [(VLCLibraryCollectionViewFlowLayout *)self.collectionView.collectionViewLayout resetLayout];
     [self.masterTableView reloadData];
     [self.detailTableView reloadData];
@@ -55,6 +71,78 @@
     [self updateHeaderInTableView:self.detailTableView forMasterSelection:self.masterTableView];
 }
 
+- (void)setLibraryModel:(VLCLibraryModel *)libraryModel
+{
+    if (_libraryModel == libraryModel) {
+        return;
+    }
+
+    _libraryModel = libraryModel;
+    [self reloadData];
+}
+
+- (void)applySnapshotForChangedItemID:(const int64_t)libraryID isDeletion:(BOOL)isDeletion
+{
+    NSArray<id<VLCMediaLibraryItemProtocol>> * const oldSnapshot = self.backingArraySnapshot ?: @[];
+    NSArray<id<VLCMediaLibraryItemProtocol>> * const newSnapshot = [self sourceBackingArray] ?: @[];
+    const NSInteger selectedRow = self.masterTableView.selectedRow;
+    const int64_t selectedItemID =
+        selectedRow > -1 && selectedRow < (NSInteger)oldSnapshot.count
+            ? oldSnapshot[selectedRow].libraryID
+            : -1;
+
+    NSArray<NSNumber *> * const oldIDs = [self backingArrayIDsForSnapshot:oldSnapshot];
+    NSArray<NSNumber *> * const newIDs = [self backingArrayIDsForSnapshot:newSnapshot];
+    const NSUInteger oldIndex = [oldIDs indexOfObject:@(libraryID)];
+    const NSUInteger newIndex = [newIDs indexOfObject:@(libraryID)];
+
+    if (isDeletion) {
+        const BOOL canDeleteSection =
+            oldIndex != NSNotFound &&
+            newIndex == NSNotFound &&
+            oldSnapshot.count == newSnapshot.count + 1 &&
+            [self newIDs:newIDs equalOldIDs:oldIDs withoutID:@(libraryID)];
+
+        if (!canDeleteSection) {
+            [self reloadViewsWithSnapshot:newSnapshot
+                   preservingSelectionRow:selectedRow
+                           selectedItemID:selectedItemID];
+            return;
+        }
+
+        self.backingArraySnapshot = newSnapshot;
+        [(VLCLibraryCollectionViewFlowLayout *)self.collectionView.collectionViewLayout resetLayout];
+        [self.collectionView performBatchUpdates:^{
+            [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:oldIndex]];
+        } completionHandler:nil];
+        [self reloadTableViewsPreservingSelectionItemID:selectedItemID
+                                            fallbackRow:selectedRow];
+        return;
+    }
+
+    const BOOL canReloadSection =
+        oldIndex != NSNotFound &&
+        newIndex != NSNotFound &&
+        oldIndex == newIndex &&
+        [oldIDs isEqualToArray:newIDs];
+
+    if (!canReloadSection) {
+        [self reloadViewsWithSnapshot:newSnapshot
+               preservingSelectionRow:selectedRow
+                       selectedItemID:selectedItemID];
+        return;
+    }
+
+    self.backingArraySnapshot = newSnapshot;
+    [self.collectionView performBatchUpdates:^{
+        [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:newIndex]];
+    } completionHandler:nil];
+    [self reloadTableViewsPreservingSelectionItemID:selectedItemID
+                                        fallbackRow:selectedRow];
+}
+
+#pragma mark - Utilities
+
 - (NSUInteger)indexOfMediaItem:(const int64_t)libraryId inArray:(NSArray const *)array
 {
     return [array indexOfObjectPassingTest:^BOOL(const id<VLCMediaLibraryItemProtocol> findItem,
@@ -65,7 +153,20 @@
     }];
 }
 
-#pragma mark - table view data source and delegation
+- (NSInteger)rowForLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
+{
+    if (libraryItem == nil) {
+        return NSNotFound;
+    }
+    return [self indexOfMediaItem:libraryItem.libraryID inArray:self.backingArray];
+}
+
+- (NSInteger)rowForLibraryItemID:(const int64_t)libraryID
+{
+    return [self indexOfMediaItem:libraryID inArray:self.backingArray];
+}
+
+#pragma mark - Table view data source and delegation
 
 - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
 {
@@ -131,15 +232,7 @@
                                    fallbackDetail:fallbackDetail];
 }
 
-- (NSInteger)rowForLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
-{
-    if (libraryItem == nil) {
-        return NSNotFound;
-    }
-    return [self indexOfMediaItem:libraryItem.libraryID inArray:self.backingArray];
-}
-
-# pragma mark - collection view data source and delegation
+#pragma mark - Collection view data source and delegation
 
 - (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView
 {
@@ -256,4 +349,77 @@ viewForSupplementaryElementOfKind:(NSCollectionViewSupplementaryElementKind)kind
     return VLCLibraryCollectionViewMediaItemSupplementaryDetailViewKind;
 }
 
+#pragma mark - Snapshot helpers
+
+- (NSArray<NSNumber *> *)backingArrayIDsForSnapshot:(NSArray<id<VLCMediaLibraryItemProtocol>> *)snapshot
+{
+    NSMutableArray<NSNumber *> * const snapshotIDs = [NSMutableArray arrayWithCapacity:snapshot.count];
+
+    for (id<VLCMediaLibraryItemProtocol> const item in snapshot) {
+        [snapshotIDs addObject:@(item.libraryID)];
+    }
+
+    return snapshotIDs.copy;
+}
+
+- (BOOL)newIDs:(NSArray<NSNumber *> *)newIDs
+   equalOldIDs:(NSArray<NSNumber *> *)oldIDs
+     withoutID:(NSNumber * _Nullable)removedID
+{
+    NSMutableArray<NSNumber *> * const filteredOldIDs =
+        [NSMutableArray arrayWithCapacity:oldIDs.count];
+
+    for (NSNumber * const itemID in oldIDs) {
+        if (removedID != nil && [itemID isEqualToNumber:removedID]) {
+            continue;
+        }
+        [filteredOldIDs addObject:itemID];
+    }
+
+    return [filteredOldIDs isEqualToArray:newIDs];
+}
+
+- (void)reloadViewsWithSnapshot:(NSArray<id<VLCMediaLibraryItemProtocol>> *)snapshot
+         preservingSelectionRow:(const NSInteger)selectedRow
+                 selectedItemID:(const int64_t)selectedItemID
+{
+    self.backingArraySnapshot = snapshot;
+    [(VLCLibraryCollectionViewFlowLayout *)self.collectionView.collectionViewLayout resetLayout];
+    [self.collectionView reloadData];
+    [self reloadTableViewsPreservingSelectionItemID:selectedItemID
+                                        fallbackRow:selectedRow];
+}
+
+- (void)reloadTableViewsPreservingSelectionItemID:(const int64_t)selectedItemID
+                                      fallbackRow:(const NSInteger)selectedRow
+{
+    [self.masterTableView reloadData];
+    [self restoreMasterSelectionUsingItemID:selectedItemID fallbackRow:selectedRow];
+    [self.detailTableView reloadData];
+    [self updateHeaderInTableView:self.detailTableView forMasterSelection:self.masterTableView];
+}
+
+- (void)restoreMasterSelectionUsingItemID:(const int64_t)itemID
+                              fallbackRow:(const NSInteger)fallbackRow
+{
+    NSInteger rowToSelect = NSNotFound;
+
+    if (itemID != -1) {
+        rowToSelect = [self rowForLibraryItemID:itemID];
+    }
+
+    if (rowToSelect == NSNotFound && fallbackRow != NSNotFound) {
+        if (self.backingArray.count == 0) {
+            rowToSelect = NSNotFound;
+        } else {
+            rowToSelect = MIN(fallbackRow, (NSInteger)self.backingArray.count - 1);
+        }
+    }
+
+    if (rowToSelect != NSNotFound) {
+        [self.masterTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:rowToSelect]
+                          byExtendingSelection:NO];
+    }
+}
+
 @end


=====================================
modules/gui/macosx/library/groups-library/VLCLibraryGroupsDataSource.h
=====================================
@@ -26,12 +26,8 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
- at class VLCLibraryModel;
-
 @interface VLCLibraryGroupsDataSource : VLCLibraryAbstractGroupingDataSource
 
- at property (readwrite, weak) VLCLibraryModel *libraryModel;
-
 @end
 
 NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/library/groups-library/VLCLibraryGroupsDataSource.m
=====================================
@@ -70,7 +70,7 @@
     [self reloadData];
 }
 
-- (NSArray<VLCMediaLibraryGroup *> *)backingArray
+- (NSArray<VLCMediaLibraryGroup *> *)sourceBackingArray
 {
     return self.libraryModel.listOfGroups;
 }
@@ -83,45 +83,13 @@
 - (void)libraryModelGroupUpdated:(NSNotification *)notification
 {
     VLCMediaLibraryGroup * const group = notification.object;
-    NSIndexPath * const indexPath = [self indexPathForLibraryItem:group];
-
-    if (indexPath != nil) {
-        [self.collectionView reloadItemsAtIndexPaths:[NSSet setWithObject:indexPath]];
-    }
-
-    const NSInteger rowIndex = [self rowForLibraryItem:group];
-    if (rowIndex != NSNotFound) {
-        const NSInteger selectedMasterRow = self.masterTableView.selectedRow;
-        [self.masterTableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex]
-                                        columnIndexes:[NSIndexSet indexSetWithIndex:0]];
-
-        // Check, if the selected row was for the group that has been reloaded, if the selection in
-        // the master table view has changed after reloading the target index. In this case, we want
-        // to reselect. If the selection has been maintained then we need to reload the detail table
-        // view.
-        if (rowIndex == selectedMasterRow && self.masterTableView.selectedRow != selectedMasterRow) {
-            [self.masterTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedMasterRow]
-                              byExtendingSelection:NO];
-        } else {
-            [self.detailTableView reloadData];
-        }
-    }
+    [self applySnapshotForChangedItemID:group.libraryID isDeletion:NO];
 }
 
 - (void)libraryModelGroupDeleted:(NSNotification *)notification
 {
     VLCMediaLibraryGroup * const group = notification.object;
-    NSIndexPath * const indexPath = [self indexPathForLibraryItem:group];
-
-    if (indexPath != nil) {
-        [self.collectionView deleteItemsAtIndexPaths:[NSSet setWithObject:indexPath]];
-    }
-
-    const NSInteger rowIndex = [self rowForLibraryItem:group];
-    if (rowIndex != NSNotFound) {
-        [self.masterTableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:rowIndex]
-                                    withAnimation:NSTableViewAnimationEffectFade];
-    }
+    [self applySnapshotForChangedItemID:group.libraryID isDeletion:YES];
 }
 
 - (NSString *)dataSourceTypeDisplayString


=====================================
modules/gui/macosx/library/video-library/VLCLibraryShowsDataSource.h
=====================================
@@ -27,12 +27,8 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
- at class VLCLibraryModel;
-
 @interface VLCLibraryShowsDataSource : VLCLibraryAbstractGroupingDataSource <VLCLibraryConnectableDataSource>
 
- at property (readwrite, weak) VLCLibraryModel *libraryModel;
-
 @end
 
 NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/library/video-library/VLCLibraryShowsDataSource.m
=====================================
@@ -68,7 +68,7 @@
     [self reloadData];
 }
 
-- (NSArray<id<VLCMediaLibraryItemProtocol>> *)backingArray
+- (NSArray<id<VLCMediaLibraryItemProtocol>> *)sourceBackingArray
 {
     return self.libraryModel.listOfShows;
 }
@@ -81,47 +81,13 @@
 - (void)libraryModelShowUpdated:(NSNotification *)notification
 {
     VLCMediaLibraryShow * const show = notification.object;
-    NSIndexPath * const indexPath = [self indexPathForLibraryItem:show];
-
-    if (indexPath != nil) {
-        [self.collectionView reloadItemsAtIndexPaths:[NSSet setWithObject:indexPath]];
-    }
-
-    const NSInteger rowIndex = [self rowForLibraryItem:show];
-    if (rowIndex == NSNotFound) {
-        return;
-    }
-
-    const NSInteger selectedMasterRow = self.masterTableView.selectedRow;
-    [self.masterTableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex]
-                                    columnIndexes:[NSIndexSet indexSetWithIndex:0]];
-
-    if (rowIndex == selectedMasterRow && self.masterTableView.selectedRow != selectedMasterRow) {
-        [self.masterTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedMasterRow]
-                          byExtendingSelection:NO];
-    } else {
-        [self.detailTableView reloadData];
-    }
+    [self applySnapshotForChangedItemID:show.libraryID isDeletion:NO];
 }
 
 - (void)libraryModelShowDeleted:(NSNotification *)notification
 {
     NSNumber * const showIdNumber = notification.object;
-    const NSInteger showLibraryId = showIdNumber.integerValue;
-    const NSInteger rowIndex = [self.backingArray indexOfObjectPassingTest:^BOOL(VLCMediaLibraryShow * const show, const NSUInteger __unused idx, BOOL * const __unused stop) {
-        return show.libraryID == showLibraryId;
-    }];
-    if (rowIndex == NSNotFound) {
-        return;
-    }
-
-    NSIndexPath * const indexPath = [NSIndexPath indexPathForItem:0 inSection:rowIndex];
-    if (indexPath != nil) {
-        [self.collectionView deleteItemsAtIndexPaths:[NSSet setWithObject:indexPath]];
-    }
-
-    [self.masterTableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:rowIndex]
-                                withAnimation:NSTableViewAnimationEffectFade];
+    [self applySnapshotForChangedItemID:showIdNumber.longLongValue isDeletion:YES];
 }
 
 - (NSString *)dataSourceTypeDisplayString



View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/95f5eb9213e4652acc1f4d48180ff5d65af859a7

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/95f5eb9213e4652acc1f4d48180ff5d65af859a7
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list