[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