diff --git a/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs b/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs index dade00de0dd..1f5effdf758 100644 --- a/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs +++ b/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs @@ -8,7 +8,7 @@ // associated with an assembly. [assembly: AssemblyCompany("Autodesk, Inc")] [assembly: AssemblyProduct("Dynamo")] -[assembly: AssemblyCopyright("Copyright © Autodesk, Inc 2023")] +[assembly: AssemblyCopyright("Copyright © Autodesk, Inc 2023")] [assembly: AssemblyTrademark("")] //In order to begin building localizable applications, set diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs index f955094aa41..d79a016e48b 100644 --- a/src/DynamoCore/Models/DynamoModel.cs +++ b/src/DynamoCore/Models/DynamoModel.cs @@ -925,16 +925,7 @@ protected DynamoModel(IStartConfiguration config) CustomNodeManager = new CustomNodeManager(NodeFactory, MigrationManager, LibraryServices); - LuceneSearch.LuceneUtilityNodeSearch = new LuceneSearchUtility(this); - - if (IsTestMode) - { - LuceneUtility.InitializeLuceneConfig(string.Empty, LuceneSearchUtility.LuceneStorage.RAM); - } - else - { - LuceneUtility.InitializeLuceneConfig(LuceneConfig.NodesIndexingDirectory); - } + LuceneSearch.LuceneUtilityNodeSearch = new LuceneSearchUtility(this, LuceneSearchUtility.DefaultNodeIndexStartConfig); InitializeCustomNodeManager(); @@ -1017,10 +1008,15 @@ protected DynamoModel(IStartConfiguration config) TraceReconciliationProcessor = this; State = DynamoModelState.StartedUIless; - // This event should only be raised at the end of this method. - DynamoReady(new ReadyParams(this)); // Write index to disk only once LuceneUtility.CommitWriterChanges(); + //Disposed writer if it is in file system mode so that the index files can be used by other processes (potentially a second Dynamo session) + if (LuceneUtility.startConfig.StorageType == LuceneSearchUtility.LuceneStorage.FILE_SYSTEM) + { + LuceneUtility.DisposeWriter(); + } + // This event should only be raised at the end of this method. + DynamoReady(new ReadyParams(this)); } /// @@ -1429,12 +1425,8 @@ public void Dispose() PreferenceSettings.MessageLogged -= LogMessage; } - //The writer have to be disposed at DynamoModel level due that we could index package-nodes as new packages are installed - LuceneUtility.DisposeWriter(); - // Lucene disposals (just if LuceneNET was initialized) - LuceneUtility.indexDir?.Dispose(); - LuceneUtility.dirReader?.Dispose(); + LuceneUtility.DisposeAll(); #if DEBUG CurrentWorkspace.NodeAdded -= CrashOnDemand.CurrentWorkspace_NodeAdded; @@ -1495,7 +1487,7 @@ private void InitializeCustomNodeManager() var iDoc = LuceneUtility.InitializeIndexDocumentForNodes(); if (searchElement != null) { - AddNodeTypeToSearchIndex(searchElement, iDoc); + LuceneUtility.AddNodeTypeToSearchIndex(searchElement, iDoc); } Action infoUpdatedHandler = null; @@ -1562,7 +1554,9 @@ private void InitializeIncludedNodes() NodeFactory.AddTypeFactoryAndLoader(outputData.Type); NodeFactory.AddAlsoKnownAs(outputData.Type, outputData.AlsoKnownAs); - SearchModel?.Add(new CodeBlockNodeSearchElement(cbnData, LibraryServices)); + var cnbNode = new CodeBlockNodeSearchElement(cbnData, LibraryServices); + SearchModel?.Add(cnbNode); + LuceneUtility.AddNodeTypeToSearchIndex(cnbNode, iDoc); var symbolSearchElement = new NodeModelSearchElement(symbolData) { @@ -1581,29 +1575,10 @@ private void InitializeIncludedNodes() }; SearchModel?.Add(symbolSearchElement); - SearchModel?.Add(outputSearchElement); + LuceneUtility.AddNodeTypeToSearchIndex(symbolSearchElement, iDoc); - //Adding this nodes are breaking the tests (due that we have two input and two output nodes): - //WhenHomeWorkspaceIsFocusedInputAndOutputNodesAreMissingFromSearch - //WhenStartingDynamoInputAndOutputNodesAreNolongerMissingFromSearch - // New index process from Lucene, adding missing nodes: Code Block, Input and Output - var ele = AddNodeTypeToSearch(outputData); - if (ele != null) - { - AddNodeTypeToSearchIndex(ele, iDoc); - } - - ele = AddNodeTypeToSearch(symbolData); - if (ele != null) - { - AddNodeTypeToSearchIndex(ele, iDoc); - } - - ele = AddNodeTypeToSearch(cbnData); - if (ele != null) - { - AddNodeTypeToSearchIndex(ele, iDoc); - } + SearchModel?.Add(outputSearchElement); + LuceneUtility.AddNodeTypeToSearchIndex(outputSearchElement, iDoc); } internal static bool IsDisabledPath(string packagesDirectory, IPreferences preferences) @@ -1756,7 +1731,7 @@ private void LoadNodeModels(List nodes, bool isPackageMember) // TODO: get search element some other way if (ele != null) { - AddNodeTypeToSearchIndex(ele, iDoc); + LuceneUtility.AddNodeTypeToSearchIndex(ele, iDoc); } } catch (Exception e) @@ -3286,24 +3261,6 @@ private NodeModelSearchElement AddNodeTypeToSearch(TypeLoadData typeLoadData) return node; } - /// - /// Add node information to Lucene index - /// - /// node info that will be indexed - /// Lucene document in which the node info will be indexed - internal void AddNodeTypeToSearchIndex(NodeSearchElement node, Document doc) - { - if (LuceneUtility.addedFields == null) return; - - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.FullCategoryName), node.FullCategoryName); - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Name), node.Name); - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Description), node.Description); - if (node.SearchKeywords.Count > 0) LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.SearchKeywords), node.SearchKeywords.Aggregate((x, y) => x + " " + y), true, true); - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Parameters), node.Parameters ?? string.Empty); - - LuceneUtility.writer?.AddDocument(doc); - } - /// /// Remove node information from Lucene indexing. /// @@ -3363,7 +3320,7 @@ private void AddZeroTouchNodeToSearch(FunctionDescriptor functionDescriptor, Doc { var ele = new ZeroTouchSearchElement(functionDescriptor); SearchModel?.Add(ele); - AddNodeTypeToSearchIndex(ele, iDoc); + LuceneUtility.AddNodeTypeToSearchIndex(ele, iDoc); } } diff --git a/src/DynamoCore/Search/NodeSearchModel.cs b/src/DynamoCore/Search/NodeSearchModel.cs index c0d84de2104..3917c65449a 100644 --- a/src/DynamoCore/Search/NodeSearchModel.cs +++ b/src/DynamoCore/Search/NodeSearchModel.cs @@ -21,6 +21,7 @@ using Lucene.Net.Analysis.Ru; using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; +using Lucene.Net.Index; using Lucene.Net.QueryParsers.Classic; using Lucene.Net.Search; @@ -246,9 +247,7 @@ internal IEnumerable Search(string search, LuceneSearchUtilit if (luceneSearchUtility != null) { //The DirectoryReader and IndexSearcher have to be assigned after commiting indexing changes and before executing the Searcher.Search() method, otherwise new indexed info won't be reflected - luceneSearchUtility.dirReader = luceneSearchUtility.writer?.GetReader(applyAllDeletes: true); - if (luceneSearchUtility.dirReader == null) return null; - + luceneSearchUtility.dirReader = luceneSearchUtility.writer != null ? luceneSearchUtility.writer.GetReader(applyAllDeletes: true) : DirectoryReader.Open(luceneSearchUtility.indexDir); luceneSearchUtility.Searcher = new IndexSearcher(luceneSearchUtility.dirReader); string searchTerm = search.Trim(); diff --git a/src/DynamoCore/Utilities/LuceneSearchUtility.cs b/src/DynamoCore/Utilities/LuceneSearchUtility.cs index 7f8746095a5..488be6520fd 100644 --- a/src/DynamoCore/Utilities/LuceneSearchUtility.cs +++ b/src/DynamoCore/Utilities/LuceneSearchUtility.cs @@ -4,6 +4,7 @@ using System.Linq; using Dynamo.Configuration; using Dynamo.Models; +using Dynamo.Search.SearchElements; using Lucene.Net.Analysis; using Lucene.Net.Analysis.Br; using Lucene.Net.Analysis.Cjk; @@ -25,15 +26,52 @@ namespace Dynamo.Utilities { + /// + /// Lucene search utility class that will be used for indexing and searching nodes and packages + /// internal class LuceneSearchUtility { internal DynamoModel dynamoModel; + + /// + /// Index fields that were added to the document + /// internal List addedFields; + + /// + /// Lucene Directory Reader + /// internal DirectoryReader dirReader; + + /// + /// Lucene Index Directory, it can be RAMDirectory or FSDirectory + /// internal Lucene.Net.Store.Directory indexDir; + + /// + /// Lucene Index write + /// internal IndexWriter writer; - internal string directory; - internal LuceneStorage currentStorageType; + + /// + /// Start config for Lucene + /// + internal LuceneStartConfig startConfig; + + /// + /// Default start config for Lucene, it will use RAM storage type and empty directory + /// + internal static readonly LuceneStartConfig DefaultStartConfig = new LuceneStartConfig(); + + /// + /// Start config for node index, it will use file storage type and node index directory + /// + internal static readonly LuceneStartConfig DefaultNodeIndexStartConfig = new LuceneStartConfig(LuceneSearchUtility.LuceneStorage.FILE_SYSTEM, LuceneConfig.NodesIndexingDirectory); + + /// + /// Start config for package index, it will use file storage type and package index directory + /// + internal static readonly LuceneStartConfig DefaultPkgIndexStartConfig = new LuceneStartConfig(LuceneSearchUtility.LuceneStorage.FILE_SYSTEM, LuceneConfig.PackagesIndexingDirectory); public enum LuceneStorage { @@ -50,15 +88,23 @@ public enum LuceneStorage // Holds the instance for the IndexSearcher internal IndexSearcher Searcher; - internal LuceneSearchUtility(DynamoModel model) + /// + /// Constructor for LuceneSearchUtility, it will use the storage type passed as parameter + /// + /// + /// + internal LuceneSearchUtility(DynamoModel model, LuceneStartConfig config) { dynamoModel = model; + // If under test mode, use the default StartConfig - RAM storage type and empty directory + startConfig = DynamoModel.IsTestMode? DefaultStartConfig : config; + InitializeLuceneConfig(); } /// - /// Initialize Lucene config file writer. + /// Initialize Lucene index writer based on start config. /// - internal void InitializeLuceneConfig(string dirName, LuceneStorage storageType = LuceneStorage.FILE_SYSTEM) + internal void InitializeLuceneConfig() { if (DynamoModel.IsHeadless) return; @@ -67,13 +113,9 @@ internal void InitializeLuceneConfig(string dirName, LuceneStorage storageType = DirectoryInfo luceneUserDataFolder; var userDataDir = new DirectoryInfo(dynamoModel.PathManager.UserDataDirectory); luceneUserDataFolder = userDataDir.Exists ? userDataDir : null; + string indexPath = Path.Combine(luceneUserDataFolder.FullName, LuceneConfig.Index, startConfig.Directory); - directory = dirName; - string indexPath = Path.Combine(luceneUserDataFolder.FullName, LuceneConfig.Index, dirName); - - currentStorageType = storageType; - - if (storageType == LuceneStorage.RAM) + if (startConfig.StorageType == LuceneStorage.RAM) { indexDir = new RAMDirectory(); } @@ -81,32 +123,36 @@ internal void InitializeLuceneConfig(string dirName, LuceneStorage storageType = { indexDir = FSDirectory.Open(indexPath); } - - // Create an analyzer to process the text Analyzer = CreateAnalyzerByLanguage(dynamoModel.PreferenceSettings.Locale); + CreateLuceneIndexWriter(); + } - // Initialize Lucene index writer, unless in test mode or we are using RAMDirectory for indexing info. - if (!DynamoModel.IsTestMode || currentStorageType == LuceneStorage.RAM) + /// + /// Create index writer for followup doc indexing + /// + /// Index open mode for Lucene index writer + internal void CreateLuceneIndexWriter(OpenMode mode = OpenMode.CREATE) + { + // Create an index writer + IndexWriterConfig indexConfig = new IndexWriterConfig(LuceneConfig.LuceneNetVersion, Analyzer) + { + OpenMode = mode + }; + try + { + writer = new IndexWriter(indexDir, indexConfig); + } + catch (LockObtainFailedException ex) { - try - { - // Create an index writer - IndexWriterConfig indexConfig = new IndexWriterConfig(LuceneConfig.LuceneNetVersion, Analyzer) - { - OpenMode = OpenMode.CREATE - }; - writer = new IndexWriter(indexDir, indexConfig); - } - catch (LockObtainFailedException ex) - { - DisposeWriter(); - dynamoModel.Logger.LogError($"LuceneNET LockObtainFailedException {ex}"); - } - catch (Exception ex) { - dynamoModel.Logger.LogError($"LuceneNET Exception {ex}"); - } + DisposeWriter(); + dynamoModel.Logger.LogError($"LuceneNET LockObtainFailedException {ex}"); + + } + catch (Exception ex) + { + dynamoModel.Logger.LogError($"LuceneNET Exception {ex}"); } } @@ -116,7 +162,7 @@ internal void InitializeLuceneConfig(string dirName, LuceneStorage storageType = /// internal Document InitializeIndexDocumentForNodes() { - if (DynamoModel.IsTestMode && currentStorageType == LuceneStorage.FILE_SYSTEM) return null; + if (DynamoModel.IsTestMode && startConfig.StorageType == LuceneStorage.FILE_SYSTEM) return null; var name = new TextField(nameof(LuceneConfig.NodeFieldsEnum.Name), string.Empty, Field.Store.YES); var fullCategory = new TextField(nameof(LuceneConfig.NodeFieldsEnum.FullCategoryName), string.Empty, Field.Store.YES); @@ -173,11 +219,11 @@ internal Document InitializeIndexDocumentForPackages() internal void SetDocumentFieldValue(Document doc, string field, string value, bool isTextField = true, bool isLast = false) { string[] indexedFields = null; - if (directory.Equals(LuceneConfig.NodesIndexingDirectory)) + if (startConfig.Directory.Equals(LuceneConfig.NodesIndexingDirectory)) { indexedFields = LuceneConfig.NodeIndexFields; } - else if (directory.Equals(LuceneConfig.PackagesIndexingDirectory)) + else if (startConfig.Directory.Equals(LuceneConfig.PackagesIndexingDirectory)) { indexedFields = LuceneConfig.PackageIndexFields; } @@ -339,23 +385,68 @@ internal Analyzer CreateAnalyzerByLanguage(string language) } } + /// + /// Dispose Lucene index write objects and reuse other objects + /// internal void DisposeWriter() { - //We need to check if we are not running Dynamo tests because otherwise parallel test start to fail when trying to write in the same Lucene directory location - if (!DynamoModel.IsTestMode || currentStorageType == LuceneStorage.RAM) - { - writer?.Dispose(); - writer = null; - } + writer?.Dispose(); + writer = null; + } + + /// + /// Dispose all the Lucene objects + /// + internal void DisposeAll() + { + DisposeWriter(); + dirReader?.Dispose(); + indexDir?.Dispose(); + Analyzer?.Dispose(); } + /// + /// Commit the changes made to the Lucene index + /// internal void CommitWriterChanges() { - if (!DynamoModel.IsTestMode || currentStorageType == LuceneStorage.RAM) + //Commit the info indexed if index writer exists + writer?.Commit(); + } + + /// + /// Add node information to existing Lucene index + /// + /// node info that will be indexed + /// Lucene document in which the node info will be indexed + internal void AddNodeTypeToSearchIndex(NodeSearchElement node, Document doc) + { + if (addedFields == null) return; + // During DynamoModel initialization, the index writer should still be valid here + // If the index writer is null and index not locked, it means the index writer has been disposed, e.g. DynamoModel finished initialization + // If the index writer is null and index locked, it means another Dynamo session is currently updating the search index + // Try to create a new index writer to amend the index + if (writer == null && !IndexWriter.IsLocked(this.indexDir)) { - //Commit the info indexed - writer?.Commit(); + CreateLuceneIndexWriter(OpenMode.CREATE_OR_APPEND); } + // If the index writer is still null, skip the indexing + if (writer == null) return; + + SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.FullCategoryName), node.FullCategoryName); + SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Name), node.Name); + SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Description), node.Description); + if (node.SearchKeywords.Count > 0) + { + SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.SearchKeywords), node.SearchKeywords.Aggregate((x, y) => x + " " + y), true, true); + } + SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Parameters), node.Parameters ?? string.Empty); + + if(writer == null) + { + CreateLuceneIndexWriter(); + } + writer?.AddDocument(doc); } } @@ -364,7 +455,7 @@ internal void CommitWriterChanges() /// public class LuceneCustomAnalyzer : Analyzer { - private LuceneVersion luceneVersion; + private readonly LuceneVersion luceneVersion; public LuceneCustomAnalyzer(LuceneVersion matchVersion) { @@ -394,4 +485,31 @@ protected override TokenStreamComponents CreateComponents(string fieldName, Text return new TokenStreamComponents(tokenizer, tok); } } + + /// + /// Start up config for Lucene indexing + /// + internal class LuceneStartConfig + { + /// + /// Lucene Index Directory name, e.g. Nodes, Packages + /// + internal string Directory { get; set; } + + /// + /// Current Lucene Index Storage type, it could be either RAM or FILE_SYSTEM + /// + internal LuceneSearchUtility.LuceneStorage StorageType { get; set; } + + /// + /// Constructor for LuceneStartConfig + /// + /// + /// + internal LuceneStartConfig(LuceneSearchUtility.LuceneStorage storageType = LuceneSearchUtility.LuceneStorage.RAM, string directory = "") + { + Directory = directory; + StorageType = storageType; + } + } } diff --git a/src/DynamoCoreWpf/Controls/StartPage.xaml.cs b/src/DynamoCoreWpf/Controls/StartPage.xaml.cs index b33d05dcc47..302e6a02192 100644 --- a/src/DynamoCoreWpf/Controls/StartPage.xaml.cs +++ b/src/DynamoCoreWpf/Controls/StartPage.xaml.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.IO; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -370,7 +371,7 @@ private void RefreshFileList(ObservableCollection files, IEnumerable filePaths) { files.Clear(); - foreach (var filePath in filePaths) + foreach (var filePath in filePaths.Where(x => x != null)) { try { diff --git a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs index a043c8f6943..2bf440aae45 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs @@ -18,7 +18,6 @@ using Dynamo.Logging; using Dynamo.Models; using Dynamo.Selection; -using Dynamo.UI; using Dynamo.Wpf.ViewModels.Core; using Newtonsoft.Json; using Point = System.Windows.Point; diff --git a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs index 4c63be9050b..a563b44b0df 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs @@ -572,6 +572,7 @@ public override void Dispose() Errors.Clear(); Annotations.Clear(); InCanvasSearchViewModel?.Dispose(); + NodeAutoCompleteSearchViewModel.LuceneUtility?.DisposeAll(); NodeAutoCompleteSearchViewModel?.Dispose(); } diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs index 0bdae89f0dd..0676543fd3f 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs @@ -1014,6 +1014,8 @@ internal virtual void InstallPackage(PackageDownloadHandle packageDownloadHandle } } SetPackageState(packageDownloadHandle, installPath); + // Dispose Index writer to avoid file lock after new package is installed + Search.LuceneSearch.LuceneUtilityNodeSearch.DisposeWriter(); Analytics.TrackEvent(Actions.Installed, Categories.PackageManagerOperations, $"{packageDownloadHandle?.Name}"); } catch (Exception e) diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs index f6c6583339f..3325e542694 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs @@ -19,6 +19,7 @@ using Dynamo.Wpf.Utilities; using Greg.Responses; using Lucene.Net.Documents; +using Lucene.Net.Index; using Lucene.Net.QueryParsers.Classic; using Lucene.Net.Search; #if NETFRAMEWORK @@ -469,7 +470,7 @@ private void AddPackageToSearchIndex(PackageManagerSearchElement package, Docume LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Name), package.Name); LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Description), package.Description); - if (package.Keywords.Count() > 0) + if (package.Keywords.Length > 0) { LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.SearchKeywords), package.Keywords); } @@ -496,9 +497,8 @@ internal void InitializeLuceneForPackageManager() { if(LuceneUtility == null) { - LuceneSearch.LuceneUtilityPackageManager = new LuceneSearchUtility(PackageManagerClientViewModel.DynamoViewModel.Model); + LuceneSearch.LuceneUtilityPackageManager = new LuceneSearchUtility(PackageManagerClientViewModel.DynamoViewModel.Model, LuceneSearchUtility.DefaultPkgIndexStartConfig); } - LuceneUtility.InitializeLuceneConfig(LuceneConfig.PackagesIndexingDirectory); } /// @@ -796,7 +796,7 @@ public void RefreshAndSearchAsync() if (!DynamoModel.IsTestMode) { - LuceneUtility.dirReader = LuceneUtility.writer?.GetReader(applyAllDeletes: true); + LuceneUtility.dirReader = LuceneUtility.writer != null ? LuceneUtility.writer.GetReader(applyAllDeletes: true) : DirectoryReader.Open(LuceneUtility.indexDir); LuceneUtility.Searcher = new IndexSearcher(LuceneUtility.dirReader); LuceneUtility.CommitWriterChanges(); @@ -1133,7 +1133,7 @@ internal IEnumerable Search(string searchT var packages = new List(); //The DirectoryReader and IndexSearcher have to be assigned after commiting indexing changes and before executing the Searcher.Search() method,otherwise new indexed info won't be reflected - LuceneUtility.dirReader = LuceneUtility.writer?.GetReader(applyAllDeletes: true); + LuceneUtility.dirReader = LuceneUtility.writer != null ? LuceneUtility.writer.GetReader(applyAllDeletes: true): DirectoryReader.Open(LuceneUtility.indexDir); if (LuceneUtility.Searcher == null && LuceneUtility.dirReader != null) { diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PackagePathViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PackagePathViewModel.cs index adbc2ff7d43..4c0bcfb4c3b 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PackagePathViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PackagePathViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Dynamo.Core; @@ -266,6 +266,8 @@ private void CommitChanges(object param) packageLoader.LoadNewCustomNodesAndPackages(newPaths, customNodeManager); } packagePathsEnabled.Clear(); + // Dispose node Index writer to avoid file lock after new package path applied and packages loaded. + Search.LuceneSearch.LuceneUtilityNodeSearch.DisposeWriter(); } internal void SetPackagesScheduledState(string packagePath, bool packagePathDisabled) diff --git a/src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs b/src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs index aa2d6799aac..fb2883e2888 100644 --- a/src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs @@ -44,7 +44,7 @@ public class NodeAutoCompleteSearchViewModel : SearchViewModel private const string nodeAutocompleteMLEndpoint = "MLNodeAutocomplete"; // Lucene search utility to perform indexing operations just for NodeAutocomplete. - private LuceneSearchUtility LuceneUtility + internal LuceneSearchUtility LuceneUtility { get { @@ -682,16 +682,13 @@ internal void SearchAutoCompleteCandidates(string input) } else { - LuceneSearch.LuceneUtilityNodeAutocomplete = new LuceneSearchUtility(dynamoViewModel.Model); - - //The dirName parameter doesn't matter because we are using RAMDirectory indexing and no files are created - LuceneUtility.InitializeLuceneConfig(string.Empty, LuceneSearchUtility.LuceneStorage.RAM); + LuceneSearch.LuceneUtilityNodeAutocomplete = new LuceneSearchUtility(dynamoViewModel.Model, LuceneSearchUtility.DefaultStartConfig); //Memory indexing process for Node Autocomplete (indexing just the nodes returned by the NodeAutocomplete service so we limit the scope of the query search) foreach (var node in searchElementsCache.Select(x => x.Model)) { var doc = LuceneUtility.InitializeIndexDocumentForNodes(); - AddNodeTypeToSearchIndex(node, doc); + LuceneUtility.AddNodeTypeToSearchIndex(node, doc); } //Write the Lucene documents to memory @@ -722,24 +719,6 @@ internal void SearchAutoCompleteCandidates(string input) } } - /// - /// Add node information to Lucene index - /// - /// node info that will be indexed - /// Lucene document in which the node info will be indexed - private void AddNodeTypeToSearchIndex(NodeSearchElement node, Document doc) - { - if (LuceneUtility.addedFields == null) return; - - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.FullCategoryName), node.FullCategoryName); - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Name), node.Name); - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Description), node.Description); - if (node.SearchKeywords.Count > 0) LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.SearchKeywords), node.SearchKeywords.Aggregate((x, y) => x + " " + y), true, true); - LuceneUtility.SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Parameters), node.Parameters ?? string.Empty); - - LuceneUtility.writer?.AddDocument(doc); - } - /// /// Returns a collection of node search elements for nodes /// that output a type compatible with the port type if it's an input port. diff --git a/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs b/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs index dac21d0b0c7..e6d48eaa279 100644 --- a/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs @@ -948,7 +948,10 @@ internal IEnumerable Search(string search, bool useL if (LuceneUtility != null) { var searchElements = Model.Search(search, LuceneUtility); - return searchElements.Select(MakeNodeSearchElementVM); + if (searchElements != null) + { + return searchElements.Select(MakeNodeSearchElementVM); + } } return null; } diff --git a/src/DynamoUtilities/PathHelper.cs b/src/DynamoUtilities/PathHelper.cs index 7a703814e71..dcc8a44548b 100644 --- a/src/DynamoUtilities/PathHelper.cs +++ b/src/DynamoUtilities/PathHelper.cs @@ -37,6 +37,9 @@ public static Exception CreateFolderIfNotExist(string folderPath) { try { + // Do not even try when folder path is null or empty. + // This usually happens when system folder dialog is initialized with empty path + if (string.IsNullOrEmpty(folderPath)) return null; // When network path is access denied, the Directory.Exits however still // return true. // EnumerateDirectories operation is additional check diff --git a/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs b/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs index 8a566f3c793..1c28c53381c 100644 --- a/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs +++ b/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs @@ -187,14 +187,17 @@ private void UnSubscribePythonNodeEvents(PythonNodeBase node) private void UnSubscribeWorkspaceEvents() { - CurrentWorkspace.RequestPackageDependencies -= PythonDependencies.AddPythonPackageDependency; - CurrentWorkspace.NodeAdded -= OnNodeAdded; - CurrentWorkspace.NodeRemoved -= OnNodeRemoved; - CurrentWorkspace.Nodes - .Where(n => n is PythonNode) - .Cast() - .ToList() - .ForEach(n => UnSubscribePythonNodeEvents(n)); + if (CurrentWorkspace != null) + { + CurrentWorkspace.RequestPackageDependencies -= PythonDependencies.AddPythonPackageDependency; + CurrentWorkspace.NodeAdded -= OnNodeAdded; + CurrentWorkspace.NodeRemoved -= OnNodeRemoved; + CurrentWorkspace.Nodes + .Where(n => n is PythonNode) + .Cast() + .ToList() + .ForEach(n => UnSubscribePythonNodeEvents(n)); + } } private void UnsubscribeEvents() diff --git a/test/DynamoCoreTests/SearchModelTests.cs b/test/DynamoCoreTests/SearchModelTests.cs index ff1d2024795..70a5b05db25 100644 --- a/test/DynamoCoreTests/SearchModelTests.cs +++ b/test/DynamoCoreTests/SearchModelTests.cs @@ -565,7 +565,7 @@ private void AddNodeElementToSearchIndex(NodeSearchElement element) var iDoc = CurrentDynamoModel.LuceneUtility.InitializeIndexDocumentForNodes(); if (element != null) { - CurrentDynamoModel.AddNodeTypeToSearchIndex(element, iDoc); + CurrentDynamoModel.LuceneUtility.AddNodeTypeToSearchIndex(element, iDoc); } } diff --git a/test/DynamoCoreWpfTests/SearchViewModelTests.cs b/test/DynamoCoreWpfTests/SearchViewModelTests.cs index 6c9a0660ebf..7c8fb997029 100644 --- a/test/DynamoCoreWpfTests/SearchViewModelTests.cs +++ b/test/DynamoCoreWpfTests/SearchViewModelTests.cs @@ -805,7 +805,7 @@ private NodeSearchElement CreateCustomNode(string name, string category, var iDoc = ViewModel.Model.LuceneUtility.InitializeIndexDocumentForNodes(); if (element != null) { - ViewModel.Model.AddNodeTypeToSearchIndex(element, iDoc); + ViewModel.Model.LuceneUtility.AddNodeTypeToSearchIndex(element, iDoc); }