private async Task LoadContent(CancellationToken token = default) { token.ThrowIfCancellationRequested(); using (CustomActivity opLoadMasterindex = await Timekeeper.StartSyncronAsync("op_load_frm_masterindex", null, CustomActivity.OperationType.RequestOperation, null)) { bool blnOldIsFinishedLoading = IsFinishedLoading; try { IsFinishedLoading = false; await _dicCachedNotes.ClearAsync(token); foreach (MasterIndexEntry objExistingEntry in _lstItems.Select(x => x.Value)) { objExistingEntry.Dispose(); } _lstItems.Clear(); _lstFileNamesWithItems.Clear(); string strSourceFilter; using (new FetchSafelyFromPool <HashSet <string> >(Utils.StringHashSetPool, out HashSet <string> setValidCodes)) { foreach (XPathNavigator xmlBookNode in await(await XmlManager.LoadXPathAsync( "books.xml", _objSelectedSetting.EnabledCustomDataDirectoryPaths, token: token)) .SelectAndCacheExpressionAsync("/chummer/books/book/code")) { setValidCodes.Add(xmlBookNode.Value); } setValidCodes.IntersectWith(_objSelectedSetting.Books); strSourceFilter = setValidCodes.Count > 0 ? '(' + string.Join(" or ", setValidCodes.Select(x => "source = " + x.CleanXPath())) + ')' : "source"; } using (_ = await Timekeeper.StartSyncronAsync("load_frm_masterindex_load_andpopulate_entries", opLoadMasterindex)) { ConcurrentBag <ListItem> lstItemsForLoading = new ConcurrentBag <ListItem>(); using (_ = await Timekeeper.StartSyncronAsync("load_frm_masterindex_load_entries", opLoadMasterindex)) { ConcurrentBag <ListItem> lstFileNamesWithItemsForLoading = new ConcurrentBag <ListItem>(); // Prevents locking the UI thread while still benefiting from static scheduling of Parallel.ForEach await Task.WhenAll(_astrFileNames.Select(strFileName => Task.Run(async() => { XPathNavigator xmlBaseNode = await XmlManager.LoadXPathAsync(strFileName, _objSelectedSetting .EnabledCustomDataDirectoryPaths, token: token); xmlBaseNode = await xmlBaseNode.SelectSingleNodeAndCacheExpressionAsync("/chummer"); if (xmlBaseNode == null) { return; } bool blnLoopFileNameHasItems = false; foreach (XPathNavigator xmlItemNode in xmlBaseNode.Select( ".//*[page and " + strSourceFilter + ']')) { blnLoopFileNameHasItems = true; string strName = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("name")) ?.Value; string strDisplayName = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("translate")) ?.Value ?? strName ?? (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("id"))?.Value ?? await LanguageManager.GetStringAsync("String_Unknown"); string strSource = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("source"))?.Value; string strPage = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("page")) ?.Value; string strDisplayPage = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("altpage"))?.Value ?? strPage; string strEnglishNameOnPage = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("nameonpage")) ?.Value ?? strName; string strTranslatedNameOnPage = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("altnameonpage")) ?.Value ?? strDisplayName; string strNotes = (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("altnotes"))?.Value ?? (await xmlItemNode.SelectSingleNodeAndCacheExpressionAsync("notes")) ?.Value; MasterIndexEntry objEntry = new MasterIndexEntry( strDisplayName, strFileName, await SourceString.GetSourceStringAsync( strSource, strPage, GlobalSettings.DefaultLanguage, GlobalSettings.InvariantCultureInfo), await SourceString.GetSourceStringAsync( strSource, strDisplayPage, GlobalSettings.Language, GlobalSettings.CultureInfo), strEnglishNameOnPage, strTranslatedNameOnPage); lstItemsForLoading.Add(new ListItem(objEntry, strDisplayName)); if (!string.IsNullOrEmpty(strNotes)) { await _dicCachedNotes.TryAddAsync(objEntry, Task.FromResult(strNotes), token); } } if (blnLoopFileNameHasItems) { lstFileNamesWithItemsForLoading.Add(new ListItem(strFileName, strFileName)); } }, token))); _lstFileNamesWithItems.AddRange(lstFileNamesWithItemsForLoading); } using (_ = await Timekeeper.StartSyncronAsync("load_frm_masterindex_populate_entries", opLoadMasterindex)) { string strSpace = await LanguageManager.GetStringAsync("String_Space"); string strFormat = "{0}" + strSpace + "[{1}]"; Dictionary <string, List <ListItem> > dicHelper = new Dictionary <string, List <ListItem> >(lstItemsForLoading.Count); try { foreach (ListItem objItem in lstItemsForLoading) { if (!(objItem.Value is MasterIndexEntry objEntry)) { continue; } string strKey = objEntry.DisplayName.ToUpperInvariant(); if (dicHelper.TryGetValue(strKey, out List <ListItem> lstExistingItems)) { ListItem objExistingItem = lstExistingItems.Find( x => x.Value is MasterIndexEntry y && objEntry.DisplaySource.Equals(y.DisplaySource)); if (objExistingItem.Value is MasterIndexEntry objLoopEntry) { objLoopEntry.FileNames.UnionWith(objEntry.FileNames); objEntry.Dispose(); } else { using (new FetchSafelyFromPool <List <ListItem> >( Utils.ListItemListPool, out List <ListItem> lstItemsNeedingNameChanges)) { lstItemsNeedingNameChanges.AddRange(lstExistingItems.FindAll( x => x.Value is MasterIndexEntry y && !objEntry.FileNames.IsSubsetOf(y.FileNames))); if (lstItemsNeedingNameChanges.Count == 0) { _lstItems.Add( objItem); // Not using AddRange because of potential memory issues lstExistingItems.Add(objItem); } else { ListItem objItemToAdd = new ListItem( objItem.Value, string.Format(GlobalSettings.CultureInfo, strFormat, objItem.Name, string.Join( ',' + strSpace, objEntry.FileNames))); _lstItems.Add( objItemToAdd); // Not using AddRange because of potential memory issues lstExistingItems.Add(objItemToAdd); foreach (ListItem objToRename in lstItemsNeedingNameChanges) { _lstItems.Remove(objToRename); lstExistingItems.Remove(objToRename); if (!(objToRename.Value is MasterIndexEntry objExistingEntry)) { continue; } objItemToAdd = new ListItem(objToRename.Value, string.Format( GlobalSettings.CultureInfo, strFormat, objExistingEntry.DisplayName, string.Join( ',' + strSpace, objExistingEntry.FileNames))); _lstItems.Add( objItemToAdd); // Not using AddRange because of potential memory issues lstExistingItems.Add(objItemToAdd); } } } } } else { _lstItems.Add(objItem); // Not using AddRange because of potential memory issues List <ListItem> lstHelperItems = Utils.ListItemListPool.Get(); lstHelperItems.Add(objItem); dicHelper.Add(strKey, lstHelperItems); } } } finally { foreach (List <ListItem> lstHelperItems in dicHelper.Values) { Utils.ListItemListPool.Return(lstHelperItems); } } } } using (_ = await Timekeeper.StartSyncronAsync("load_frm_masterindex_sort_entries", opLoadMasterindex)) { _lstItems.Sort(CompareListItems.CompareNames); _lstFileNamesWithItems.Sort(CompareListItems.CompareNames); } using (_ = await Timekeeper.StartSyncronAsync("load_frm_masterindex_populate_controls", opLoadMasterindex)) { _lstFileNamesWithItems.Insert( 0, new ListItem(string.Empty, await LanguageManager.GetStringAsync("String_All"))); int intOldSelectedIndex = await cboFile.DoThreadSafeFuncAsync(x => x.SelectedIndex, token); await cboFile.PopulateWithListItemsAsync(_lstFileNamesWithItems, token); await cboFile.DoThreadSafeAsync(x => { try { x.SelectedIndex = Math.Max(intOldSelectedIndex, 0); } // For some reason, some unit tests will fire this exception even when _lstFileNamesWithItems is explicitly checked for having enough items catch (ArgumentOutOfRangeException) { x.SelectedIndex = -1; } }, token); await lstItems.PopulateWithListItemsAsync(_lstItems, token); await lstItems.DoThreadSafeAsync(x => x.SelectedIndex = -1, token); } } finally { _blnSkipRefresh = false; IsFinishedLoading = blnOldIsFinishedLoading; } } }