public void OnDeleteTopic(Engine.Topics.Topic topic, IDObjects.NumberSet linksAffected, CodeDB.EventAccessor eventAccessor) { if (!IncludeInIndex(topic)) { return; } var entry = new SearchIndex.Entries.Topic(topic, this); accessLock.EnterWriteLock(); try { foreach (string keyword in entry.Keywords) { string prefix = KeywordPrefix(keyword); var numberSet = prefixTopicIDs[prefix]; if (numberSet != null) { numberSet.Remove(topic.TopicID); if (numberSet.IsEmpty) { prefixTopicIDs.Remove(prefix); foreach (var changeWatcher in changeWatchers) { changeWatcher.OnDeletePrefix(prefix, eventAccessor); } } else { foreach (var changeWatcher in changeWatchers) { changeWatcher.OnUpdatePrefix(prefix, eventAccessor); } } } } } finally { accessLock.ExitWriteLock(); } }
// Group: CodeDB.IChangeWatcher Functions // __________________________________________________________________________ public void OnAddTopic(Engine.Topics.Topic topic, CodeDB.EventAccessor eventAccessor) { if (!IncludeInIndex(topic)) { return; } var entry = new SearchIndex.Entries.Topic(topic, this); accessLock.EnterWriteLock(); try { foreach (string keyword in entry.Keywords) { string prefix = KeywordPrefix(keyword); var topicIDs = prefixTopicIDs[prefix]; if (topicIDs == null) { topicIDs = new IDObjects.NumberSet(); topicIDs.Add(topic.TopicID); prefixTopicIDs[prefix] = topicIDs; foreach (var changeWatcher in changeWatchers) { changeWatcher.OnAddPrefix(prefix, eventAccessor); } } else { topicIDs.Add(topic.TopicID); foreach (var changeWatcher in changeWatchers) { changeWatcher.OnUpdatePrefix(prefix, eventAccessor); } } } } finally { accessLock.ExitWriteLock(); } }
/* Function: AppendTopic * Appends a topic entry as a JSON array. */ protected void AppendTopic(SearchIndex.Entries.Topic topicEntry, string keywordHTMLName, bool includeLanguage, StringBuilder output) { string topicHTMLPrefix, topicHTMLName, topicSearchText; if (topicEntry.EndOfDisplayNameQualifiers == 0) { topicHTMLPrefix = null; topicHTMLName = topicEntry.DisplayName.ToHTML(); topicSearchText = topicEntry.SearchText; } else { topicHTMLPrefix = topicEntry.DisplayName.Substring(0, topicEntry.EndOfDisplayNameQualifiers); if (topicHTMLPrefix[topicHTMLPrefix.Length - 1] == '.') { topicHTMLPrefix = topicHTMLPrefix.Substring(0, topicHTMLPrefix.Length - 1); } else if (topicHTMLPrefix.EndsWith("::") || topicHTMLPrefix.EndsWith("->")) { topicHTMLPrefix = topicHTMLPrefix.Substring(0, topicHTMLPrefix.Length - 2); } topicHTMLPrefix = topicHTMLPrefix.ToHTML(); topicHTMLName = topicEntry.DisplayName.Substring(topicEntry.EndOfDisplayNameQualifiers).ToHTML(); topicSearchText = topicEntry.SearchText.Substring(topicEntry.EndOfSearchTextQualifiers); } output.Append('['); if (topicHTMLPrefix != null) { output.Append('"'); output.StringEscapeAndAppend(topicHTMLPrefix); output.Append('"'); } output.Append(','); if (topicHTMLName != keywordHTMLName) { output.Append('"'); output.StringEscapeAndAppend(topicHTMLName); output.Append('"'); } output.Append(','); if (includeLanguage) { output.Append('"'); output.StringEscapeAndAppend(EngineInstance.Languages.FromID(topicEntry.WrappedTopic.LanguageID).Name); output.Append('"'); } output.Append(','); if (topicSearchText != topicHTMLName.ToLower()) { output.Append('"'); output.StringEscapeAndAppend(topicSearchText); output.Append('"'); } output.Append(','); output.Append(UsedCommentTypeIndex(topicEntry.WrappedTopic.CommentTypeID)); output.Append(",\""); Context fileContext = new Context(context.Target, topicEntry.WrappedTopic.FileID, topicEntry.WrappedTopic); output.StringEscapeAndAppend(fileContext.HashPath); output.Append('"'); if (topicEntry.WrappedTopic.ClassID != 0) { output.Append(",\""); Context classContext = new Context(context.Target, topicEntry.WrappedTopic.ClassID, topicEntry.WrappedTopic.ClassString, topicEntry.WrappedTopic); output.StringEscapeAndAppend(classContext.HashPath); output.Append('"'); } output.Append(']'); }
public void OnUpdateTopic(Engine.Topics.Topic oldTopic, Engine.Topics.Topic newTopic, Engine.Topics.Topic.ChangeFlags changeFlags, CodeDB.EventAccessor eventAccessor) { #if DEBUG if (IncludeInIndex(newTopic) != IncludeInIndex(oldTopic)) { throw new Exception("SearchIndex incorrectly assumes IncludeInIndex() will be the same for both the old and new topics in OnUpdateTopic()."); } #endif if (!IncludeInIndex(newTopic)) { return; } if ((changeFlags & (Engine.Topics.Topic.ChangeFlags.Title | Engine.Topics.Topic.ChangeFlags.CommentTypeID | Engine.Topics.Topic.ChangeFlags.SymbolDefinitonNumber | Engine.Topics.Topic.ChangeFlags.Symbol | Engine.Topics.Topic.ChangeFlags.LanguageID | Engine.Topics.Topic.ChangeFlags.FileID | Engine.Topics.Topic.ChangeFlags.EffectiveAccessLevel | Engine.Topics.Topic.ChangeFlags.Class)) == 0) { return; } // We assume that if the topics are similar enough to use OnUpdateTopic() instead of OnAdd/RemoveTopic() then they'll generate the exact // same keyword list, and they'll even be in the same order. This allows for a nice optimization here, but test it in debug builds in case these // assumptions are wrong in the future. #if DEBUG if (newTopic.TopicID != oldTopic.TopicID) { throw new Exception("SearchIndex incorrectly assumes both the old and new topics in OnUpdateTopic() have the same topic IDs."); } var newEntry = new SearchIndex.Entries.Topic(newTopic, this); var oldEntry = new SearchIndex.Entries.Topic(oldTopic, this); if (newEntry.Keywords.Count != oldEntry.Keywords.Count) { throw new Exception("SearchIndex incorrectly assumes both the old and new topics in OnUpdateTopic() have the same keywords."); } for (int i = 0; i < newEntry.Keywords.Count; i++) { if (newEntry.Keywords[i] != oldEntry.Keywords[i]) { throw new Exception("SearchIndex incorrectly assumes both the old and new topics in OnUpdateTopic() have the same keywords."); } } #endif var entry = new SearchIndex.Entries.Topic(newTopic, this); // We use upgradeable in case one of the change handlers needs to do something that requires a write lock. accessLock.EnterUpgradeableReadLock(); try { foreach (var keyword in entry.Keywords) { string prefix = KeywordPrefix(keyword); foreach (var changeWatcher in changeWatchers) { changeWatcher.OnUpdatePrefix(prefix, eventAccessor); } } } finally { // We don't have to test to see if it was upgraded because if it was the write lock should have been released // by the change handler and we should recursively be back to an upgradeable read lock. accessLock.ExitUpgradeableReadLock(); } }
/* Function: GetKeywordEntries * Returns a list of all the <KeywordEntries> for a prefix, complete with all their <TopicEntries>. If there are none it will return * null. The returned list will not be in any particular order, it is up to the calling code to sort them as desired. */ public List <Keyword> GetKeywordEntries(string prefix, CodeDB.Accessor accessor, CancelDelegate cancelDelegate) { // Retrieve the topics from the database IDObjects.NumberSet topicIDs = PrefixTopicIDs(prefix); if (topicIDs == null || topicIDs.IsEmpty) { return(null); } List <Engine.Topics.Topic> topics = null; bool releaseDBLock = false; if (accessor.LockHeld == CodeDB.Accessor.LockType.None) { accessor.GetReadOnlyLock(); releaseDBLock = true; } try { // Need to lookup class strings to be able to build hash paths topics = accessor.GetTopicsByID(topicIDs, cancelDelegate, CodeDB.Accessor.GetTopicFlags.BodyLengthOnly | CodeDB.Accessor.GetTopicFlags.DontLookupContexts | CodeDB.Accessor.GetTopicFlags.DontIncludeSummary | CodeDB.Accessor.GetTopicFlags.DontIncludePrototype); } finally { if (releaseDBLock) { accessor.ReleaseLock(); } } if (cancelDelegate()) { return(null); } // Convert the topics into entries StringTable <Keyword> keywordEntryTable = new StringTable <Keyword>(KeySettings.IgnoreCase); foreach (var topic in topics) { var topicEntry = new SearchIndex.Entries.Topic(topic, this); foreach (var keyword in topicEntry.Keywords) { if (KeywordMatchesPrefix(keyword, prefix)) { var keywordEntry = keywordEntryTable[keyword]; if (keywordEntry == null) { keywordEntry = new Keyword(keyword); keywordEntryTable[keyword] = keywordEntry; } else if (keywordEntry.DisplayName != keyword) { // If they differ in case we still want to combine them, but we have to choose which case will be in the // results. Prioritize in this order: mixed case ('m'), all lowercase ('l'), all uppercase ('u'). char keywordCase, keywordEntryCase; if (keyword == keyword.ToLower()) { keywordCase = 'l'; } else if (keyword == keyword.ToUpper()) { keywordCase = 'u'; } else { keywordCase = 'm'; } if (keywordEntry.DisplayName == keywordEntry.DisplayName.ToLower()) { keywordEntryCase = 'l'; } else if (keywordEntry.DisplayName == keywordEntry.DisplayName.ToUpper()) { keywordEntryCase = 'u'; } else { keywordEntryCase = 'm'; } if ((keywordCase == 'm' && keywordEntryCase != 'm') || (keywordCase == 'l' && keywordEntryCase == 'u')) { keywordEntry.DisplayName = keyword; } else if (keywordCase == 'm' && keywordEntryCase == 'm') { // If they're both mixed, use the sort order. This lets SomeValue be used instead of someValue. if (string.Compare(keyword, keywordEntry.DisplayName) > 0) { keywordEntry.DisplayName = keyword; } } } keywordEntry.TopicEntries.Add(topicEntry); } } } List <Keyword> keywordEntries = new List <Keyword>(keywordEntryTable.Count); foreach (var keywordEntryTablePair in keywordEntryTable) { keywordEntries.Add(keywordEntryTablePair.Value); } return(keywordEntries); }