mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
* Revert "Revert to SMO with older SQLClient driver (#889)"
This reverts commit b763abae47.
* Bump SMO and SqlClient
380 lines
16 KiB
C#
380 lines
16 KiB
C#
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using Microsoft.Data.SqlClient;
|
|
using System.Diagnostics;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.Hosting.Protocol;
|
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
|
using Microsoft.SqlTools.Utility;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser
|
|
{
|
|
/// <summary>
|
|
/// Main class for file browser service
|
|
/// </summary>
|
|
public sealed class FileBrowserService: IDisposable
|
|
{
|
|
private static readonly Lazy<FileBrowserService> LazyInstance = new Lazy<FileBrowserService>(() => new FileBrowserService());
|
|
public static FileBrowserService Instance => LazyInstance.Value;
|
|
|
|
// Cache file browser operations for expanding node request
|
|
private readonly ConcurrentDictionary<string, FileBrowserOperation> ownerToFileBrowserMap = new ConcurrentDictionary<string, FileBrowserOperation>();
|
|
private readonly ConcurrentDictionary<string, ValidatePathsCallback> validatePathsCallbackMap = new ConcurrentDictionary<string, ValidatePathsCallback>();
|
|
private ConnectionService connectionService;
|
|
private ConnectedBindingQueue fileBrowserQueue = new ConnectedBindingQueue(needsMetadata: false);
|
|
private static int DefaultTimeout = 120000;
|
|
private string serviceName = "FileBrowser";
|
|
|
|
/// <summary>
|
|
/// Signature for callback method that validates the selected file paths
|
|
/// </summary>
|
|
/// <param name="eventArgs"></param>
|
|
public delegate bool ValidatePathsCallback(FileBrowserValidateEventArgs eventArgs, out string errorMessage);
|
|
|
|
internal ConnectionService ConnectionServiceInstance
|
|
{
|
|
get
|
|
{
|
|
if (connectionService == null)
|
|
{
|
|
connectionService = ConnectionService.Instance;
|
|
connectionService.RegisterConnectedQueue(this.serviceName, this.fileBrowserQueue);
|
|
}
|
|
return connectionService;
|
|
}
|
|
set
|
|
{
|
|
connectionService = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register validate path callback
|
|
/// </summary>
|
|
/// <param name="service"></param>
|
|
/// <param name="callback"></param>
|
|
public void RegisterValidatePathsCallback(string service, ValidatePathsCallback callback)
|
|
{
|
|
validatePathsCallbackMap.AddOrUpdate(service, callback, (key, oldValue) => callback);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the service instance
|
|
/// </summary>
|
|
/// <param name="serviceHost">Service host to register handlers with</param>
|
|
public void InitializeService(ServiceHost serviceHost)
|
|
{
|
|
// Open a file browser
|
|
serviceHost.SetRequestHandler(FileBrowserOpenRequest.Type, HandleFileBrowserOpenRequest);
|
|
|
|
// Expand a folder node
|
|
serviceHost.SetRequestHandler(FileBrowserExpandRequest.Type, HandleFileBrowserExpandRequest);
|
|
|
|
// Validate the selected files
|
|
serviceHost.SetRequestHandler(FileBrowserValidateRequest.Type, HandleFileBrowserValidateRequest);
|
|
|
|
// Close the file browser
|
|
serviceHost.SetRequestHandler(FileBrowserCloseRequest.Type, HandleFileBrowserCloseRequest);
|
|
}
|
|
|
|
#region request handlers
|
|
|
|
internal async Task HandleFileBrowserOpenRequest(FileBrowserOpenParams fileBrowserParams, RequestContext<bool> requestContext)
|
|
{
|
|
try
|
|
{
|
|
var task = Task.Run(() => RunFileBrowserOpenTask(fileBrowserParams, requestContext))
|
|
.ContinueWithOnFaulted(null);
|
|
await requestContext.SendResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(TraceEventType.Error, "Unexpected exception while handling file browser open request: " + ex.Message);
|
|
await requestContext.SendResult(false);
|
|
}
|
|
}
|
|
|
|
internal async Task HandleFileBrowserExpandRequest(FileBrowserExpandParams fileBrowserParams, RequestContext<bool> requestContext)
|
|
{
|
|
try
|
|
{
|
|
var task = Task.Run(() => RunFileBrowserExpandTask(fileBrowserParams, requestContext))
|
|
.ContinueWithOnFaulted(null);
|
|
await requestContext.SendResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(TraceEventType.Error, "Unexpected exception while handling file browser expand request: " + ex.Message);
|
|
await requestContext.SendResult(false);
|
|
}
|
|
}
|
|
|
|
internal async Task HandleFileBrowserValidateRequest(FileBrowserValidateParams fileBrowserParams, RequestContext<bool> requestContext)
|
|
{
|
|
try
|
|
{
|
|
var task = Task.Run(() => RunFileBrowserValidateTask(fileBrowserParams, requestContext))
|
|
.ContinueWithOnFaulted(null);
|
|
await requestContext.SendResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(TraceEventType.Error, "Unexpected exception while handling file browser validate request: " + ex.Message);
|
|
await requestContext.SendResult(false);
|
|
}
|
|
}
|
|
|
|
internal async Task HandleFileBrowserCloseRequest(
|
|
FileBrowserCloseParams fileBrowserParams,
|
|
RequestContext<FileBrowserCloseResponse> requestContext)
|
|
{
|
|
try
|
|
{
|
|
var task = Task.Run(() => RunFileBrowserCloseTask(fileBrowserParams, requestContext))
|
|
.ContinueWithOnFaulted(null);
|
|
await requestContext.SendResult(new FileBrowserCloseResponse() { Succeeded = true });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(TraceEventType.Error, "Unexpected exception while handling file browser close request: " + ex.Message);
|
|
await requestContext.SendResult(new FileBrowserCloseResponse() { Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void Dispose()
|
|
{
|
|
this.fileBrowserQueue.Dispose();
|
|
}
|
|
|
|
internal async Task RunFileBrowserCloseTask(FileBrowserCloseParams fileBrowserParams, RequestContext<FileBrowserCloseResponse> requestContext)
|
|
{
|
|
FileBrowserCloseResponse result = new FileBrowserCloseResponse();
|
|
try
|
|
{
|
|
FileBrowserOperation operation = null;
|
|
ConnectionInfo connInfo;
|
|
ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out operation);
|
|
this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo);
|
|
|
|
if (operation != null && connInfo != null)
|
|
{
|
|
if (!operation.FileTreeCreated)
|
|
{
|
|
operation.Cancel();
|
|
}
|
|
|
|
// Clear queued items
|
|
this.fileBrowserQueue.ClearQueuedItems();
|
|
|
|
// Queue operation to clean up resources
|
|
QueueItem queueItem = fileBrowserQueue.QueueBindingOperation(
|
|
key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName),
|
|
bindingTimeout: DefaultTimeout,
|
|
waitForLockTimeout: DefaultTimeout,
|
|
bindOperation: (bindingContext, cancelToken) =>
|
|
{
|
|
FileBrowserOperation removedOperation = null;
|
|
ownerToFileBrowserMap.TryRemove(fileBrowserParams.OwnerUri, out removedOperation);
|
|
if (removedOperation != null)
|
|
{
|
|
removedOperation.Dispose();
|
|
}
|
|
result.Succeeded = true;
|
|
return result;
|
|
});
|
|
|
|
queueItem.ItemProcessed.WaitOne();
|
|
if (queueItem.GetResultAsT<FileBrowserCloseResponse>() != null)
|
|
{
|
|
result = queueItem.GetResultAsT<FileBrowserCloseResponse>();
|
|
}
|
|
|
|
this.fileBrowserQueue.CloseConnections(connInfo.ConnectionDetails.ServerName, connInfo.ConnectionDetails.DatabaseName, DefaultTimeout);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Write(TraceEventType.Error, "Unexpected exception while closing file browser: " + ex.Message);
|
|
result.Message = ex.Message;
|
|
}
|
|
|
|
await requestContext.SendEvent(FileBrowserClosedNotification.Type, result);
|
|
}
|
|
|
|
internal async Task RunFileBrowserOpenTask(FileBrowserOpenParams fileBrowserParams, RequestContext<bool> requestContext)
|
|
{
|
|
FileBrowserOpenedParams result = new FileBrowserOpenedParams();
|
|
FileBrowserOperation browser = null;
|
|
bool isCancelRequested = false;
|
|
|
|
try
|
|
{
|
|
ConnectionInfo connInfo;
|
|
this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo);
|
|
if (connInfo != null)
|
|
{
|
|
QueueItem queueItem = fileBrowserQueue.QueueBindingOperation(
|
|
key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName),
|
|
bindingTimeout: DefaultTimeout,
|
|
waitForLockTimeout: DefaultTimeout,
|
|
bindOperation: (bindingContext, cancelToken) =>
|
|
{
|
|
if (!fileBrowserParams.ChangeFilter)
|
|
{
|
|
browser = new FileBrowserOperation(bindingContext.ServerConnection, fileBrowserParams.ExpandPath, fileBrowserParams.FileFilters);
|
|
}
|
|
else
|
|
{
|
|
ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out browser);
|
|
if (browser != null)
|
|
{
|
|
browser.Initialize(fileBrowserParams.ExpandPath, fileBrowserParams.FileFilters);
|
|
}
|
|
}
|
|
|
|
if (browser != null)
|
|
{
|
|
ownerToFileBrowserMap.AddOrUpdate(fileBrowserParams.OwnerUri, browser, (key, value) => browser);
|
|
|
|
// Create file browser tree
|
|
browser.PopulateFileTree();
|
|
|
|
if (browser.IsCancellationRequested)
|
|
{
|
|
isCancelRequested = true;
|
|
}
|
|
else
|
|
{
|
|
result.OwnerUri = fileBrowserParams.OwnerUri;
|
|
result.FileTree = browser.FileTree;
|
|
result.Succeeded = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
queueItem.ItemProcessed.WaitOne();
|
|
|
|
if (queueItem.GetResultAsT<FileBrowserOpenedParams>() != null)
|
|
{
|
|
result = queueItem.GetResultAsT<FileBrowserOpenedParams>();
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Message = ex.Message;
|
|
}
|
|
|
|
if (!isCancelRequested)
|
|
{
|
|
await requestContext.SendEvent(FileBrowserOpenedNotification.Type, result);
|
|
}
|
|
}
|
|
|
|
internal async Task RunFileBrowserExpandTask(FileBrowserExpandParams fileBrowserParams, RequestContext<bool> requestContext)
|
|
{
|
|
FileBrowserExpandedParams result = new FileBrowserExpandedParams();
|
|
try
|
|
{
|
|
FileBrowserOperation operation = null;
|
|
ConnectionInfo connInfo;
|
|
ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out operation);
|
|
this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo);
|
|
|
|
if (operation != null && connInfo != null)
|
|
{
|
|
QueueItem queueItem = fileBrowserQueue.QueueBindingOperation(
|
|
key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName),
|
|
bindingTimeout: DefaultTimeout,
|
|
waitForLockTimeout: DefaultTimeout,
|
|
bindOperation: (bindingContext, cancelToken) =>
|
|
{
|
|
result.ExpandPath = fileBrowserParams.ExpandPath;
|
|
result.Children = operation.GetChildren(fileBrowserParams.ExpandPath).ToArray();
|
|
result.OwnerUri = fileBrowserParams.OwnerUri;
|
|
result.Succeeded = true;
|
|
return result;
|
|
});
|
|
|
|
queueItem.ItemProcessed.WaitOne();
|
|
|
|
if (queueItem.GetResultAsT<FileBrowserExpandedParams>() != null)
|
|
{
|
|
result = queueItem.GetResultAsT<FileBrowserExpandedParams>();
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Message = ex.Message;
|
|
}
|
|
|
|
await requestContext.SendEvent(FileBrowserExpandedNotification.Type, result);
|
|
}
|
|
|
|
internal async Task RunFileBrowserValidateTask(FileBrowserValidateParams fileBrowserParams, RequestContext<bool> requestContext)
|
|
{
|
|
FileBrowserValidatedParams result = new FileBrowserValidatedParams();
|
|
|
|
try
|
|
{
|
|
ValidatePathsCallback callback;
|
|
ConnectionInfo connInfo;
|
|
this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo);
|
|
if (validatePathsCallbackMap.TryGetValue(fileBrowserParams.ServiceType, out callback)
|
|
&& callback != null
|
|
&& connInfo != null
|
|
&& fileBrowserParams.SelectedFiles != null
|
|
&& fileBrowserParams.SelectedFiles.Length > 0)
|
|
{
|
|
QueueItem queueItem = fileBrowserQueue.QueueBindingOperation(
|
|
key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName),
|
|
bindingTimeout: DefaultTimeout,
|
|
waitForLockTimeout: DefaultTimeout,
|
|
bindOperation: (bindingContext, cancelToken) =>
|
|
{
|
|
string errorMessage;
|
|
result.Succeeded = callback(new FileBrowserValidateEventArgs
|
|
{
|
|
ServiceType = fileBrowserParams.ServiceType,
|
|
OwnerUri = fileBrowserParams.OwnerUri,
|
|
FilePaths = fileBrowserParams.SelectedFiles
|
|
}, out errorMessage);
|
|
result.Message = errorMessage;
|
|
return result;
|
|
});
|
|
|
|
queueItem.ItemProcessed.WaitOne();
|
|
|
|
if (queueItem.GetResultAsT<FileBrowserValidatedParams>() != null)
|
|
{
|
|
result = queueItem.GetResultAsT<FileBrowserValidatedParams>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.Succeeded = true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Message = ex.Message;
|
|
}
|
|
|
|
await requestContext.SendEvent(FileBrowserValidatedNotification.Type, result);
|
|
}
|
|
}
|
|
} |