protected void SortFilterValuesList() { Sorting.Sorting sorting = CurrentSorting; if (sorting == null) { return; } lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } if (!_sortable) { return; } // Mark the list as being built _buildingList = true; _listDirty = false; } try { ItemsList items = _items; List <FilterItem> itemsList = items.Select(li => li as FilterItem).ToList(); itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); bool dirty; lock (_syncObj) dirty = _listDirty; if (dirty) { UpdateOrRebuildView(items, false); return; } items.Clear(); CollectionUtils.AddAll(items, itemsList); UpdateOrRebuildView(items, false); } finally { lock (_syncObj) _buildingList = false; } }
/// <summary> /// Updates the GUI data for a filter values selection screen which reflects the available filter values for /// the current base view specification of our <see cref="AbstractScreenData._navigationData"/>. /// </summary> protected async Task ReloadFilterValuesList(bool createNewList) { MediaLibraryQueryViewSpecification currentVS = _navigationData.BaseViewSpecification as MediaLibraryQueryViewSpecification; if (currentVS == null) { // Should never happen ServiceRegistration.Get <ILogger>().Error("FilterScreenData: Wrong type of media library view '{0}'", _navigationData.BaseViewSpecification); return; } // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built _buildingList = true; _listDirty = false; } try { ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { Display_ListBeingBuilt(); IFilter filter = currentVS.FilterTree.BuildFilter(_filterPath); ICollection <Guid> necessaryMIAs = _necessaryFilteredMIATypeIds ?? currentVS.NecessaryMIATypeIds; // If the number of values to create exceeds MAX_NUM_ITEMS_VISIBLE we need to try and group the items. // We request all values first, rather than groups, on the assumption that most of the time the limit // shouldn't be reached given that we are filtering the values. bool grouping = false; ICollection <FilterValue> fv = await _filterCriterion.GetAvailableValuesAsync(necessaryMIAs, _clusterFilter, filter).ConfigureAwait(false); if (fv.Count > Consts.MAX_NUM_ITEMS_VISIBLE && _clusterFilter == null) { ICollection <FilterValue> groupValues = await _filterCriterion.GroupValuesAsync(necessaryMIAs, _clusterFilter, filter).ConfigureAwait(false); if (groupValues != null && groupValues.Count > 0) { fv = groupValues; grouping = true; } } if (fv.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(fv.Count); } else { bool dirty; lock (_syncObj) dirty = _listDirty; if (dirty) { UpdateOrRebuildView(items, createNewList); return; } _sortable = true; int totalNumItems = 0; List <FilterItem> itemsList = new List <FilterItem>(); // Build collection of available (filter/display) screens which will remain in the next view - that is all currently // available screens without the screen which equals this current screen. But we cannot simply remove "this" // from the collection, because "this" could be a derived screen (in case our base screen showed groups). // So we need an equality criterion when the screen to be removed is equal to this screen in terms of its // filter criterion. But with the given data, we actually cannot derive that equality. // So we simply use the MenuItemLabel, which should be the same in this and the base screen of the same filter. foreach (FilterValue f in fv) { //Used for enclosure FilterValue filterValue = f; _sortable &= filterValue.Item != null; string filterTitle = filterValue.Title; IFilter selectAttributeFilter = filterValue.SelectAttributeFilter; MediaLibraryQueryViewSpecification subVS = currentVS.CreateSubViewSpecification(filterTitle, FilterTreePath.Combine(_filterPath, filterValue.RelativeFilterPath), filterValue.Filter, filterValue.LinkedId); T filterValueItem = new T { // Support non-playable MediaItems (i.e. Series, Seasons) MediaItem = filterValue.Item, SimpleTitle = filterTitle, NumItems = filterValue.NumItems, Id = filterValue.Id, Command = new MethodDelegateCommand(() => { if (grouping) { NavigateToGroup(subVS, selectAttributeFilter); } else { NavigateToSubView(subVS); } }), View = subVS.BuildView() }; itemsList.Add(filterValueItem); if (filterValue.NumItems.HasValue) { totalNumItems += filterValue.NumItems.Value; } } if (_sortable) { Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } } // Derived classes can implement special initial selection handling here, // e.g. the first unwatched episode could be selected from a list of episodes SetSelectedItem(itemsList); CollectionUtils.AddAll(items, itemsList); Display_Normal(items.Count, totalNumItems == 0 ? new int?() : totalNumItems); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractFiltersScreenData: Error creating filter values list", e); Display_ItemsInvalid(); } UpdateOrRebuildView(items, createNewList); } finally { lock (_syncObj) _buildingList = false; } }
/// <summary> /// Updates the GUI data for a media items view screen which reflects the data of the <see cref="CurrentView"/>. /// </summary> /// <param name="createNewList">If set to <c>true</c>, this method will re-create the /// <see cref="AbstractScreenData.Items"/> list, else it will reuse it.</param> protected void UpdateMediaItems(bool createNewList) { View view; // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built view = _view; InstallViewChangeNotificator(_view.CreateViewChangeNotificator()); _buildingList = true; _listDirty = false; } try { Display_ListBeingBuilt(); ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { // TODO: Add the items in a separate job while the UI already shows the new screen if (view.IsValid) { // Add items for sub views IList <View> subViews = view.SubViews; IList <MediaItem> mediaItems = view.MediaItems; lock (_syncObj) if (_listDirty) { goto RebuildView; } if (subViews == null || mediaItems == null) { Display_ItemsInvalid(); } else { if (subViews.Count + mediaItems.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(subViews.Count + mediaItems.Count); } else { int totalNumItems = 0; List <NavigationItem> viewsList = new List <NavigationItem>(); foreach (View sv in subViews) { ViewItem item = new ViewItem(sv, null, sv.AbsNumItems); View subView = sv; item.Command = new MethodDelegateCommand(() => NavigateToView(subView.Specification)); viewsList.Add(item); if (sv.AbsNumItems.HasValue) { totalNumItems += sv.AbsNumItems.Value; } } viewsList.Sort((v1, v2) => string.Compare(v1.SortString, v2.SortString)); CollectionUtils.AddAll(items, viewsList); lock (_syncObj) if (_listDirty) { goto RebuildView; } PlayableItemCreatorDelegate picd = PlayableItemCreator; List <PlayableMediaItem> itemsList = mediaItems.Select(childItem => picd(childItem)).Where(item => item != null).ToList(); Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } else { // Default sorting: Use SortString itemsList.Sort((i1, i2) => string.Compare(i1.SortString, i2.SortString)); } CollectionUtils.AddAll(items, itemsList); Display_Normal(items.Count, totalNumItems == 0 ? new int?() : totalNumItems); } } } else { Display_ItemsInvalid(); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractItemsScreenData: Error creating items list", e); Display_ItemsInvalid(); } RebuildView: bool dirty; lock (_syncObj) if (_listDirty) { dirty = true; _buildingList = false; } else { dirty = false; } if (dirty) { UpdateMediaItems(createNewList); } else { _items = items; _items.FireChange(); } } finally { lock (_syncObj) _buildingList = false; } }
/// <summary> /// Updates the GUI data for a media items view screen which reflects the data of the <see cref="CurrentView"/>. /// </summary> /// <param name="createNewList">If set to <c>true</c>, this method will re-create the /// <see cref="AbstractScreenData.Items"/> list, else it will reuse it.</param> protected void UpdateMediaItems(bool createNewList) { View view; // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built view = _view; InstallViewChangeNotificator(_view.CreateViewChangeNotificator()); _buildingList = true; _listDirty = false; } try { Display_ListBeingBuilt(); ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { // TODO: Add the items in a separate job while the UI already shows the new screen if (view.IsValid) { // Add items for sub views IList <View> subViews = view.SubViews; IList <MediaItem> mediaItems = view.MediaItems; lock (_syncObj) if (_listDirty) { goto RebuildView; } if (subViews == null || mediaItems == null) { Display_ItemsInvalid(); } else { if (subViews.Count + mediaItems.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(subViews.Count + mediaItems.Count); } else { int totalNumItems = 0; bool subViewsPreSorted = false; List <NavigationItem> viewsList = new List <NavigationItem>(); foreach (View sv in subViews) { if (sv.Specification.SortedSubViews) { subViewsPreSorted = true; } ViewItem item = new ViewItem(sv, null, sv.AbsNumItems); View subView = sv; item.Command = new MethodDelegateCommand(() => NavigateToView(subView.Specification)); viewsList.Add(item); if (sv.AbsNumItems.HasValue) { totalNumItems += sv.AbsNumItems.Value; } } // Morpheus_xx, 2014-05-03: Only sort the subviews here, if they are not pre-sorted by the ViewSpecification if (!subViewsPreSorted) { viewsList.Sort((v1, v2) => string.Compare(v1.SortString, v2.SortString)); } CollectionUtils.AddAll(items, viewsList); lock (_syncObj) if (_listDirty) { goto RebuildView; } PlayableItemCreatorDelegate picd = PlayableItemCreator; List <PlayableMediaItem> itemsList = mediaItems.Select(childItem => picd(childItem)).Where(item => item != null).ToList(); Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } else { // Default sorting: Use SortString itemsList.Sort((i1, i2) => string.Compare(i1.SortString, i2.SortString)); } // Derived classes can implement special initial selection handling here, // e.g. the first unwatched episode could be selected from a list of episodes SetSelectedItem(itemsList); CollectionUtils.AddAll(items, itemsList); // Support custom sorting logic by view specification. At this time it can work on both MediaItems and SubViews. if (view.Specification.CustomItemsListSorting != null) { view.Specification.CustomItemsListSorting(items, sorting); } _currentTotalNumItems = totalNumItems == 0 ? new int?() : totalNumItems; Display_Normal(items.Count, _currentTotalNumItems); } } } else { Display_ItemsInvalid(); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractItemsScreenData: Error creating items list", e); Display_ItemsInvalid(); } RebuildView: bool dirty; lock (_syncObj) if (_listDirty) { dirty = true; _buildingList = false; } else { dirty = false; } if (dirty) { UpdateMediaItems(createNewList); } else { _items = items; _items.FireChange(); } } finally { lock (_syncObj) _buildingList = false; } }
/// <summary> /// Updates the GUI data for a filter values selection screen which reflects the available filter values for /// the current base view specification of our <see cref="AbstractScreenData._navigationData"/>. /// </summary> protected void ReloadFilterValuesList(bool createNewList) { MediaLibraryQueryViewSpecification currentVS = _navigationData.BaseViewSpecification as MediaLibraryQueryViewSpecification; if (currentVS == null) { // Should never happen ServiceRegistration.Get <ILogger>().Error("FilterScreenData: Wrong type of media library view '{0}'", _navigationData.BaseViewSpecification); return; } // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built _buildingList = true; _listDirty = false; } try { ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { Display_ListBeingBuilt(); bool grouping = true; //If currentVS is the base view it's possible that it has a filter that is incompatible with _filterCriterion. //This is the case if a plugin has added a base filter to exclude certain items, e.g. TV excludes recordings //and the new filter filters by a different media type, e.g. series'. Ignore the base filter in this case. IFilter currentFilter = (_navigationData.Parent != null && CanFilter(_navigationData.Parent.CurrentScreenData)) || currentVS.CanCombineFilters(_filteredMias) ? currentVS.Filter : null; ICollection <FilterValue> fv = _clusterFilter == null? _filterCriterion.GroupValues(currentVS.NecessaryMIATypeIds, _clusterFilter, currentFilter) : null; if (fv == null || fv.Count <= Consts.MAX_NUM_ITEMS_VISIBLE) { fv = _filterCriterion.GetAvailableValues(currentVS.NecessaryMIATypeIds, _clusterFilter, currentFilter); grouping = false; } if (fv.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(fv.Count); } else { bool dirty; lock (_syncObj) dirty = _listDirty; if (dirty) { UpdateOrRebuildView(items, createNewList); return; } _sortable = true; int totalNumItems = 0; List <FilterItem> itemsList = new List <FilterItem>(); // Build collection of available (filter/display) screens which will remain in the next view - that is all currently // available screens without the screen which equals this current screen. But we cannot simply remove "this" // from the collection, because "this" could be a derived screen (in case our base screen showed groups). // So we need an equality criterion when the screen to be removed is equal to this screen in terms of its // filter criterion. But with the given data, we actually cannot derive that equality. // So we simply use the MenuItemLabel, which should be the same in this and the base screen of the same filter. foreach (FilterValue filterValue in fv) { _sortable &= filterValue.Item != null; string filterTitle = filterValue.Title; IFilter selectAttributeFilter = filterValue.SelectAttributeFilter; MediaLibraryQueryViewSpecification subVS = currentVS.CreateSubViewSpecification(filterTitle, filterValue.Filter, _filteredMias); T filterValueItem = new T { // Support non-playable MediaItems (i.e. Series, Seasons) MediaItem = filterValue.Item, SimpleTitle = filterTitle, NumItems = filterValue.NumItems, Id = filterValue.Id, Command = grouping ? new MethodDelegateCommand(() => NavigateToGroup(subVS, selectAttributeFilter)) : new MethodDelegateCommand(() => NavigateToSubView(subVS)) }; itemsList.Add(filterValueItem); if (filterValue.NumItems.HasValue) { totalNumItems += filterValue.NumItems.Value; } } if (_sortable) { Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } } CollectionUtils.AddAll(items, itemsList); Display_Normal(items.Count, totalNumItems == 0 ? new int?() : totalNumItems); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractFiltersScreenData: Error creating filter values list", e); Display_ItemsInvalid(); } UpdateOrRebuildView(items, createNewList); } finally { lock (_syncObj) _buildingList = false; } }