Spracherkennung für: .m vermutete Sprache: Abap {ABAP[77] Lisp[55] Masm[55]} [Methode: maximale Elemente, drei Dimensionen]
/*****************************************************************************
* VLCLibraryModel.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* Authors: Felix Paul Kühne <fkuehne # videolan -dot- org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#import "VLCLibraryModel.h"
#include <sys/qos.h>
#import "VLCMediaLibraryFolderObserver.h"
#import "extensions/NSArray+VLCAdditions.h"
#import "extensions/NSString+Helpers.h"
#import "main/VLCMain.h"
NSString * const VLCLibraryModelArtistListReset = @"VLCLibraryModelArtistListReset";
NSString * const VLCLibraryModelAlbumListReset = @"VLCLibraryModelAlbumListReset";
NSString * const VLCLibraryModelGenreListReset = @"VLCLibraryModelGenreListReset";
NSString * const VLCLibraryModelListOfMonitoredFoldersUpdated = @"VLCLibraryModelListOfMonitoredFoldersUpdated";
NSString * const VLCLibraryModelMediaItemThumbnailGenerated = @"VLCLibraryModelMediaItemThumbnailGenerated";
NSString * const VLCLibraryModelAllCachesDropped = @"VLCLibraryModelAllCachesDropped";
NSString * const VLCLibraryModelAudioMediaListReset = @"VLCLibraryModelAudioMediaListReset";
NSString * const VLCLibraryModelVideoMediaListReset = @"VLCLibraryModelVideoMediaListReset";
NSString * const VLCLibraryModelFavoriteAudioMediaListReset = @"VLCLibraryModelFavoriteAudioMediaListReset";
NSString * const VLCLibraryModelFavoriteVideoMediaListReset = @"VLCLibraryModelFavoriteVideoMediaListReset";
NSString * const VLCLibraryModelFavoriteAlbumsListReset = @"VLCLibraryModelFavoriteAlbumsListReset";
NSString * const VLCLibraryModelFavoriteArtistsListReset = @"VLCLibraryModelFavoriteArtistsListReset";
NSString * const VLCLibraryModelFavoriteGenresListReset = @"VLCLibraryModelFavoriteGenresListReset";
NSString * const VLCLibraryModelRecentsMediaListReset = @"VLCLibraryModelRecentsMediaListReset";
NSString * const VLCLibraryModelRecentAudioMediaListReset = @"VLCLibraryModelRecentAudioMediaListReset";
NSString * const VLCLibraryModelListOfShowsReset = @"VLCLibraryModelListOfShowsReset";
NSString * const VLCLibraryModelListOfMoviesReset = @"VLCLibraryModelListOfMoviesReset";
NSString * const VLCLibraryModelListOfGroupsReset = @"VLCLibraryModelListOfGroupsReset";
NSString * const VLCLibraryModelPlaylistAdded = @"VLCLibraryModelPlaylistAdded";
NSString * const VLCLibraryModelAudioMediaItemDeleted = @"VLCLibraryModelAudioMediaItemDeleted";
NSString * const VLCLibraryModelVideoMediaItemDeleted = @"VLCLibraryModelVideoMediaItemDeleted";
NSString * const VLCLibraryModelRecentsMediaItemDeleted = @"VLCLibraryModelRecentsMediaItemDeleted";
NSString * const VLCLibraryModelRecentAudioMediaItemDeleted = @"VLCLibraryModelRecentAudioMediaItemDeleted";
NSString * const VLCLibraryModelAlbumDeleted = @"VLCLibraryModelAlbumDeleted";
NSString * const VLCLibraryModelArtistDeleted = @"VLCLibraryModelArtistDeleted";
NSString * const VLCLibraryModelGenreDeleted = @"VLCLibraryModelGenreDeleted";
NSString * const VLCLibraryModelGroupDeleted = @"VLCLibraryModelGroupDeleted";
NSString * const VLCLibraryModelPlaylistDeleted = @"VLCLibraryModelPlaylistDeleted";
NSString * const VLCLibraryModelShowDeleted = @"VLCLibraryModelShowDeleted";
NSString * const VLCLibraryModelAudioMediaItemUpdated = @"VLCLibraryModelAudioMediaItemUpdated";
NSString * const VLCLibraryModelVideoMediaItemUpdated = @"VLCLibraryModelVideoMediaItemUpdated";
NSString * const VLCLibraryModelRecentsMediaItemUpdated = @"VLCLibraryModelRecentsMediaItemUpdated";
NSString * const VLCLibraryModelRecentAudioMediaItemUpdated = @"VLCLibraryModelRecentAudioMediaItemUpdated";
NSString * const VLCLibraryModelAlbumUpdated = @"VLCLibraryModelAlbumUpdated";
NSString * const VLCLibraryModelArtistUpdated = @"VLCLibraryModelArtistUpdated";
NSString * const VLCLibraryModelGenreUpdated = @"VLCLibraryModelGenreUpdated";
NSString * const VLCLibraryModelGroupUpdated = @"VLCLibraryModelGroupUpdated";
NSString * const VLCLibraryModelPlaylistUpdated = @"VLCLibraryModelPlaylistUpdated";
NSString * const VLCLibraryModelShowUpdated = @"VLCLibraryModelShowUpdated";
NSString * const VLCLibraryModelDiscoveryStarted = @"VLCLibraryModelDiscoveryStarted";
NSString * const VLCLibraryModelDiscoveryProgress = @"VLCLibraryModelDiscoveryProgress";
NSString * const VLCLibraryModelDiscoveryCompleted = @"VLCLibraryModelDiscoveryCompleted";
NSString * const VLCLibraryModelDiscoveryFailed = @"VLCLibraryModelDiscoveryFailed";
@interface VLCLibraryModel ()
{
vlc_medialibrary_t *_p_mediaLibrary;
vlc_ml_event_callback_t *_p_eventCallback;
NSNotificationCenter *_defaultNotificationCenter;
enum vlc_ml_sorting_criteria_t _sortCriteria;
bool _sortDescending;
size_t _initialVideoCount;
size_t _initialAudioCount;
size_t _initialAlbumCount;
size_t _initialArtistCount;
size_t _initialGenreCount;
size_t _initialShowCount;
size_t _initialMovieCount;
size_t _initialGroupCount;
size_t _initialRecentsCount;
size_t _initialRecentAudioCount;
dispatch_queue_t _mediaItemCacheModificationQueue;
dispatch_queue_t _albumCacheModificationQueue;
dispatch_queue_t _artistCacheModificationQueue;
dispatch_queue_t _genreCacheModificationQueue;
dispatch_queue_t _groupCacheModificationQueue;
dispatch_queue_t _mediaTitlesCacheModificationQueue;
NSCountedSet<NSString *> *_mediaTitleCounts;
NSMutableDictionary<NSValue *, NSObject *> *_cacheUpdateTokens;
NSMutableSet<NSValue *> *_pendingCacheUpdates;
// Owned by the media-cache queue. Preserve membership after the ML removes
// a relationship, even if an earlier media update has cleared the subtype.
NSMutableDictionary<NSNumber *, NSNumber *> *_showIDsByEpisodeID;
NSMutableDictionary<NSNumber *, NSObject *> *_showRefreshTokens;
// Owned by each cache's modification queue. Later updates must preserve a
// pending membership/order refresh even when they change unrelated fields.
BOOL _audioCacheNeedsReload;
BOOL _videoCacheNeedsReload;
BOOL _artistCacheNeedsReload;
BOOL _albumCacheNeedsReload;
BOOL _genreCacheNeedsReload;
}
@property (readwrite) NSArray<VLCMediaLibraryFolderObserver *> *folderObservers;
@property (atomic) NSUInteger cacheGeneration;
@property (readwrite, nonatomic) NSArray *cachedAudioMedia;
@property (readwrite, nonatomic) NSArray *cachedArtists;
@property (readwrite, nonatomic) NSArray *cachedAlbums;
@property (readwrite, nonatomic) NSArray *cachedGenres;
@property (readwrite, nonatomic) NSArray *cachedVideoMedia;
@property (readwrite, nonatomic) NSArray *cachedListOfShows;
@property (readwrite, nonatomic) NSArray *cachedListOfMovies;
@property (readwrite, nonatomic) NSArray *cachedListOfGroups;
@property (readwrite, nonatomic) NSArray *cachedRecentMedia;
@property (readwrite, nonatomic) NSArray *cachedRecentAudioMedia;
@property (readwrite, nonatomic) NSArray *cachedListOfMonitoredFolders;
@property (readwrite, nonatomic) NSArray *cachedMediaTitles;
- (void)resetCachedListOfRecentMedia;
- (void)resetCachedListOfRecentAudioMedia;
- (void)resetCachedListOfArtists;
- (void)resetCachedListOfAlbums;
- (void)resetCachedListOfGenres;
- (void)resetCachedListOfShows;
- (void)resetCachedListOfGroups;
- (void)resetCachedListOfMonitoredFolders;
- (void)mediaItemThumbnailGenerated:(VLCMediaLibraryMediaItem *)mediaItem;
- (void)handleMediaItemAddedEvent:(const vlc_ml_event_t * const)p_event;
- (void)handlePlaylistAddedEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleMediaItemDeletionEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleAlbumDeletionEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleArtistDeletionEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleGenreDeletionEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleGroupDeletionEvent:(const vlc_ml_event_t * const)p_event;
- (void)handlePlaylistDeletionEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleMediaItemUpdateEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleAlbumUpdateEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleArtistUpdateEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleGenreUpdateEvent:(const vlc_ml_event_t * const)p_event;
- (void)handleGroupUpdateEvent:(const vlc_ml_event_t * const)p_event;
- (void)handlePlaylistUpdateEvent:(const vlc_ml_event_t * const)p_event;
- (void)performAfterCacheWritesOnQueue:(dispatch_queue_t)queue
block:(dispatch_block_t)block;
@end
static void libraryCallback(void *p_data, const vlc_ml_event_t *p_event)
{
VLCLibraryModel * const libraryModel = (__bridge VLCLibraryModel *)p_data;
if (libraryModel == nil) {
return;
}
@autoreleasepool {
switch(p_event->i_type)
{
case VLC_ML_EVENT_MEDIA_ADDED:
[libraryModel handleMediaItemAddedEvent:p_event];
break;
case VLC_ML_EVENT_MEDIA_UPDATED:
[libraryModel handleMediaItemUpdateEvent:p_event];
break;
case VLC_ML_EVENT_MEDIA_DELETED:
[libraryModel handleMediaItemDeletionEvent:p_event];
break;
case VLC_ML_EVENT_MEDIA_THUMBNAIL_GENERATED:
if (p_event->media_thumbnail_generated.b_success) {
VLCMediaLibraryMediaItem *mediaItem = [[VLCMediaLibraryMediaItem alloc] initWithMediaItem:(struct vlc_ml_media_t *)p_event->media_thumbnail_generated.p_media];
if (mediaItem == nil) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[libraryModel mediaItemThumbnailGenerated:mediaItem];
});
}
break;
case VLC_ML_EVENT_ARTIST_ADDED:
[libraryModel resetCachedListOfArtists];
break;
case VLC_ML_EVENT_ARTIST_UPDATED:
[libraryModel handleArtistUpdateEvent:p_event];
break;
case VLC_ML_EVENT_ARTIST_DELETED:
[libraryModel handleArtistDeletionEvent:p_event];
break;
case VLC_ML_EVENT_ALBUM_ADDED:
[libraryModel resetCachedListOfAlbums];
break;
case VLC_ML_EVENT_ALBUM_UPDATED:
[libraryModel handleAlbumUpdateEvent:p_event];
break;
case VLC_ML_EVENT_ALBUM_DELETED:
[libraryModel handleAlbumDeletionEvent:p_event];
break;
case VLC_ML_EVENT_GENRE_ADDED:
[libraryModel resetCachedListOfGenres];
break;
case VLC_ML_EVENT_GENRE_UPDATED:
[libraryModel handleGenreUpdateEvent:p_event];
break;
case VLC_ML_EVENT_GENRE_DELETED:
[libraryModel handleGenreDeletionEvent:p_event];
break;
case VLC_ML_EVENT_GROUP_ADDED:
[libraryModel resetCachedListOfGroups];
break;
case VLC_ML_EVENT_GROUP_UPDATED:
[libraryModel handleGroupUpdateEvent:p_event];
break;
case VLC_ML_EVENT_GROUP_DELETED:
[libraryModel handleGroupDeletionEvent:p_event];
break;
case VLC_ML_EVENT_PLAYLIST_ADDED:
[libraryModel handlePlaylistAddedEvent:p_event];
break;
case VLC_ML_EVENT_PLAYLIST_UPDATED:
[libraryModel handlePlaylistUpdateEvent:p_event];
break;
case VLC_ML_EVENT_PLAYLIST_DELETED:
[libraryModel handlePlaylistDeletionEvent:p_event];
break;
case VLC_ML_EVENT_FOLDER_ADDED:
case VLC_ML_EVENT_FOLDER_UPDATED:
case VLC_ML_EVENT_FOLDER_DELETED:
[libraryModel resetCachedListOfMonitoredFolders];
break;
case VLC_ML_EVENT_HISTORY_CHANGED:
[libraryModel resetCachedListOfRecentMedia];
[libraryModel resetCachedListOfRecentAudioMedia];
break;
case VLC_ML_EVENT_FAVORITES_CHANGED:
{
dispatch_async(dispatch_get_main_queue(), ^{
[NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteAudioMediaListReset object:libraryModel];
[NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteVideoMediaListReset object:libraryModel];
[NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteAlbumsListReset object:libraryModel];
[NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteArtistsListReset object:libraryModel];
[NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteGenresListReset object:libraryModel];
});
break;
}
case VLC_ML_EVENT_DISCOVERY_STARTED:
dispatch_async(dispatch_get_main_queue(), ^{
NSNotificationCenter * const defaultCenter = NSNotificationCenter.defaultCenter;
[defaultCenter postNotificationName:VLCLibraryModelDiscoveryStarted object:nil];
});
break;
case VLC_ML_EVENT_DISCOVERY_PROGRESS:
{
NSString * const entryPoint = toNSStr(p_event->discovery_progress.psz_entry_point);
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary<NSString *, NSString *> * const info = entryPoint == nil
? nil
: @{@"entryPoint": entryPoint};
NSNotificationCenter * const defaultCenter = NSNotificationCenter.defaultCenter;
[defaultCenter postNotificationName:VLCLibraryModelDiscoveryProgress
object:nil
userInfo:info];
});
break;
}
case VLC_ML_EVENT_DISCOVERY_COMPLETED:
dispatch_async(dispatch_get_main_queue(), ^{
NSNotificationCenter * const defaultCenter = NSNotificationCenter.defaultCenter;
[defaultCenter postNotificationName:VLCLibraryModelDiscoveryCompleted object:nil];
});
break;
case VLC_ML_EVENT_DISCOVERY_FAILED:
dispatch_async(dispatch_get_main_queue(), ^{
NSNotificationCenter * const defaultCenter = NSNotificationCenter.defaultCenter;
[defaultCenter postNotificationName:VLCLibraryModelDiscoveryFailed object:nil];
});
break;
default:
break;
}
}
}
@implementation VLCLibraryModel
@synthesize cachedAudioMedia = _cachedAudioMedia;
@synthesize cachedVideoMedia = _cachedVideoMedia;
@synthesize cachedRecentMedia = _cachedRecentMedia;
@synthesize cachedRecentAudioMedia = _cachedRecentAudioMedia;
@synthesize cachedListOfShows = _cachedListOfShows;
@synthesize cachedListOfMovies = _cachedListOfMovies;
@synthesize cachedListOfMonitoredFolders = _cachedListOfMonitoredFolders;
@synthesize cachedAlbums = _cachedAlbums;
@synthesize cachedArtists = _cachedArtists;
@synthesize cachedGenres = _cachedGenres;
@synthesize cachedListOfGroups = _cachedListOfGroups;
@synthesize cachedMediaTitles = _cachedMediaTitles;
+ (NSUInteger)modelIndexFromModelItemNotification:(NSNotification * const)aNotification
{
NSParameterAssert(aNotification);
NSDictionary * const notificationUserInfo = aNotification.userInfo;
NSAssert(notificationUserInfo != nil, @"Video item-related notification should carry valid user info");
NSNumber * const modelIndexNumber = (NSNumber * const)[notificationUserInfo objectForKey:@"index"];
NSAssert(modelIndexNumber != nil, @"Video item notification user info should carry index for updated item");
return modelIndexNumber.longLongValue;
}
- (instancetype)initWithLibrary:(vlc_medialibrary_t *)library
{
self = [super init];
if (self) {
_changeDelegate = [[VLCLibraryModelChangeDelegate alloc] initWithLibraryModel:self];
_sortCriteria = VLC_ML_SORTING_DEFAULT;
_sortDescending = NO;
_filterString = @"";
_recentMediaLimit = 20;
_p_mediaLibrary = library;
_p_eventCallback = vlc_ml_event_register_callback(_p_mediaLibrary, libraryCallback, (__bridge void *)self);
// Concurrent queues allow multiple readers while using barrier flags for exclusive writes,
// providing better performance than serial queues while maintaining thread safety
_mediaItemCacheModificationQueue = dispatch_queue_create("mediaItemCacheModificationQueue", DISPATCH_QUEUE_CONCURRENT);
_albumCacheModificationQueue = dispatch_queue_create("albumCacheModificationQueue", DISPATCH_QUEUE_CONCURRENT);
_artistCacheModificationQueue = dispatch_queue_create("artistCacheModificationQueue", DISPATCH_QUEUE_CONCURRENT);
_genreCacheModificationQueue = dispatch_queue_create("genreCacheModificationQueue", DISPATCH_QUEUE_CONCURRENT);
_groupCacheModificationQueue = dispatch_queue_create("groupCacheModificationQueue", DISPATCH_QUEUE_CONCURRENT);
_mediaTitlesCacheModificationQueue = dispatch_queue_create("mediaTitlesCacheModificationQueue", DISPATCH_QUEUE_CONCURRENT);
_mediaTitleCounts = [NSCountedSet new];
_cacheUpdateTokens = NSMutableDictionary.dictionary;
_pendingCacheUpdates = NSMutableSet.set;
_showRefreshTokens = NSMutableDictionary.dictionary;
_defaultNotificationCenter = NSNotificationCenter.defaultCenter;
[_defaultNotificationCenter addObserver:self
selector:@selector(applicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParameters = vlc_ml_query_params_create();
// Preload video and audio count for gui
self->_initialVideoCount = vlc_ml_count_video_media(self->_p_mediaLibrary, &queryParameters);
self->_initialAudioCount = vlc_ml_count_audio_media(self->_p_mediaLibrary, &queryParameters);
self->_initialAlbumCount = vlc_ml_count_albums(self->_p_mediaLibrary, &queryParameters);
self->_initialArtistCount = vlc_ml_count_artists(self->_p_mediaLibrary, &queryParameters, true);
self->_initialGenreCount = vlc_ml_count_genres(self->_p_mediaLibrary, &queryParameters);
self->_initialShowCount = vlc_ml_count_shows(self->_p_mediaLibrary, &queryParameters);
self->_initialMovieCount = vlc_ml_count_movies(self->_p_mediaLibrary, &queryParameters);
self->_initialGroupCount = vlc_ml_count_groups(self->_p_mediaLibrary, &queryParameters);
queryParameters.i_nbResults = self->_recentMediaLimit;
self->_initialRecentsCount = vlc_ml_count_video_history(self->_p_mediaLibrary, &queryParameters);
self->_initialRecentAudioCount = vlc_ml_count_audio_history(self->_p_mediaLibrary, &queryParameters);
});
[self resetCachedListOfMonitoredFolders];
}
return self;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
if (_p_eventCallback) {
vlc_ml_event_unregister_callback(_p_mediaLibrary, _p_eventCallback);
}
}
- (void)dealloc
{
[_defaultNotificationCenter removeObserver:self];
}
- (void)mediaItemThumbnailGenerated:(VLCMediaLibraryMediaItem *)mediaItem
{
[self.changeDelegate notifyChange:VLCLibraryModelMediaItemThumbnailGenerated withObject:mediaItem];
}
#pragma mark - Thread-Safe Cache Getters
- (NSArray *)readCacheOnQueue:(dispatch_queue_t)queue withBlock:(NSArray *(^)(void))block
{
__block NSArray *cache;
dispatch_sync(queue, ^{
cache = block();
});
return cache;
}
- (NSArray<VLCMediaLibraryMediaItem *> *)cachedAudioMedia
{
return [self readCacheOnQueue:_mediaItemCacheModificationQueue withBlock:^NSArray *{
return self->_cachedAudioMedia;
}];
}
- (NSArray<VLCMediaLibraryMediaItem *> *)cachedVideoMedia
{
return [self readCacheOnQueue:_mediaItemCacheModificationQueue withBlock:^NSArray *{
return self->_cachedVideoMedia;
}];
}
- (NSArray<VLCMediaLibraryMediaItem *> *)cachedRecentMedia
{
return [self readCacheOnQueue:_mediaItemCacheModificationQueue withBlock:^NSArray *{
return self->_cachedRecentMedia;
}];
}
- (NSArray<VLCMediaLibraryMediaItem *> *)cachedRecentAudioMedia
{
return [self readCacheOnQueue:_mediaItemCacheModificationQueue withBlock:^NSArray *{
return self->_cachedRecentAudioMedia;
}];
}
- (NSArray<VLCMediaLibraryShow *> *)cachedListOfShows
{
return [self readCacheOnQueue:_mediaItemCacheModificationQueue withBlock:^NSArray *{
return self->_cachedListOfShows;
}];
}
- (NSArray<VLCMediaLibraryMovie *> *)cachedListOfMovies
{
return [self readCacheOnQueue:_mediaItemCacheModificationQueue withBlock:^NSArray *{
return self->_cachedListOfMovies;
}];
}
- (NSArray<VLCMediaLibraryEntryPoint *> *)cachedListOfMonitoredFolders
{
return [self readCacheOnQueue:_mediaItemCacheModificationQueue withBlock:^NSArray *{
return self->_cachedListOfMonitoredFolders;
}];
}
- (NSArray<VLCMediaLibraryAlbum *> *)cachedAlbums
{
return [self readCacheOnQueue:_albumCacheModificationQueue withBlock:^NSArray *{
return self->_cachedAlbums;
}];
}
- (NSArray<VLCMediaLibraryArtist *> *)cachedArtists
{
return [self readCacheOnQueue:_artistCacheModificationQueue withBlock:^NSArray *{
return self->_cachedArtists;
}];
}
- (NSArray<VLCMediaLibraryGenre *> *)cachedGenres
{
return [self readCacheOnQueue:_genreCacheModificationQueue withBlock:^NSArray *{
return self->_cachedGenres;
}];
}
- (NSArray<VLCMediaLibraryGroup *> *)cachedListOfGroups
{
return [self readCacheOnQueue:_groupCacheModificationQueue withBlock:^NSArray *{
return self->_cachedListOfGroups;
}];
}
- (NSArray<NSString *> *)cachedMediaTitles
{
return [self readCacheOnQueue:_mediaTitlesCacheModificationQueue withBlock:^NSArray *{
return self->_cachedMediaTitles;
}];
}
#pragma mark - Custom Thread-Safe Cache Setters
- (void)setCachedAudioMedia:(NSArray *)cachedAudioMedia
{
[self invalidateCacheUpdateForGetter:@selector(cachedAudioMedia)];
NSArray * const media = [cachedAudioMedia copy];
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
[self replaceMediaTitlesFromMedia:self->_cachedAudioMedia withMedia:media];
self->_cachedAudioMedia = media;
self->_audioCacheNeedsReload = NO;
});
}
- (void)setCachedVideoMedia:(NSArray *)cachedVideoMedia
{
[self invalidateCacheUpdateForGetter:@selector(cachedVideoMedia)];
NSArray * const media = [cachedVideoMedia copy];
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
[self replaceMediaTitlesFromMedia:self->_cachedVideoMedia withMedia:media];
self->_cachedVideoMedia = media;
self->_videoCacheNeedsReload = NO;
});
}
- (void)setCachedRecentMedia:(NSArray *)cachedRecentMedia
{
[self invalidateCacheUpdateForGetter:@selector(cachedRecentMedia)];
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
self->_cachedRecentMedia = [cachedRecentMedia copy];
});
}
- (void)setCachedRecentAudioMedia:(NSArray *)cachedRecentAudioMedia
{
[self invalidateCacheUpdateForGetter:@selector(cachedRecentAudioMedia)];
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
self->_cachedRecentAudioMedia = [cachedRecentAudioMedia copy];
});
}
- (NSDictionary<NSNumber *, NSNumber *> *)showIDsByEpisodeIDForShows:(NSArray<VLCMediaLibraryShow *> *)shows
{
NSMutableDictionary<NSNumber *, NSNumber *> * const showIDs = NSMutableDictionary.dictionary;
for (VLCMediaLibraryShow * const show in shows) {
if (show.episodeCount == 0) {
continue;
}
vlc_ml_media_list_t * const episodes = vlc_ml_list_show_episodes(_p_mediaLibrary, NULL, show.libraryID);
if (episodes == NULL) {
return nil;
}
for (size_t index = 0; index < episodes->i_nb_items; index++) {
showIDs[@(episodes->p_items[index].i_id)] = @(show.libraryID);
}
vlc_ml_media_list_release(episodes);
}
return showIDs;
}
- (void)replaceCachedShows:(NSArray<VLCMediaLibraryShow *> *)shows
showIDsByEpisodeID:(NSDictionary<NSNumber *, NSNumber *> *)showIDsByEpisodeID
{
_cachedListOfShows = shows.copy;
_showIDsByEpisodeID = showIDsByEpisodeID.mutableCopy;
[_showRefreshTokens removeAllObjects];
}
- (void)setCachedListOfShows:(NSArray<VLCMediaLibraryShow *> *)cachedListOfShows
{
NSArray<VLCMediaLibraryShow *> * const shows = cachedListOfShows.copy;
if (shows == nil) {
[self invalidateCacheUpdateForGetter:@selector(cachedListOfShows)];
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
[self replaceCachedShows:nil showIDsByEpisodeID:nil];
});
return;
}
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedListOfShows)];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
NSDictionary<NSNumber *, NSNumber *> * const showIDs = [self showIDsByEpisodeIDForShows:shows];
if (showIDs == nil) {
return;
}
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedListOfShows)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
[self replaceCachedShows:shows showIDsByEpisodeID:showIDs];
}];
});
}
- (void)setCachedListOfMovies:(NSArray *)cachedListOfMovies
{
[self invalidateCacheUpdateForGetter:@selector(cachedListOfMovies)];
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
self->_cachedListOfMovies = [cachedListOfMovies copy];
});
}
- (void)setCachedListOfMonitoredFolders:(NSArray *)cachedListOfMonitoredFolders
{
[self invalidateCacheUpdateForGetter:@selector(cachedListOfMonitoredFolders)];
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
self->_cachedListOfMonitoredFolders = [cachedListOfMonitoredFolders copy];
});
}
- (void)setCachedAlbums:(NSArray *)cachedAlbums
{
[self invalidateCacheUpdateForGetter:@selector(cachedAlbums)];
dispatch_barrier_async(_albumCacheModificationQueue, ^{
self->_cachedAlbums = [cachedAlbums copy];
self->_albumCacheNeedsReload = NO;
});
}
- (void)setCachedArtists:(NSArray *)cachedArtists
{
[self invalidateCacheUpdateForGetter:@selector(cachedArtists)];
dispatch_barrier_async(_artistCacheModificationQueue, ^{
self->_cachedArtists = [cachedArtists copy];
self->_artistCacheNeedsReload = NO;
});
}
- (void)setCachedGenres:(NSArray *)cachedGenres
{
[self invalidateCacheUpdateForGetter:@selector(cachedGenres)];
dispatch_barrier_async(_genreCacheModificationQueue, ^{
self->_cachedGenres = [cachedGenres copy];
self->_genreCacheNeedsReload = NO;
});
}
- (void)setCachedListOfGroups:(NSArray *)cachedListOfGroups
{
[self invalidateCacheUpdateForGetter:@selector(cachedListOfGroups)];
dispatch_barrier_async(_groupCacheModificationQueue, ^{
self->_cachedListOfGroups = [cachedListOfGroups copy];
});
}
- (void)setCachedMediaTitles:(NSArray *)cachedMediaTitles
{
[self invalidateCacheUpdateForGetter:@selector(cachedMediaTitles)];
dispatch_barrier_async(_mediaTitlesCacheModificationQueue, ^{
self->_cachedMediaTitles = [cachedMediaTitles copy];
});
}
- (size_t)numberOfAudioMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedAudioMedia;
if (cache == nil) {
[self resetCachedListOfAudioMedia];
// Return initial count here, otherwise it will return 0 on the first time
return _initialAudioCount;
}
return cache.count;
}
- (vlc_ml_query_params_t)queryParams
{
const vlc_ml_query_params_t queryParams = { .psz_pattern = self->_filterString.length > 0 ? [self->_filterString UTF8String] : NULL,
.i_sort = self->_sortCriteria,
.b_desc = self->_sortDescending };
return queryParams;
}
- (void)performAfterCacheWritesOnQueue:(dispatch_queue_t)queue
block:(dispatch_block_t)block
{
const NSUInteger generation = self.cacheGeneration;
// The sentinel runs after all cache writes already queued on this queue.
dispatch_barrier_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (generation == self.cacheGeneration) {
block();
}
});
});
}
- (NSObject *)beginCacheUpdateForGetter:(SEL)getter
{
NSObject * const token = NSObject.new;
@synchronized (_cacheUpdateTokens) {
_cacheUpdateTokens[[NSValue valueWithPointer:getter]] = token;
[_pendingCacheUpdates addObject:[NSValue valueWithPointer:getter]];
}
return token;
}
- (BOOL)isCurrentCacheUpdate:(NSObject *)token forGetter:(SEL)getter
{
@synchronized (_cacheUpdateTokens) {
return _cacheUpdateTokens[[NSValue valueWithPointer:getter]] == token;
}
}
- (BOOL)invalidateCacheUpdateForGetter:(SEL)getter
{
@synchronized (_cacheUpdateTokens) {
NSValue * const key = [NSValue valueWithPointer:getter];
const BOOL pending = [_pendingCacheUpdates containsObject:key];
[_cacheUpdateTokens removeObjectForKey:key];
[_pendingCacheUpdates removeObject:key];
return pending;
}
}
- (void)performCacheMutationForGetters:(const SEL *)getters
count:(NSUInteger)count
block:(dispatch_block_t)block
{
@synchronized (_cacheUpdateTokens) {
BOOL pending[count];
for (NSUInteger index = 0; index < count; index++) {
pending[index] = [self invalidateCacheUpdateForGetter:getters[index]];
}
block();
// Incremental changes cannot replace a pending membership/order query.
// Reconcile again unless the mutation already started a newer query.
for (NSUInteger index = 0; index < count; index++) {
const SEL getter = getters[index];
if (!pending[index] || [_pendingCacheUpdates containsObject:[NSValue valueWithPointer:getter]]) {
continue;
}
if (getter == @selector(cachedAudioMedia))
[self resetCachedListOfAudioMedia];
else if (getter == @selector(cachedVideoMedia))
[self resetCachedListOfVideoMedia];
else if (getter == @selector(cachedRecentAudioMedia))
[self resetCachedListOfRecentAudioMedia];
else if (getter == @selector(cachedRecentMedia))
[self resetCachedListOfRecentMedia];
else if (getter == @selector(cachedArtists))
[self resetCachedListOfArtists];
else if (getter == @selector(cachedAlbums))
[self resetCachedListOfAlbums];
else if (getter == @selector(cachedGenres))
[self resetCachedListOfGenres];
else if (getter == @selector(cachedListOfGroups))
[self resetCachedListOfGroups];
else if (getter == @selector(cachedListOfShows))
[self resetCachedListOfShows];
}
}
}
- (void)performCacheMutationForGetter:(SEL)getter block:(dispatch_block_t)block
{
SEL const getters[] = { getter };
[self performCacheMutationForGetters:getters count:1 block:block];
}
- (void)performCacheWriteForGeneration:(NSUInteger)generation
token:(NSObject *)token
getter:(SEL)getter
onQueue:(dispatch_queue_t)queue
block:(dispatch_block_t)block
{
dispatch_barrier_async(queue, ^{
// Validate at the write, not when the background query finishes.
@synchronized (self->_cacheUpdateTokens) {
if (generation == self.cacheGeneration && [self isCurrentCacheUpdate:token forGetter:getter]) {
[self->_pendingCacheUpdates removeObject:[NSValue valueWithPointer:getter]];
block();
}
}
});
}
- (void)resetCachedListOfAudioMedia
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedAudioMedia)];
NSString * const filterString = [self.filterString copy];
const vlc_ml_query_params_t capturedParams = [self queryParams];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParams = capturedParams;
queryParams.psz_pattern = filterString.length > 0 ? filterString.UTF8String : NULL;
vlc_ml_media_list_t * const p_media_list = vlc_ml_list_audio_media(self->_p_mediaLibrary, &queryParams);
NSArray * const mediaArray = [NSArray arrayFromVlcMediaList:p_media_list];
if (mediaArray == nil) {
return;
}
vlc_ml_media_list_release(p_media_list);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedAudioMedia)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
[self replaceMediaTitlesFromMedia:self->_cachedAudioMedia withMedia:mediaArray];
self->_cachedAudioMedia = mediaArray;
self->_audioCacheNeedsReload = NO;
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelAudioMediaListReset withObject:self];
}];
}];
});
}
- (NSArray<VLCMediaLibraryMediaItem *> *)listOfAudioMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedAudioMedia;
if (cache == nil) {
[self resetCachedListOfAudioMedia];
}
return cache;
}
- (size_t)numberOfArtists
{
NSArray<VLCMediaLibraryArtist *> * const cache = self.cachedArtists;
if (cache == nil) {
[self resetCachedListOfArtists];
// Return initial count here, otherwise it will return 0 on the first time
return _initialArtistCount;
}
return cache.count;
}
- (void)resetCachedListOfArtists
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedArtists)];
NSString * const filterString = [self.filterString copy];
const vlc_ml_query_params_t capturedParams = [self queryParams];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParams = capturedParams;
queryParams.psz_pattern = filterString.length > 0 ? filterString.UTF8String : NULL;
vlc_ml_artist_list_t * const p_artist_list = vlc_ml_list_artists(self->_p_mediaLibrary, &queryParams, YES);
const size_t numberOfArtists = p_artist_list->i_nb_items;
NSMutableArray * const mutableArtistArray = [[NSMutableArray alloc] initWithCapacity:numberOfArtists];
for (size_t x = 0; x < numberOfArtists; x++) {
VLCMediaLibraryArtist * const artist = [[VLCMediaLibraryArtist alloc] initWithArtist:&p_artist_list->p_items[x]];
if (artist != nil) {
[mutableArtistArray addObject:artist];
}
}
vlc_ml_artist_list_release(p_artist_list);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedArtists)
onQueue:self->_artistCacheModificationQueue
block:^{
self->_cachedArtists = mutableArtistArray.copy;
self->_artistCacheNeedsReload = NO;
[self performAfterCacheWritesOnQueue:self->_artistCacheModificationQueue block:^{
if (![self isCurrentCacheUpdate:token forGetter:@selector(cachedArtists)]) {
return;
}
[self.changeDelegate notifyChange:VLCLibraryModelArtistListReset withObject:self];
}];
}];
});
}
- (NSArray<VLCMediaLibraryArtist *> *)listOfArtists
{
NSArray<VLCMediaLibraryArtist *> * const cache = self.cachedArtists;
if (cache == nil) {
[self resetCachedListOfArtists];
}
return cache;
}
- (size_t)numberOfAlbums
{
NSArray<VLCMediaLibraryAlbum *> * const cache = self.cachedAlbums;
if (cache == nil) {
[self resetCachedListOfAlbums];
// Return initial count here, otherwise it will return 0 on the first time
return _initialAlbumCount;
}
return cache.count;
}
- (void)resetCachedListOfAlbums
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedAlbums)];
NSString * const filterString = [self.filterString copy];
const vlc_ml_query_params_t capturedParams = [self queryParams];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParams = capturedParams;
queryParams.psz_pattern = filterString.length > 0 ? filterString.UTF8String : NULL;
vlc_ml_album_list_t * const p_album_list = vlc_ml_list_albums(self->_p_mediaLibrary, &queryParams);
const size_t numberOfAlbums = p_album_list->i_nb_items;
NSMutableArray * const mutableAlbumArray = [[NSMutableArray alloc] initWithCapacity:numberOfAlbums];
for (size_t x = 0; x < numberOfAlbums; x++) {
VLCMediaLibraryAlbum * const album = [[VLCMediaLibraryAlbum alloc] initWithAlbum:&p_album_list->p_items[x]];
[mutableAlbumArray addObject:album];
}
vlc_ml_album_list_release(p_album_list);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedAlbums)
onQueue:self->_albumCacheModificationQueue
block:^{
self->_cachedAlbums = mutableAlbumArray.copy;
self->_albumCacheNeedsReload = NO;
[self performAfterCacheWritesOnQueue:self->_albumCacheModificationQueue block:^{
if (![self isCurrentCacheUpdate:token forGetter:@selector(cachedAlbums)]) {
return;
}
[self.changeDelegate notifyChange:VLCLibraryModelAlbumListReset withObject:self];
}];
}];
});
}
- (NSArray<VLCMediaLibraryAlbum *> *)listOfAlbums
{
NSArray<VLCMediaLibraryAlbum *> * const cache = self.cachedAlbums;
if (cache == nil) {
[self resetCachedListOfAlbums];
}
return cache;
}
- (size_t)numberOfGenres
{
NSArray<VLCMediaLibraryGenre *> * const cache = self.cachedGenres;
if (cache == nil) {
[self resetCachedListOfGenres];
// Return initial count here, otherwise it will return 0 on the first time
return _initialGenreCount;
}
return cache.count;
}
- (void)resetCachedListOfGenres
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedGenres)];
NSString * const filterString = [self.filterString copy];
const vlc_ml_query_params_t capturedParams = [self queryParams];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParams = capturedParams;
queryParams.psz_pattern = filterString.length > 0 ? filterString.UTF8String : NULL;
vlc_ml_genre_list_t * const p_genre_list = vlc_ml_list_genres(self->_p_mediaLibrary, &queryParams);
const size_t numberOfGenres = p_genre_list->i_nb_items;
NSMutableArray * const mutableGenreArray = [[NSMutableArray alloc] initWithCapacity:numberOfGenres];
for (size_t x = 0; x < numberOfGenres; x++) {
VLCMediaLibraryGenre * const genre = [[VLCMediaLibraryGenre alloc] initWithGenre:&p_genre_list->p_items[x]];
[mutableGenreArray addObject:genre];
}
vlc_ml_genre_list_release(p_genre_list);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedGenres)
onQueue:self->_genreCacheModificationQueue
block:^{
self->_cachedGenres = mutableGenreArray.copy;
self->_genreCacheNeedsReload = NO;
[self performAfterCacheWritesOnQueue:self->_genreCacheModificationQueue block:^{
if (![self isCurrentCacheUpdate:token forGetter:@selector(cachedGenres)]) {
return;
}
[self.changeDelegate notifyChange:VLCLibraryModelGenreListReset withObject:self];
}];
}];
});
}
- (NSArray<VLCMediaLibraryGenre *> *)listOfGenres
{
NSArray<VLCMediaLibraryGenre *> * const cache = self.cachedGenres;
if (cache == nil) {
[self resetCachedListOfGenres];
}
return cache;
}
- (size_t)numberOfVideoMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedVideoMedia;
if (cache == nil) {
[self resetCachedListOfVideoMedia];
// Return initial count here, otherwise it will return 0 on the first time
return _initialVideoCount;
}
return cache.count;
}
- (void)resetCachedListOfVideoMedia
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedVideoMedia)];
NSString * const filterString = [self.filterString copy];
const vlc_ml_query_params_t capturedParams = [self queryParams];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParameters = capturedParams;
queryParameters.psz_pattern = filterString.length > 0 ? filterString.UTF8String : NULL;
vlc_ml_media_list_t *p_media_list = vlc_ml_list_video_media(self->_p_mediaLibrary, &queryParameters);
if (p_media_list == NULL) {
return;
}
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:p_media_list->i_nb_items];
for (size_t x = 0; x < p_media_list->i_nb_items; x++) {
VLCMediaLibraryMediaItem *mediaItem = [[VLCMediaLibraryMediaItem alloc] initWithMediaItem:&p_media_list->p_items[x]];
if (mediaItem != nil) {
[mutableArray addObject:mediaItem];
}
}
vlc_ml_media_list_release(p_media_list);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedVideoMedia)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
NSArray<VLCMediaLibraryMediaItem *> * const media = mutableArray.copy;
[self replaceMediaTitlesFromMedia:self->_cachedVideoMedia withMedia:media];
self->_cachedVideoMedia = media;
self->_videoCacheNeedsReload = NO;
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelVideoMediaListReset withObject:self];
}];
}];
});
}
- (NSArray<VLCMediaLibraryMediaItem *> *)listOfVideoMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedVideoMedia;
if (cache == nil) {
[self resetCachedListOfVideoMedia];
}
return cache;
}
- (void)replaceMediaTitlesFromMedia:(NSArray<VLCMediaLibraryMediaItem *> *)oldMedia
withMedia:(NSArray<VLCMediaLibraryMediaItem *> *)newMedia
{
dispatch_barrier_async(_mediaTitlesCacheModificationQueue, ^{
for (VLCMediaLibraryMediaItem * const item in oldMedia) {
NSString * const title = item.displayString;
if (title) {
[self->_mediaTitleCounts removeObject:title];
}
}
for (VLCMediaLibraryMediaItem * const item in newMedia) {
NSString * const title = item.displayString;
if (title) {
[self->_mediaTitleCounts addObject:title];
}
}
if (newMedia == nil && self->_mediaTitleCounts.count == 0) {
self->_cachedMediaTitles = nil;
} else if (newMedia != nil || self->_cachedMediaTitles != nil) {
self->_cachedMediaTitles = [self->_mediaTitleCounts.allObjects
sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}
});
}
- (void)replaceMediaTitle:(NSString *)oldTitle withTitle:(NSString *)newTitle
{
if (oldTitle == newTitle || [oldTitle isEqualToString:newTitle]) {
return;
}
dispatch_barrier_async(_mediaTitlesCacheModificationQueue, ^{
BOOL removeTitle = NO;
BOOL insertTitle = NO;
if (oldTitle && [self->_mediaTitleCounts countForObject:oldTitle] > 0) {
[self->_mediaTitleCounts removeObject:oldTitle];
removeTitle = [self->_mediaTitleCounts countForObject:oldTitle] == 0;
}
if (newTitle) {
insertTitle = [self->_mediaTitleCounts countForObject:newTitle] == 0;
[self->_mediaTitleCounts addObject:newTitle];
}
if (self->_cachedMediaTitles == nil || (!removeTitle && !insertTitle)) {
return;
}
NSMutableArray<NSString *> * const titles = self->_cachedMediaTitles.mutableCopy;
if (removeTitle) {
[titles removeObject:oldTitle];
}
if (insertTitle) {
const NSUInteger index =
[titles indexOfObject:newTitle
inSortedRange:NSMakeRange(0, titles.count)
options:NSBinarySearchingInsertionIndex
usingComparator:^NSComparisonResult(NSString *left, NSString *right) {
return [left localizedCaseInsensitiveCompare:right];
}];
[titles insertObject:newTitle atIndex:index];
}
self->_cachedMediaTitles = titles.copy;
});
}
- (NSArray<NSString *> *)listOfMediaTitles
{
// Source setters maintain the counts; only a title lookup loads missing sources.
if (self.cachedAudioMedia == nil) {
[self resetCachedListOfAudioMedia];
}
if (self.cachedVideoMedia == nil) {
[self resetCachedListOfVideoMedia];
}
return self.cachedMediaTitles;
}
- (void)getListOfRecentMediaOfType:(vlc_ml_media_type_t)type
withCountLimit:(size_t)countLimit
withCompletion:(void (^)(NSArray *recentMediaArray))completionHandler
{
NSString * const filterString = [self.filterString copy];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParameters = vlc_ml_query_params_create();
queryParameters.i_nbResults = countLimit;
queryParameters.psz_pattern = filterString.length > 0 ? filterString.UTF8String : NULL;
// we don't set the sorting criteria here as they are not applicable to history
vlc_ml_media_list_t *p_media_list = NULL;
if (type == VLC_ML_MEDIA_TYPE_VIDEO)
p_media_list = vlc_ml_list_video_history(self->_p_mediaLibrary, &queryParameters);
else if (type == VLC_ML_MEDIA_TYPE_AUDIO)
p_media_list = vlc_ml_list_audio_history(self->_p_mediaLibrary, &queryParameters);
NSArray * const mediaArray = [NSArray arrayFromVlcMediaList:p_media_list];
if (mediaArray == nil) {
return;
}
vlc_ml_media_list_release(p_media_list);
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(mediaArray);
});
});
}
- (size_t)countOfRecentVideoMediaWithCountLimit:(size_t)countLimit
{
vlc_ml_query_params_t queryParameters = vlc_ml_query_params_create();
queryParameters.i_nbResults = countLimit;
queryParameters.psz_pattern = self->_filterString.length > 0 ? self->_filterString.UTF8String : NULL;
return vlc_ml_count_video_history(self->_p_mediaLibrary, &queryParameters);
}
- (void)resetCachedListOfRecentMedia
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedRecentMedia)];
[self getListOfRecentMediaOfType:VLC_ML_MEDIA_TYPE_VIDEO
withCountLimit:_recentMediaLimit
withCompletion:^(NSArray * const mediaArray) {
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedRecentMedia)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
self->_cachedRecentMedia = mediaArray;
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelRecentsMediaListReset withObject:self];
}];
}];
}];
}
- (size_t)numberOfRecentMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedRecentMedia;
if (cache == nil) {
[self resetCachedListOfRecentMedia];
// Return the filtered count immediately during search, otherwise keep the startup fast path.
if (_filterString.length > 0) {
return [self countOfRecentVideoMediaWithCountLimit:_recentMediaLimit];
}
return _initialRecentsCount;
}
return cache.count;
}
- (NSArray<VLCMediaLibraryMediaItem *> *)listOfRecentMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedRecentMedia;
if (cache == nil) {
[self resetCachedListOfRecentMedia];
}
return cache;
}
- (void)resetCachedListOfRecentAudioMedia
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedRecentAudioMedia)];
[self getListOfRecentMediaOfType:VLC_ML_MEDIA_TYPE_AUDIO
withCountLimit:_recentAudioMediaLimit
withCompletion:^(NSArray * const mediaArray) {
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedRecentAudioMedia)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
self->_cachedRecentAudioMedia = mediaArray;
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelRecentAudioMediaListReset withObject:self];
}];
}];
}];
}
- (size_t)numberOfRecentAudioMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedRecentAudioMedia;
if (cache == nil) {
[self resetCachedListOfRecentAudioMedia];
// Return initial count here, otherwise it will return 0 on the first time
return _initialRecentAudioCount;
}
return cache.count;
}
- (NSArray<VLCMediaLibraryMediaItem *> *)listOfRecentAudioMedia
{
NSArray<VLCMediaLibraryMediaItem *> * const cache = self.cachedRecentAudioMedia;
if (cache == nil) {
[self resetCachedListOfRecentAudioMedia];
}
return cache;
}
- (void)resetCachedListOfShows
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedListOfShows)];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_show_list_t * const p_show_list = vlc_ml_list_shows(self->_p_mediaLibrary, NULL);
if (p_show_list == NULL) {
return;
}
const size_t itemCount = p_show_list->i_nb_items;
NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:itemCount];
for (size_t x = 0; x < p_show_list->i_nb_items; x++) {
vlc_ml_show_t * const p_vlc_show = &p_show_list->p_items[x];
VLCMediaLibraryShow * const show = [[VLCMediaLibraryShow alloc] initWithShow:p_vlc_show];
if (show) {
[mutableArray addObject:show];
}
}
vlc_ml_show_list_release(p_show_list);
NSDictionary<NSNumber *, NSNumber *> * const showIDs = [self showIDsByEpisodeIDForShows:mutableArray];
if (showIDs == nil) {
return;
}
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedListOfShows)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
[self replaceCachedShows:mutableArray showIDsByEpisodeID:showIDs];
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelListOfShowsReset withObject:self];
}];
}];
});
}
- (void)refreshCachedShowForEpisodeID:(int64_t)episodeID
{
if ([self invalidateCacheUpdateForGetter:@selector(cachedListOfShows)]) {
// The pending list may contain an episode not mapped by the old cache.
[self resetCachedListOfShows];
return;
}
NSNumber * const showID = _showIDsByEpisodeID[@(episodeID)];
if (showID == nil) {
return;
}
const BOOL refreshPending = _showRefreshTokens[showID] != nil;
_showRefreshTokens[showID] = NSObject.new;
if (!refreshPending) {
[self refreshCachedShowForLibraryID:showID.longLongValue];
}
}
- (void)refreshCachedShowForLibraryID:(int64_t)libraryID
{
const NSUInteger generation = self.cacheGeneration;
NSNumber * const showID = @(libraryID);
NSObject * const token = _showRefreshTokens[showID];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_show_t * const p_show = vlc_ml_get_show(self->_p_mediaLibrary, libraryID);
VLCMediaLibraryShow * const updatedShow =
p_show == NULL ? nil : [[VLCMediaLibraryShow alloc] initWithShow:p_show];
if (p_show != NULL) {
vlc_ml_show_release(p_show);
}
[self updateCachedShow:updatedShow
libraryID:libraryID
generation:generation
token:token];
});
}
- (void)updateCachedShow:(VLCMediaLibraryShow *)updatedShow
libraryID:(int64_t)libraryID
generation:(NSUInteger)generation
token:(NSObject *)token
{
NSNumber * const showID = @(libraryID);
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
NSObject * const currentToken = self->_showRefreshTokens[showID];
if (generation != self.cacheGeneration || currentToken == nil) {
return;
}
if (currentToken != token) {
// Coalesce changes that arrived during this lookup
[self refreshCachedShowForLibraryID:libraryID];
return;
}
[self->_showRefreshTokens removeObjectForKey:showID];
const NSUInteger index = [self->_cachedListOfShows indexOfObjectPassingTest:^BOOL(VLCMediaLibraryShow * const show, NSUInteger __unused idx, BOOL * __unused stop) {
return show.libraryID == libraryID;
}];
if (self->_cachedListOfShows == nil || index == NSNotFound) {
return;
}
NSMutableArray<VLCMediaLibraryShow *> * const shows = self->_cachedListOfShows.mutableCopy;
const BOOL deleted = updatedShow == nil || updatedShow.episodeCount == 0;
if (deleted) {
[shows removeObjectAtIndex:index];
NSSet<NSNumber *> * const episodeIDs = [self->_showIDsByEpisodeID keysOfEntriesPassingTest:^BOOL(NSNumber * __unused key, NSNumber * const parentID, BOOL * __unused stop) {
return [parentID isEqualToNumber:showID];
}];
[self->_showIDsByEpisodeID removeObjectsForKeys:episodeIDs.allObjects];
} else {
[shows replaceObjectAtIndex:index withObject:updatedShow];
}
self->_cachedListOfShows = shows.copy;
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:deleted ? VLCLibraryModelShowDeleted : VLCLibraryModelShowUpdated
withObject:deleted ? showID : updatedShow];
}];
});
}
- (void)resetCachedListOfMovies
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedListOfMovies)];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_media_list_t * const p_movie_list = vlc_ml_list_movies(self->_p_mediaLibrary, NULL);
if (p_movie_list == NULL) {
return;
}
const size_t itemCount = p_movie_list->i_nb_items;
NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:itemCount];
for (size_t x = 0; x < p_movie_list->i_nb_items; x++) {
vlc_ml_media_t * const p_vlc_media = &p_movie_list->p_items[x];
// Defensive: only add if subtype is MOVIE
if (p_vlc_media->i_subtype == VLC_ML_MEDIA_SUBTYPE_MOVIE) {
VLCMediaLibraryMovie * const movie = [[VLCMediaLibraryMovie alloc] initWithMediaItem:p_vlc_media];
if (movie) {
[mutableArray addObject:movie];
}
}
}
vlc_ml_media_list_release(p_movie_list);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedListOfMovies)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
self->_cachedListOfMovies = mutableArray.copy;
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelListOfMoviesReset withObject:self];
[NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelListOfMoviesReset object:self];
}];
}];
});
}
- (size_t)numberOfShows
{
NSArray<VLCMediaLibraryShow *> * const cache = self.cachedListOfShows;
if (cache == nil) {
[self resetCachedListOfShows];
// Return initial count here, otherwise it will return 0 on the first time
return _initialShowCount;
}
return cache.count;
}
- (size_t)numberOfMovies
{
NSArray<VLCMediaLibraryMovie *> * const cache = self.cachedListOfMovies;
if (cache == nil) {
[self resetCachedListOfMovies];
// Return initial count here, otherwise it will return 0 on the first time
return _initialMovieCount;
}
return cache.count;
}
- (NSArray<VLCMediaLibraryShow *> *)listOfShows
{
NSArray<VLCMediaLibraryShow *> * const cache = self.cachedListOfShows;
if (cache == nil) {
[self resetCachedListOfShows];
}
return cache;
}
- (NSArray<VLCMediaLibraryMovie *> *)listOfMovies
{
NSArray<VLCMediaLibraryMovie *> * const cache = self.cachedListOfMovies;
if (cache == nil) {
[self resetCachedListOfMovies];
}
return cache;
}
- (size_t)numberOfGroups
{
NSArray<VLCMediaLibraryGroup *> * const cache = self.cachedListOfGroups;
if (cache == nil) {
[self resetCachedListOfGroups];
// Return initial count here, otherwise it will return 0 on the first time
return _initialGroupCount;
}
return cache.count;
}
- (NSArray<VLCMediaLibraryGroup *> *)listOfGroups
{
NSArray<VLCMediaLibraryGroup *> * const cache = self.cachedListOfGroups;
if (cache == nil) {
[self resetCachedListOfGroups];
}
return cache;
}
- (void)resetCachedListOfGroups
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedListOfGroups)];
NSString * const filterString = [self.filterString copy];
const vlc_ml_query_params_t capturedParams = [self queryParams];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_query_params_t queryParams = capturedParams;
queryParams.psz_pattern = filterString.length > 0 ? filterString.UTF8String : NULL;
vlc_ml_group_list_t * const p_group_list =
vlc_ml_list_groups(self->_p_mediaLibrary, &queryParams);
if (p_group_list == NULL) {
return;
}
const size_t itemCount = p_group_list->i_nb_items;
NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:itemCount];
for (size_t x = 0; x < p_group_list->i_nb_items; x++) {
vlc_ml_group_t * const p_vlc_group = &p_group_list->p_items[x];
VLCMediaLibraryGroup * const group = [[VLCMediaLibraryGroup alloc] initWithGroup:p_vlc_group];
if (group) {
[mutableArray addObject:group];
}
}
vlc_ml_group_list_release(p_group_list);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedListOfGroups)
onQueue:self->_groupCacheModificationQueue
block:^{
self->_cachedListOfGroups = mutableArray.copy;
[self performAfterCacheWritesOnQueue:self->_groupCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelListOfGroupsReset withObject:self];
}];
}];
});
}
- (void)handleMediaItemAddedEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event);
const vlc_ml_media_t * const p_media = p_event->creation.p_media;
NSParameterAssert(p_media);
if (p_media->i_type == VLC_ML_MEDIA_TYPE_AUDIO || p_media->i_type == VLC_ML_MEDIA_TYPE_UNKNOWN) {
[self resetCachedListOfAudioMedia];
}
if (p_media->i_type == VLC_ML_MEDIA_TYPE_VIDEO || p_media->i_type == VLC_ML_MEDIA_TYPE_UNKNOWN) {
[self resetCachedListOfVideoMedia];
if (p_media->i_subtype == VLC_ML_MEDIA_SUBTYPE_SHOW_EPISODE) {
[self resetCachedListOfShows];
}
}
}
- (size_t)numberOfPlaylistsOfType:(const enum vlc_ml_playlist_type_t)playlistType
{
const vlc_ml_query_params_t queryParams = self.queryParams;
return vlc_ml_count_playlists(_p_mediaLibrary, &queryParams, playlistType);
}
- (nullable NSArray<VLCMediaLibraryPlaylist *> *)listOfPlaylistsOfType:(const enum vlc_ml_playlist_type_t)playlistType
{
const vlc_ml_query_params_t queryParams = self.queryParams;
vlc_ml_playlist_list_t * const p_playlistList =
vlc_ml_list_playlists(_p_mediaLibrary, &queryParams, playlistType);
if (p_playlistList == NULL) {
return nil;
}
NSMutableArray * const mutableArray =
[[NSMutableArray alloc] initWithCapacity:p_playlistList->i_nb_items];
for (size_t x = 0; x < p_playlistList->i_nb_items; x++) {
VLCMediaLibraryPlaylist * const playlist =
[[VLCMediaLibraryPlaylist alloc] initWithPlaylist:&p_playlistList->p_items[x]];
if (playlist) {
[mutableArray addObject:playlist];
}
}
vlc_ml_playlist_list_release(p_playlistList);
return mutableArray.copy;
}
- (void)resetCachedListOfMonitoredFolders
{
const NSUInteger generation = self.cacheGeneration;
NSObject * const token = [self beginCacheUpdateForGetter:@selector(cachedListOfMonitoredFolders)];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
vlc_ml_folder_list_t *pp_entrypoints = vlc_ml_list_entry_points(self->_p_mediaLibrary, NULL);
if (pp_entrypoints == NULL) {
msg_Err(getIntf(), "failed to retrieve list of monitored library folders");
return;
}
NSMutableArray * const mutableArray =
[[NSMutableArray alloc] initWithCapacity:pp_entrypoints->i_nb_items];
for (size_t x = 0; x < pp_entrypoints->i_nb_items; x++) {
VLCMediaLibraryEntryPoint * const entryPoint =
[[VLCMediaLibraryEntryPoint alloc] initWithEntryPoint:&pp_entrypoints->p_items[x]];
if (entryPoint) {
[mutableArray addObject:entryPoint];
}
}
vlc_ml_folder_list_release(pp_entrypoints);
[self performCacheWriteForGeneration:generation
token:token
getter:@selector(cachedListOfMonitoredFolders)
onQueue:self->_mediaItemCacheModificationQueue
block:^{
// Stop old observers before creating new ones to avoid duplicate
// FSEvent streams for the same paths
self.folderObservers = nil;
NSMutableArray * const mutableObservers =
[[NSMutableArray alloc] initWithCapacity:mutableArray.count];
for (VLCMediaLibraryEntryPoint * const entryPoint in mutableArray) {
NSURL * const url = [NSURL URLWithString:entryPoint.MRL];
VLCMediaLibraryFolderObserver * const observer =
[[VLCMediaLibraryFolderObserver alloc] initWithURL:url];
if (observer) {
[mutableObservers addObject:observer];
}
}
self->_cachedListOfMonitoredFolders = mutableArray.copy;
self.folderObservers = mutableObservers.copy;
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelListOfMonitoredFoldersUpdated withObject:self];
}];
}];
});
}
- (NSArray<VLCMediaLibraryEntryPoint *> *)listOfMonitoredFolders
{
NSArray<VLCMediaLibraryEntryPoint *> * const cache = self.cachedListOfMonitoredFolders;
if (cache == nil) {
[self resetCachedListOfMonitoredFolders];
}
return cache;
}
- (nullable NSArray <VLCMediaLibraryAlbum *>*)listAlbumsOfParentType:(const enum vlc_ml_parent_type)parentType forID:(int64_t)ID
{
const vlc_ml_query_params_t queryParams = [self queryParams];
vlc_ml_album_list_t *p_albumList = vlc_ml_list_albums_of(_p_mediaLibrary, &queryParams, parentType, ID);
if (p_albumList == NULL) {
return nil;
}
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:p_albumList->i_nb_items];
for (size_t x = 0; x < p_albumList->i_nb_items; x++) {
VLCMediaLibraryAlbum *album = [[VLCMediaLibraryAlbum alloc] initWithAlbum:&p_albumList->p_items[x]];
[mutableArray addObject:album];
}
vlc_ml_album_list_release(p_albumList);
return [mutableArray copy];
}
- (NSArray<id<VLCMediaLibraryItemProtocol>> *)listOfLibraryItemsOfParentType:(const VLCMediaLibraryParentGroupType)parentType
{
switch(parentType) {
case VLCMediaLibraryParentGroupTypeAllFavorites:
{
const vlc_ml_query_params_t queryParams = [self queryParams];
vlc_ml_media_list_t * const p_media_list = vlc_ml_list_favorite_media(_p_mediaLibrary, &queryParams);
NSArray * const mediaArray = [NSArray arrayFromVlcMediaList:p_media_list];
if (p_media_list) {
vlc_ml_media_list_release(p_media_list);
}
return mediaArray ?: @[];
}
case VLCMediaLibraryParentGroupTypeArtist:
return self.listOfArtists;
case VLCMediaLibraryParentGroupTypeAlbum:
return self.listOfAlbums;
case VLCMediaLibraryParentGroupTypeGenre:
return self.listOfGenres;
case VLCMediaLibraryParentGroupTypeAudioLibrary:
return self.listOfAudioMedia;
case VLCMediaLibraryParentGroupTypeVideoLibrary:
return self.listOfVideoMedia;
case VLCMediaLibraryParentGroupTypeRecentVideos:
return self.listOfRecentMedia;
case VLCMediaLibraryParentGroupTypeUnknown:
default:
return nil;
}
}
- (NSArray<VLCMediaLibraryMediaItem *> *)listOfMediaItemsForParentType:(const VLCMediaLibraryParentGroupType)parentType
{
NSArray<id<VLCMediaLibraryItemProtocol>> * const libraryItems = [self listOfLibraryItemsOfParentType:parentType];
NSMutableArray<VLCMediaLibraryMediaItem *> * const mediaItems = [[NSMutableArray alloc] initWithCapacity:self.numberOfAudioMedia];
for (id<VLCMediaLibraryItemProtocol> libraryItem in libraryItems) {
[mediaItems addObjectsFromArray:libraryItem.mediaItems];
}
return mediaItems.copy;
}
- (void)sortByCriteria:(enum vlc_ml_sorting_criteria_t)sortCriteria andDescending:(bool)descending
{
if(sortCriteria == _sortCriteria && descending == _sortDescending) {
return;
}
_sortCriteria = sortCriteria;
_sortDescending = descending;
[self dropCaches];
}
- (void)setFilterString:(NSString *)filterString
{
if([filterString isEqualToString:_filterString]) {
return;
}
_filterString = filterString;
[self dropCaches];
}
- (void)dropCaches
{
@synchronized (_cacheUpdateTokens) {
self.cacheGeneration++;
[_cacheUpdateTokens removeAllObjects];
[_pendingCacheUpdates removeAllObjects];
}
dispatch_queue_t const cacheQueues[] = {
_mediaItemCacheModificationQueue,
_albumCacheModificationQueue,
_artistCacheModificationQueue,
_genreCacheModificationQueue,
_groupCacheModificationQueue,
_mediaTitlesCacheModificationQueue,
};
const NSUInteger cacheQueueCount = sizeof(cacheQueues) / sizeof(cacheQueues[0]);
dispatch_group_t const cacheDropGroup = dispatch_group_create();
for (NSUInteger index = 0; index < cacheQueueCount; index++) {
dispatch_group_enter(cacheDropGroup);
}
self.cachedVideoMedia = nil;
self.cachedAudioMedia = nil;
self.cachedRecentMedia = nil;
self.cachedRecentAudioMedia = nil;
self.cachedListOfShows = nil;
self.cachedListOfMovies = nil;
self.cachedListOfMonitoredFolders = nil;
self.cachedAlbums = nil;
self.cachedArtists = nil;
self.cachedGenres = nil;
self.cachedListOfGroups = nil;
// Barrier sentinels complete after the setter writes on each queue.
for (NSUInteger index = 0; index < cacheQueueCount; index++) {
dispatch_barrier_async(cacheQueues[index], ^{
dispatch_group_leave(cacheDropGroup);
});
}
dispatch_group_notify(cacheDropGroup, _mediaTitlesCacheModificationQueue, ^{
[self performAfterCacheWritesOnQueue:self->_mediaTitlesCacheModificationQueue block:^{
[self.changeDelegate notifyChange:VLCLibraryModelAllCachesDropped withObject:self];
}];
});
}
- (void)performActionOnMediaItemInCache:(const int64_t)libraryId
action:(void (^)(
NSMutableArray * const,
const NSUInteger,
NSMutableArray * const,
const NSUInteger
))action
{
const NSUInteger generation = self.cacheGeneration;
dispatch_barrier_async(_mediaItemCacheModificationQueue, ^{
if (generation != self.cacheGeneration) {
return;
}
BOOL (^idCheckBlock)(VLCMediaLibraryMediaItem * const, const NSUInteger, BOOL * const) = ^BOOL(VLCMediaLibraryMediaItem * const mediaItem, const NSUInteger __unused idx, BOOL * const __unused stop) {
NSAssert(mediaItem != nil, @"Cache list should not contain nil media items");
return mediaItem.libraryID == libraryId;
};
NSArray<VLCMediaLibraryMediaItem *> * const cachedRecents = self->_cachedRecentMedia;
NSArray<VLCMediaLibraryMediaItem *> * const cachedVideos = self->_cachedVideoMedia;
const NSUInteger recentsIndex = cachedRecents ? [cachedRecents indexOfObjectPassingTest:idCheckBlock] : NSNotFound;
const NSUInteger videoIndex = cachedVideos ? [cachedVideos indexOfObjectPassingTest:idCheckBlock] : NSNotFound;
if (videoIndex != NSNotFound || recentsIndex != NSNotFound) {
// Create mutable copies for modification
NSMutableArray<VLCMediaLibraryMediaItem *> * const recentsMutable = cachedRecents.mutableCopy;
NSMutableArray<VLCMediaLibraryMediaItem *> * const videoMutable = cachedVideos.mutableCopy;
SEL const getters[] = { @selector(cachedVideoMedia), @selector(cachedRecentMedia) };
[self performCacheMutationForGetters:getters count:2 block:^{
action(videoMutable, videoIndex, recentsMutable, recentsIndex);
// Item handlers adjust title counts individually inside this barrier.
self->_cachedVideoMedia = videoMutable.copy;
self->_cachedRecentMedia = recentsMutable.copy;
}];
return;
}
// Not in either video cache, check the audio caches.
NSArray<VLCMediaLibraryMediaItem *> * const cachedRecentAudios = self->_cachedRecentAudioMedia;
NSArray<VLCMediaLibraryMediaItem *> * const cachedAudios = self->_cachedAudioMedia;
const NSUInteger recentAudiosIndex = cachedRecentAudios ? [cachedRecentAudios indexOfObjectPassingTest:idCheckBlock] : NSNotFound;
const NSUInteger audioIndex = cachedAudios ? [cachedAudios indexOfObjectPassingTest:idCheckBlock] : NSNotFound;
if (audioIndex != NSNotFound || recentAudiosIndex != NSNotFound) {
// Found in an audio cache - create mutable copies for modification.
NSMutableArray<VLCMediaLibraryMediaItem *> * const recentAudiosMutable = cachedRecentAudios.mutableCopy;
NSMutableArray<VLCMediaLibraryMediaItem *> * const audioMutable = cachedAudios.mutableCopy;
SEL const getters[] = { @selector(cachedAudioMedia), @selector(cachedRecentAudioMedia) };
[self performCacheMutationForGetters:getters count:2 block:^{
action(audioMutable, audioIndex, recentAudiosMutable, recentAudiosIndex);
self->_cachedAudioMedia = audioMutable.copy;
self->_cachedRecentAudioMedia = recentAudiosMutable.copy;
}];
return;
}
// An ongoing medialibrary fetch may have retrieved this item without
// writing it to the cache yet. Restart pending fetches so an outdated
// result cannot restore a deleted item or overwrite an updated one.
// Its media type is unknown here, so check all four caches, but only
// restart fetches already in progress
const SEL getters[] = {
@selector(cachedAudioMedia),
@selector(cachedRecentAudioMedia),
@selector(cachedVideoMedia),
@selector(cachedRecentMedia),
};
[self performCacheMutationForGetters:getters count:4 block:^{
action(nil, NSNotFound, nil, NSNotFound);
}];
});
}
- (BOOL)shouldReloadCacheForUpdatedItem:(id<VLCMediaLibraryItemProtocol>)item
previousItem:(id<VLCMediaLibraryItemProtocol>)previousItem
{
if (previousItem == nil || item.class != previousItem.class ||
item.libraryID != previousItem.libraryID)
return YES;
if ([item isKindOfClass:VLCMediaLibraryGenre.class]) {
// Genre queries search and sort by name, regardless of sort criterion.
return ![((VLCMediaLibraryGenre *)item).name isEqualToString:((VLCMediaLibraryGenre *)previousItem).name];
}
if ([item isKindOfClass:VLCMediaLibraryArtist.class]) {
VLCMediaLibraryArtist * const artist = (id)item;
VLCMediaLibraryArtist * const previousArtist = (id)previousItem;
if (![artist.name isEqualToString:previousArtist.name] ||
(artist.numberOfPresentTracks > 0) != (previousArtist.numberOfPresentTracks > 0) ||
(artist.numberOfTracks > 0) != (previousArtist.numberOfTracks > 0))
return YES;
switch (_sortCriteria) {
case VLC_ML_SORTING_DEFAULT:
case VLC_ML_SORTING_ALPHA:
return NO;
case VLC_ML_SORTING_TRACKNUMBER:
return artist.numberOfTracks != previousArtist.numberOfTracks;
default:
return YES;
}
}
if ([item isKindOfClass:VLCMediaLibraryAlbum.class]) {
VLCMediaLibraryAlbum * const album = (id)item;
VLCMediaLibraryAlbum * const previousAlbum = (id)previousItem;
if (![album.title isEqualToString:previousAlbum.title] ||
(album.numberOfPresentTracks > 0) != (previousAlbum.numberOfPresentTracks > 0) ||
(_filterString.length > 0 && ![album.artistName isEqualToString:previousAlbum.artistName]))
return YES;
switch (_sortCriteria) {
case VLC_ML_SORTING_DEFAULT:
case VLC_ML_SORTING_ALPHA:
return ![album.artistName isEqualToString:previousAlbum.artistName];
case VLC_ML_SORTING_DURATION:
return album.duration != previousAlbum.duration;
case VLC_ML_SORTING_RELEASEDATE:
return album.year != previousAlbum.year;
case VLC_ML_SORTING_ARTIST:
return ![album.artistName isEqualToString:previousAlbum.artistName];
case VLC_ML_SORTING_TRACKNUMBER:
return album.numberOfTracks != previousAlbum.numberOfTracks;
default:
// Aggregate play counts and insertion dates are not in the snapshot.
return YES;
}
}
if ([item isKindOfClass:VLCMediaLibraryGroup.class]) {
VLCMediaLibraryGroup * const group = (id)item;
VLCMediaLibraryGroup * const previousGroup = (id)previousItem;
if (![group.name isEqualToString:previousGroup.name] ||
(group.numberOfPresentTotalItems > 0) != (previousGroup.numberOfPresentTotalItems > 0))
return YES;
switch (_sortCriteria) {
case VLC_ML_SORTING_DEFAULT:
case VLC_ML_SORTING_ALPHA:
return NO;
case VLC_ML_SORTING_DURATION:
return group.duration != previousGroup.duration;
case VLC_ML_SORTING_INSERTIONDATE:
return ![group.creationDate isEqualToDate:previousGroup.creationDate];
case VLC_ML_SORTING_LASTMODIFICATIONDATE:
return ![group.lastModificationDate isEqualToDate:previousGroup.lastModificationDate];
default:
return YES;
}
}
// Media labels are lazy lookups, not snapshots of the previous search data.
// Retain full queries where membership or sort keys cannot be compared safely.
return _filterString.length > 0 ||
(_sortCriteria != VLC_ML_SORTING_DEFAULT && _sortCriteria != VLC_ML_SORTING_ALPHA) ||
![previousItem.displayString isEqualToString:item.displayString];
}
- (void)handleMediaItemUpdateEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
VLCMediaLibraryMediaItem * const mediaItem = [VLCMediaLibraryMediaItem mediaItemForLibraryID:itemId];
if (mediaItem == nil) {
NSLog(@"Could not find a library media item with this ID. Can't handle update.");
return;
}
[self performActionOnMediaItemInCache:itemId action:^(
NSMutableArray * const cachedMediaArray,
const NSUInteger cachedMediaIndex,
NSMutableArray * const recentMediaArray,
const NSUInteger recentMediaIndex
) {
[self refreshCachedShowForEpisodeID:itemId];
id<VLCMediaLibraryItemProtocol> const previousItem =
cachedMediaIndex == NSNotFound ? nil : cachedMediaArray[cachedMediaIndex];
const BOOL audio = mediaItem.mediaType == VLC_ML_MEDIA_TYPE_AUDIO;
NSArray * const mainCache = audio ? self->_cachedAudioMedia : self->_cachedVideoMedia;
NSArray * const recentCache = audio ? self->_cachedRecentAudioMedia : self->_cachedRecentMedia;
const BOOL reloadMain = mainCache != nil &&
((audio ? self->_audioCacheNeedsReload : self->_videoCacheNeedsReload) ||
[self shouldReloadCacheForUpdatedItem:mediaItem previousItem:previousItem]);
const BOOL reloadRecent = recentCache != nil && self->_filterString.length > 0;
if (reloadMain) {
if (audio) {
self->_audioCacheNeedsReload = YES;
[self resetCachedListOfAudioMedia];
} else {
self->_videoCacheNeedsReload = YES;
[self resetCachedListOfVideoMedia];
}
}
if (reloadRecent) {
if (audio)
[self resetCachedListOfRecentAudioMedia];
else
[self resetCachedListOfRecentMedia];
}
if (cachedMediaIndex == NSNotFound && recentMediaIndex == NSNotFound) {
if (!reloadMain && !reloadRecent)
NSLog(@"Could not handle update for media library item with id %lld in model", itemId);
return;
}
// Notify what happened
if (cachedMediaIndex != NSNotFound && !reloadMain) {
[self replaceMediaTitle:((VLCMediaLibraryMediaItem *)cachedMediaArray[cachedMediaIndex]).displayString
withTitle:mediaItem.displayString];
[cachedMediaArray replaceObjectAtIndex:cachedMediaIndex withObject:mediaItem];
}
if (recentMediaArray != nil && recentMediaIndex != NSNotFound && !reloadRecent) {
[recentMediaArray replaceObjectAtIndex:recentMediaIndex withObject:mediaItem];
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
switch (mediaItem.mediaType) {
case VLC_ML_MEDIA_TYPE_VIDEO:
[self.changeDelegate notifyChange:VLCLibraryModelRecentsMediaItemUpdated
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_AUDIO:
[self.changeDelegate notifyChange:VLCLibraryModelRecentAudioMediaItemUpdated
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_UNKNOWN:
NSLog(@"Unknown type of media type encountered, don't know what to do in deletion");
break;
}
}];
}
if (reloadMain) {
return;
}
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
switch (mediaItem.mediaType) {
case VLC_ML_MEDIA_TYPE_VIDEO:
[self.changeDelegate notifyChange:VLCLibraryModelVideoMediaItemUpdated
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_AUDIO:
[self.changeDelegate notifyChange:VLCLibraryModelAudioMediaItemUpdated
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_UNKNOWN:
NSLog(@"Unknown type of media type encountered, don't know what to do in update");
break;
}
}];
}];
}
- (void)handleMediaItemDeletionEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
[self performActionOnMediaItemInCache:itemId action:^(
NSMutableArray * const cachedMediaArray,
const NSUInteger cachedMediaIndex,
NSMutableArray * const recentMediaArray,
const NSUInteger recentMediaIndex
) {
[self refreshCachedShowForEpisodeID:itemId];
[self->_showIDsByEpisodeID removeObjectForKey:@(itemId)];
// A deletion also supersedes any snapshot fetched before it. Preserve
// pending membership/order changes by querying the latest state again.
if (self->_audioCacheNeedsReload && self->_cachedAudioMedia != nil)
[self resetCachedListOfAudioMedia];
if (self->_videoCacheNeedsReload && self->_cachedVideoMedia != nil)
[self resetCachedListOfVideoMedia];
if (cachedMediaIndex == NSNotFound && recentMediaIndex == NSNotFound) {
NSLog(@"Could not handle deletion for media library item with id %lld in model", itemId);
return;
}
VLCMediaLibraryMediaItem * const mediaItem = cachedMediaIndex != NSNotFound
? cachedMediaArray[cachedMediaIndex] : recentMediaArray[recentMediaIndex];
// Notify what happened
if (cachedMediaIndex != NSNotFound) {
[self replaceMediaTitle:mediaItem.displayString withTitle:nil];
[cachedMediaArray removeObjectAtIndex:cachedMediaIndex];
}
if (recentMediaArray != nil && recentMediaIndex != NSNotFound) {
[recentMediaArray removeObjectAtIndex:recentMediaIndex];
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
switch (mediaItem.mediaType) {
case VLC_ML_MEDIA_TYPE_VIDEO:
[self.changeDelegate notifyChange:VLCLibraryModelRecentsMediaItemDeleted
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_AUDIO:
[self.changeDelegate notifyChange:VLCLibraryModelRecentAudioMediaItemDeleted
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_UNKNOWN:
NSLog(@"Unknown type of media type encountered, don't know what to do in deletion");
break;
}
}];
}
[self performAfterCacheWritesOnQueue:self->_mediaItemCacheModificationQueue block:^{
switch (mediaItem.mediaType) {
case VLC_ML_MEDIA_TYPE_VIDEO:
[self.changeDelegate notifyChange:VLCLibraryModelVideoMediaItemDeleted
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_AUDIO:
[self.changeDelegate notifyChange:VLCLibraryModelAudioMediaItemDeleted
withObject:mediaItem];
break;
case VLC_ML_MEDIA_TYPE_UNKNOWN:
NSLog(@"Unknown type of media type encountered, don't know what to do in deletion");
break;
}
}];
}];
}
- (NSInteger)indexForAudioGroupInCache:(NSArray * const)cache withItemId:(const int64_t)itemId
{
if (cache == nil) {
return NSNotFound;
}
return [cache indexOfObjectPassingTest:^BOOL(id<VLCMediaLibraryAudioGroupProtocol> audioGroupItem, const NSUInteger __unused idx, BOOL * const __unused stop) {
NSAssert(audioGroupItem != nil, @"Cache list should not contain nil audio group items");
return audioGroupItem.libraryID == itemId;
}];
}
- (void)updateAudioGroupItem:(const id<VLCMediaLibraryAudioGroupProtocol>)audioGroupItem
usingGetter:(NSArray *(^)(void))cacheGetter
forCache:(SEL)getter
usingSetter:(void (^)(NSArray *))cacheSetter
usingRefresh:(dispatch_block_t)cacheRefresh
reloadPending:(BOOL *)reloadPending
usingQueue:(const dispatch_queue_t)queue
withNotificationName:(const NSNotificationName)notificationName
{
NSParameterAssert(cacheGetter != nil);
NSParameterAssert(cacheSetter != nil);
NSParameterAssert(cacheRefresh != nil);
NSParameterAssert(reloadPending != NULL);
const int64_t itemId = audioGroupItem.libraryID;
dispatch_barrier_async(queue, ^{
NSArray * const cache = cacheGetter();
if (cache == nil) {
return;
}
const NSUInteger audioGroupIndex = [self indexForAudioGroupInCache:cache
withItemId:itemId];
id<VLCMediaLibraryItemProtocol> const previousItem =
audioGroupIndex == NSNotFound ? nil : cache[audioGroupIndex];
if (*reloadPending || [self shouldReloadCacheForUpdatedItem:audioGroupItem previousItem:previousItem]) {
*reloadPending = YES;
cacheRefresh();
return;
}
// Create mutable copy for modification
NSMutableArray * const mutableAudioGroupCache = [cache mutableCopy];
[mutableAudioGroupCache replaceObjectAtIndex:audioGroupIndex withObject:audioGroupItem];
// Complete the read-modify-write within this barrier, before another
// event can take a snapshot of the same cache.
[self performCacheMutationForGetter:getter block:^{
cacheSetter(mutableAudioGroupCache.copy);
}];
[self performAfterCacheWritesOnQueue:queue block:^{
[self.changeDelegate notifyChange:notificationName withObject:audioGroupItem];
}];
});
}
- (void)deleteAudioGroupItemWithId:(const int64_t)itemId
usingGetter:(NSArray *(^)(void))cacheGetter
forCache:(SEL)getter
usingSetter:(void (^)(NSArray *))cacheSetter
usingRefresh:(dispatch_block_t)cacheRefresh
reloadPending:(BOOL *)reloadPending
usingQueue:(const dispatch_queue_t)queue
withNotificationName:(const NSNotificationName)notificationName
{
NSParameterAssert(cacheGetter != nil);
NSParameterAssert(cacheSetter != nil);
NSParameterAssert(cacheRefresh != nil);
NSParameterAssert(reloadPending != NULL);
dispatch_barrier_async(queue, ^{
[self performCacheMutationForGetter:getter block:^{
NSArray * const cache = cacheGetter();
if (cache == nil) {
return;
}
if (*reloadPending) {
cacheRefresh();
return;
}
const NSUInteger audioGroupIndex = [self indexForAudioGroupInCache:cache
withItemId:itemId];
if (audioGroupIndex == NSNotFound) {
NSLog(@"Did not find audio group item with id %lli in cache.", itemId);
return;
}
const id<VLCMediaLibraryAudioGroupProtocol> audioGroupItem = cache[audioGroupIndex];
// Create mutable copy for modification
NSMutableArray * const mutableAudioGroupCache = [cache mutableCopy];
[mutableAudioGroupCache removeObjectAtIndex:audioGroupIndex];
cacheSetter(mutableAudioGroupCache.copy);
[self performAfterCacheWritesOnQueue:queue block:^{
[self.changeDelegate notifyChange:notificationName withObject:audioGroupItem];
}];
}];
});
}
- (void)handleAlbumUpdateEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
VLCMediaLibraryAlbum * const album = [VLCMediaLibraryAlbum albumWithID:itemId];
if (album == nil) {
NSLog(@"Could not find a library album with this ID. Can't handle update.");
return;
}
[self updateAudioGroupItem:album
usingGetter:^NSArray *{ return self->_cachedAlbums; }
forCache:@selector(cachedAlbums)
usingSetter:^(NSArray *cache) {
self->_cachedAlbums = cache;
}
usingRefresh:^{ [self resetCachedListOfAlbums]; }
reloadPending:&_albumCacheNeedsReload
usingQueue:_albumCacheModificationQueue
withNotificationName:VLCLibraryModelAlbumUpdated];
}
- (void)handleAlbumDeletionEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
[self deleteAudioGroupItemWithId:itemId
usingGetter:^NSArray *{ return self->_cachedAlbums; }
forCache:@selector(cachedAlbums)
usingSetter:^(NSArray *cache) {
self->_cachedAlbums = cache;
}
usingRefresh:^{ [self resetCachedListOfAlbums]; }
reloadPending:&_albumCacheNeedsReload
usingQueue:_albumCacheModificationQueue
withNotificationName:VLCLibraryModelAlbumDeleted];
}
- (void)handleArtistUpdateEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
VLCMediaLibraryArtist * const artist = [VLCMediaLibraryArtist artistWithID:itemId];
if (artist == nil) {
NSLog(@"Could not find a library artist with this ID. Can't handle update.");
return;
}
[self updateAudioGroupItem:artist
usingGetter:^NSArray *{ return self->_cachedArtists; }
forCache:@selector(cachedArtists)
usingSetter:^(NSArray *cache) {
self->_cachedArtists = cache;
}
usingRefresh:^{ [self resetCachedListOfArtists]; }
reloadPending:&_artistCacheNeedsReload
usingQueue:_artistCacheModificationQueue
withNotificationName:VLCLibraryModelArtistUpdated];
}
- (void)handleArtistDeletionEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
[self deleteAudioGroupItemWithId:itemId
usingGetter:^NSArray *{ return self->_cachedArtists; }
forCache:@selector(cachedArtists)
usingSetter:^(NSArray *cache) {
self->_cachedArtists = cache;
}
usingRefresh:^{ [self resetCachedListOfArtists]; }
reloadPending:&_artistCacheNeedsReload
usingQueue:_artistCacheModificationQueue
withNotificationName:VLCLibraryModelArtistDeleted];
}
- (void)handleGenreUpdateEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
VLCMediaLibraryGenre * const genre = [VLCMediaLibraryGenre genreWithID:itemId];
if (genre == nil) {
NSLog(@"Could not find a library genre with this ID. Can't handle update.");
return;
}
[self updateAudioGroupItem:genre
usingGetter:^NSArray *{ return self->_cachedGenres; }
forCache:@selector(cachedGenres)
usingSetter:^(NSArray *cache) {
self->_cachedGenres = cache;
}
usingRefresh:^{ [self resetCachedListOfGenres]; }
reloadPending:&_genreCacheNeedsReload
usingQueue:_genreCacheModificationQueue
withNotificationName:VLCLibraryModelGenreUpdated];
}
- (void)handleGenreDeletionEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
[self deleteAudioGroupItemWithId:itemId
usingGetter:^NSArray *{ return self->_cachedGenres; }
forCache:@selector(cachedGenres)
usingSetter:^(NSArray *cache) {
self->_cachedGenres = cache;
}
usingRefresh:^{ [self resetCachedListOfGenres]; }
reloadPending:&_genreCacheNeedsReload
usingQueue:_genreCacheModificationQueue
withNotificationName:VLCLibraryModelGenreDeleted];
}
- (void)handleGroupDeletionEvent:(const vlc_ml_event_t *const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
dispatch_barrier_async(_groupCacheModificationQueue, ^{
[self performCacheMutationForGetter:@selector(cachedListOfGroups) block:^{
NSArray<VLCMediaLibraryGroup *> * const cachedGroups = self->_cachedListOfGroups;
if (cachedGroups == nil) {
return;
}
const NSUInteger groupIdx =
[cachedGroups indexOfObjectPassingTest:^BOOL(VLCMediaLibraryGroup * const group,
const NSUInteger __unused idx,
BOOL * const __unused stop) {
return group.libraryID == itemId;
}];
if (groupIdx == NSNotFound) {
NSLog(@"Could not handle deletion of group with id %lld in model", itemId);
return;
}
VLCMediaLibraryGroup * const groupToDelete = cachedGroups[groupIdx];
NSMutableArray * const mutableGroups = cachedGroups.mutableCopy;
[mutableGroups removeObjectAtIndex:groupIdx];
self->_cachedListOfGroups = mutableGroups.copy;
[self performAfterCacheWritesOnQueue:self->_groupCacheModificationQueue block:^{
[self->_defaultNotificationCenter postNotificationName:VLCLibraryModelGroupDeleted
object:groupToDelete];
}];
}];
});
}
- (void)handleGroupUpdateEvent:(const vlc_ml_event_t *const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
VLCMediaLibraryGroup * const group = [VLCMediaLibraryGroup groupWithID:itemId];
if (group == nil) {
NSLog(@"Could not find a library group with this ID. Can't handle update.");
return;
}
dispatch_barrier_async(_groupCacheModificationQueue, ^{
[self performCacheMutationForGetter:@selector(cachedListOfGroups) block:^{
NSArray<VLCMediaLibraryGroup *> * const cachedGroups = self->_cachedListOfGroups;
if (cachedGroups == nil) {
return;
}
const NSUInteger groupIdx =
[cachedGroups indexOfObjectPassingTest:^BOOL(VLCMediaLibraryGroup * const group,
const NSUInteger __unused idx,
BOOL * const __unused stop) {
NSAssert(group != nil, @"Cache list should not contain nil groups");
return group.libraryID == itemId;
}];
VLCMediaLibraryGroup * const previousGroup = groupIdx == NSNotFound ? nil : cachedGroups[groupIdx];
if ([self shouldReloadCacheForUpdatedItem:group previousItem:previousGroup]) {
[self resetCachedListOfGroups];
return;
}
NSMutableArray * const mutableGroups = cachedGroups.mutableCopy;
[mutableGroups replaceObjectAtIndex:groupIdx withObject:group];
self->_cachedListOfGroups = mutableGroups.copy;
[self performAfterCacheWritesOnQueue:self->_groupCacheModificationQueue block:^{
[self->_defaultNotificationCenter postNotificationName:VLCLibraryModelGroupUpdated
object:group];
}];
}];
});
}
- (void)handlePlaylistAddedEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const vlc_ml_playlist_t * const p_playlist = p_event->creation.p_playlist;
VLCMediaLibraryPlaylist * const playlist =
[[VLCMediaLibraryPlaylist alloc] initWithPlaylist:p_playlist];
if (playlist == nil) {
NSLog(@"Could not find a library playlist with this ID. Can't handle update.");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self->_defaultNotificationCenter postNotificationName:VLCLibraryModelPlaylistAdded
object:playlist];
});
}
- (void)handlePlaylistUpdateEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
VLCMediaLibraryPlaylist * const playlist = [VLCMediaLibraryPlaylist playlistForLibraryID:itemId];
if (playlist == nil) {
NSLog(@"Could not find a library playlist with this ID. Can't handle update.");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self->_defaultNotificationCenter postNotificationName:VLCLibraryModelPlaylistUpdated
object:playlist];
});
}
- (void)handlePlaylistDeletionEvent:(const vlc_ml_event_t * const)p_event
{
NSParameterAssert(p_event != NULL);
const int64_t itemId = p_event->modification.i_entity_id;
dispatch_async(dispatch_get_main_queue(), ^{
[self->_defaultNotificationCenter postNotificationName:VLCLibraryModelPlaylistDeleted
object:@(itemId)];
});
}
#pragma mark - Favorites Support
- (NSArray<VLCMediaLibraryMediaItem *> *)listOfFavoriteAudioMedia
{
const vlc_ml_query_params_t queryParams = [self queryParams];
vlc_ml_media_list_t * const p_media_list = vlc_ml_list_favorite_audios(_p_mediaLibrary, &queryParams);
NSArray * const mediaArray = [NSArray arrayFromVlcMediaList:p_media_list];
if (mediaArray == nil) {
return @[];
}
vlc_ml_media_list_release(p_media_list);
return mediaArray;
}
- (NSArray<VLCMediaLibraryMediaItem *> *)listOfFavoriteVideoMedia
{
const vlc_ml_query_params_t queryParams = [self queryParams];
vlc_ml_media_list_t * const p_media_list = vlc_ml_list_favorite_videos(_p_mediaLibrary, &queryParams);
NSArray * const mediaArray = [NSArray arrayFromVlcMediaList:p_media_list];
if (mediaArray == nil) {
return @[];
}
vlc_ml_media_list_release(p_media_list);
return mediaArray;
}
- (NSArray<VLCMediaLibraryAlbum *> *)listOfFavoriteAlbums
{
const vlc_ml_query_params_t queryParams = [self queryParams];
vlc_ml_album_list_t * const p_album_list = vlc_ml_list_favorite_albums(_p_mediaLibrary, &queryParams);
if (p_album_list == NULL) {
return @[];
}
NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:p_album_list->i_nb_items];
for (size_t x = 0; x < p_album_list->i_nb_items; x++) {
VLCMediaLibraryAlbum * const album = [[VLCMediaLibraryAlbum alloc] initWithAlbum:&p_album_list->p_items[x]];
if (album != nil) {
[mutableArray addObject:album];
}
}
vlc_ml_album_list_release(p_album_list);
return mutableArray.copy;
}
- (NSArray<VLCMediaLibraryArtist *> *)listOfFavoriteArtists
{
const vlc_ml_query_params_t queryParams = [self queryParams];
vlc_ml_artist_list_t * const p_artist_list = vlc_ml_list_favorite_artists(_p_mediaLibrary, &queryParams);
if (p_artist_list == NULL) {
return @[];
}
NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:p_artist_list->i_nb_items];
for (size_t x = 0; x < p_artist_list->i_nb_items; x++) {
VLCMediaLibraryArtist * const artist = [[VLCMediaLibraryArtist alloc] initWithArtist:&p_artist_list->p_items[x]];
if (artist != nil) {
[mutableArray addObject:artist];
}
}
vlc_ml_artist_list_release(p_artist_list);
return mutableArray.copy;
}
- (NSArray<VLCMediaLibraryGenre *> *)listOfFavoriteGenres
{
const vlc_ml_query_params_t queryParams = [self queryParams];
vlc_ml_genre_list_t * const p_genre_list = vlc_ml_list_favorite_genres(_p_mediaLibrary, &queryParams);
if (p_genre_list == NULL) {
return @[];
}
NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:p_genre_list->i_nb_items];
for (size_t x = 0; x < p_genre_list->i_nb_items; x++) {
VLCMediaLibraryGenre * const genre = [[VLCMediaLibraryGenre alloc] initWithGenre:&p_genre_list->p_items[x]];
if (genre != nil) {
[mutableArray addObject:genre];
}
}
vlc_ml_genre_list_release(p_genre_list);
return mutableArray.copy;
}
- (size_t)numberOfFavoriteAudioMedia
{
const vlc_ml_query_params_t queryParams = [self queryParams];
return vlc_ml_count_favorite_audios(_p_mediaLibrary, &queryParams);
}
- (size_t)numberOfFavoriteVideoMedia
{
const vlc_ml_query_params_t queryParams = [self queryParams];
return vlc_ml_count_favorite_videos(_p_mediaLibrary, &queryParams);
}
- (size_t)numberOfFavoriteAlbums
{
const vlc_ml_query_params_t queryParams = [self queryParams];
return vlc_ml_count_favorite_albums(_p_mediaLibrary, &queryParams);
}
- (size_t)numberOfFavoriteArtists
{
const vlc_ml_query_params_t queryParams = [self queryParams];
return vlc_ml_count_favorite_artists(_p_mediaLibrary, &queryParams);
}
- (size_t)numberOfFavoriteGenres
{
const vlc_ml_query_params_t queryParams = [self queryParams];
return vlc_ml_count_favorite_genres(_p_mediaLibrary, &queryParams);
}
@end