mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
Create remote file browser service (#448)
* code refactoring * Add filebrowser service and tests * change dataset reference * Address pr comments * add more tests * address pr comments * address pr comments and added more tests * minor change * minor fix * Fix test break and add dataset result check
This commit is contained in:
@@ -0,0 +1,162 @@
|
|||||||
|
//
|
||||||
|
// 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.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||||
|
using Microsoft.SqlServer.Management.Smo;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validate selected file paths for backup/restore operations
|
||||||
|
/// </summary>
|
||||||
|
public static class DisasterRecoveryFileValidator
|
||||||
|
{
|
||||||
|
internal const string LocalSqlServer = "(local)";
|
||||||
|
internal const string LocalMachineName = ".";
|
||||||
|
|
||||||
|
public static bool ValidatePaths(FileBrowserValidateEventArgs args, out string errorMessage)
|
||||||
|
{
|
||||||
|
errorMessage = string.Empty;
|
||||||
|
bool result = true;
|
||||||
|
SqlConnection connection = null;
|
||||||
|
|
||||||
|
if (args != null)
|
||||||
|
{
|
||||||
|
ConnectionInfo connInfo;
|
||||||
|
ConnectionService.Instance.TryFindConnection(args.OwnerUri, out connInfo);
|
||||||
|
if (connInfo != null)
|
||||||
|
{
|
||||||
|
DbConnection dbConnection = null;
|
||||||
|
connInfo.TryGetConnection(Connection.ConnectionType.Default, out dbConnection);
|
||||||
|
if (dbConnection != null)
|
||||||
|
{
|
||||||
|
connection = ReliableConnectionHelper.GetAsSqlConnection(dbConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection != null)
|
||||||
|
{
|
||||||
|
bool isLocal = false;
|
||||||
|
if (string.Compare(GetMachineName(connection.DataSource), Environment.MachineName, StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
|
{
|
||||||
|
isLocal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string filePath in args.FilePaths)
|
||||||
|
{
|
||||||
|
bool isFolder;
|
||||||
|
bool existing = IsPathExisting(connection, filePath, out isFolder);
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
{
|
||||||
|
if (isFolder)
|
||||||
|
{
|
||||||
|
errorMessage = string.Format(SR.BackupPathIsFolderError, filePath);
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the file path doesn't exist, check if the folder exists
|
||||||
|
string folderPath = PathWrapper.GetDirectoryName(filePath);
|
||||||
|
if (isLocal)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(folderPath) && !Directory.Exists(folderPath))
|
||||||
|
{
|
||||||
|
errorMessage = string.Format(SR.InvalidBackupPathError, folderPath);
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool isFolderOnRemote;
|
||||||
|
bool existsOnRemote = IsPathExisting(connection, folderPath, out isFolderOnRemote);
|
||||||
|
if (!existsOnRemote)
|
||||||
|
{
|
||||||
|
errorMessage = string.Format(SR.InvalidBackupPathError, folderPath);
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region private methods
|
||||||
|
|
||||||
|
internal static bool IsPathExisting(SqlConnection connection, string path, out bool isFolder)
|
||||||
|
{
|
||||||
|
Request req = new Request
|
||||||
|
{
|
||||||
|
Urn = "Server/File[@FullName='" + Urn.EscapeString(path) + "']",
|
||||||
|
Fields = new[] { "IsFile" }
|
||||||
|
};
|
||||||
|
|
||||||
|
Enumerator en = new Enumerator();
|
||||||
|
bool isExisting = false;
|
||||||
|
isFolder = false;
|
||||||
|
|
||||||
|
using (DataSet ds = en.Process(connection, req))
|
||||||
|
{
|
||||||
|
if (FileBrowserBase.IsValidDataSet(ds))
|
||||||
|
{
|
||||||
|
isFolder = !(Convert.ToBoolean(ds.Tables[0].Rows[0]["IsFile"], CultureInfo.InvariantCulture));
|
||||||
|
isExisting = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isExisting;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetMachineName(string sqlServerName)
|
||||||
|
{
|
||||||
|
string machineName = string.Empty;
|
||||||
|
if (sqlServerName != null)
|
||||||
|
{
|
||||||
|
string serverName = sqlServerName.ToLowerInvariant().Trim();
|
||||||
|
if ((serverName == LocalSqlServer) || (serverName == LocalMachineName))
|
||||||
|
{
|
||||||
|
machineName = System.Environment.MachineName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
machineName = sqlServerName;
|
||||||
|
if (sqlServerName.Trim().Length != 0)
|
||||||
|
{
|
||||||
|
// [0] = machine, [1] = instance
|
||||||
|
return sqlServerName.Split('\\')[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return machineName;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,11 @@ using Microsoft.SqlTools.Hosting.Protocol;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.Admin;
|
using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation;
|
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||||
{
|
{
|
||||||
@@ -26,6 +25,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
|
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
|
||||||
private static ConnectionService connectionService = null;
|
private static ConnectionService connectionService = null;
|
||||||
private SqlTaskManager sqlTaskManagerInstance = null;
|
private SqlTaskManager sqlTaskManagerInstance = null;
|
||||||
|
private FileBrowserService fileBrowserService = null;
|
||||||
private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper();
|
private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -78,6 +78,25 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current filebrowser service instance
|
||||||
|
/// </summary>
|
||||||
|
internal FileBrowserService FileBrowserServiceInstance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (fileBrowserService == null)
|
||||||
|
{
|
||||||
|
fileBrowserService = FileBrowserService.Instance;
|
||||||
|
}
|
||||||
|
return fileBrowserService;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
fileBrowserService = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the service instance
|
/// Initializes the service instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -89,14 +108,18 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
// Create backup
|
// Create backup
|
||||||
serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest);
|
serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest);
|
||||||
|
|
||||||
// Create respore task
|
// Create restore task
|
||||||
serviceHost.SetRequestHandler(RestoreRequest.Type, HandleRestoreRequest);
|
serviceHost.SetRequestHandler(RestoreRequest.Type, HandleRestoreRequest);
|
||||||
|
|
||||||
// Create respore plan
|
// Create restore plan
|
||||||
serviceHost.SetRequestHandler(RestorePlanRequest.Type, HandleRestorePlanRequest);
|
serviceHost.SetRequestHandler(RestorePlanRequest.Type, HandleRestorePlanRequest);
|
||||||
|
|
||||||
// Create respore config
|
// Create restore config
|
||||||
serviceHost.SetRequestHandler(RestoreConfigInfoRequest.Type, HandleRestoreConfigInfoRequest);
|
serviceHost.SetRequestHandler(RestoreConfigInfoRequest.Type, HandleRestoreConfigInfoRequest);
|
||||||
|
|
||||||
|
// Register file path validation callbacks
|
||||||
|
FileBrowserServiceInstance.RegisterValidatePathsCallback(FileValidationServiceConstants.Backup, DisasterRecoveryFileValidator.ValidatePaths);
|
||||||
|
FileBrowserServiceInstance.RegisterValidatePathsCallback(FileValidationServiceConstants.Restore, DisasterRecoveryFileValidator.ValidatePaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters to pass to close file browser
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserCloseParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connection uri
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Response for closing the browser
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserCloseResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Result of the operation
|
||||||
|
/// </summary>
|
||||||
|
public bool Succeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error message if any
|
||||||
|
/// </summary>
|
||||||
|
public string Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requst to close the file browser
|
||||||
|
/// </summary>
|
||||||
|
class FileBrowserCloseRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<FileBrowserCloseParams, FileBrowserCloseResponse> Type =
|
||||||
|
RequestType<FileBrowserCloseParams, FileBrowserCloseResponse>.Create("filebrowser/close");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event params for expanding a node
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserExpandCompleteParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Expanded node
|
||||||
|
/// </summary>
|
||||||
|
public FileTreeNode ExpandedNode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of the operation
|
||||||
|
/// </summary>
|
||||||
|
public bool Succeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error message if any
|
||||||
|
/// </summary>
|
||||||
|
public string Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification for expand completion
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserExpandCompleteNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<FileBrowserExpandCompleteParams> Type =
|
||||||
|
EventType<FileBrowserExpandCompleteParams>.Create("filebrowser/expandcomplete");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for expanding a folder node
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserExpandParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connection uri
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path to expand the nodes for
|
||||||
|
/// </summary>
|
||||||
|
public string ExpandPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request to expand a node in the file browser
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserExpandRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<FileBrowserExpandParams, bool> Type =
|
||||||
|
RequestType<FileBrowserExpandParams, bool>.Create("filebrowser/expand");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event params for opening a file browser
|
||||||
|
/// Returns full directory structure on the server side
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserOpenCompleteParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Entire file/folder tree
|
||||||
|
/// </summary>
|
||||||
|
public FileTree FileTree;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of the operation
|
||||||
|
/// </summary>
|
||||||
|
public bool Succeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error message
|
||||||
|
/// </summary>
|
||||||
|
public string Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification for completing file browser opening
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserOpenCompleteNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<FileBrowserOpenCompleteParams> Type =
|
||||||
|
EventType<FileBrowserOpenCompleteParams>.Create("filebrowser/opencomplete");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for opening file browser
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserOpenParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connection uri
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initial path to expand the nodes for (e.g. Backup will set this path to default backup folder)
|
||||||
|
/// </summary>
|
||||||
|
public string ExpandPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File extension filter (e.g. *.bak)
|
||||||
|
/// </summary>
|
||||||
|
public string[] FileFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request to open a file browser
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserOpenRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<FileBrowserOpenParams, bool> Type =
|
||||||
|
RequestType<FileBrowserOpenParams, bool>.Create("filebrowser/open");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event params for validation completion
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserValidateCompleteParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Result of the operation
|
||||||
|
/// </summary>
|
||||||
|
public bool Succeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error message if any
|
||||||
|
/// </summary>
|
||||||
|
public string Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification for validation completion
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserValidateCompleteNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<FileBrowserValidateCompleteParams> Type =
|
||||||
|
EventType<FileBrowserValidateCompleteParams>.Create("filebrowser/validatecomplete");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for validating selected file paths
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserValidateParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connection uri
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type of service that uses the file browser
|
||||||
|
/// </summary>
|
||||||
|
public string ServiceType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selected files
|
||||||
|
/// </summary>
|
||||||
|
public string[] SelectedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requst to validate the selected file paths
|
||||||
|
/// </summary>
|
||||||
|
class FileBrowserValidateRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<FileBrowserValidateParams, bool> Type =
|
||||||
|
RequestType<FileBrowserValidateParams, bool>.Create("filebrowser/validate");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tree to represent file/folder structure
|
||||||
|
/// </summary>
|
||||||
|
public class FileTree
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Root node of the tree
|
||||||
|
/// </summary>
|
||||||
|
public FileTreeNode RootNode { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selected node of the tree
|
||||||
|
/// </summary>
|
||||||
|
public FileTreeNode SelectedNode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public FileTree()
|
||||||
|
{
|
||||||
|
this.RootNode = new FileTreeNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tree node to represent file or folder
|
||||||
|
/// </summary>
|
||||||
|
public class FileTreeNode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public FileTreeNode()
|
||||||
|
{
|
||||||
|
this.Children = new List<FileTreeNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent node
|
||||||
|
/// </summary>
|
||||||
|
public FileTreeNode Parent { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of children nodes
|
||||||
|
/// </summary>
|
||||||
|
public List<FileTreeNode> Children { get; private set; }
|
||||||
|
|
||||||
|
// Indicates if the node is expanded, applicable to a folder.
|
||||||
|
public bool IsExpanded { get; set; }
|
||||||
|
|
||||||
|
// Indicates if the node is file or folder
|
||||||
|
public bool IsFile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File or folder name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full path
|
||||||
|
/// </summary>
|
||||||
|
public string FullPath { get; set; }
|
||||||
|
|
||||||
|
public void AddChildNode(FileTreeNode item)
|
||||||
|
{
|
||||||
|
item.Parent = this;
|
||||||
|
this.Children.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
//
|
||||||
|
// 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.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser
|
||||||
|
{
|
||||||
|
public struct FileInfo
|
||||||
|
{
|
||||||
|
// Empty for folder
|
||||||
|
public string fileName;
|
||||||
|
public string folderName;
|
||||||
|
public string path;
|
||||||
|
// Includes file name in the path. Empty for folder.
|
||||||
|
public string fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for file browser
|
||||||
|
/// </summary>
|
||||||
|
public abstract class FileBrowserBase
|
||||||
|
{
|
||||||
|
private Enumerator enumerator = null;
|
||||||
|
protected SqlConnection sqlConnection = null;
|
||||||
|
|
||||||
|
protected Enumerator Enumerator
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.enumerator = (this.enumerator ?? new Enumerator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Separator string for components of the file path. Defaults to \ for Windows and / for Linux
|
||||||
|
/// </summary>
|
||||||
|
internal char PathSeparator { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the PathSeparator values of the Server.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>PathSeparator</returns>
|
||||||
|
internal static char GetPathSeparator(Enumerator enumerator, SqlConnection connection)
|
||||||
|
{
|
||||||
|
var req = new Request();
|
||||||
|
req.Urn = "Server";
|
||||||
|
req.Fields = new[] { "PathSeparator" };
|
||||||
|
string pathSeparator = string.Empty;
|
||||||
|
|
||||||
|
using (DataSet ds = enumerator.Process(connection, req))
|
||||||
|
{
|
||||||
|
if (IsValidDataSet(ds))
|
||||||
|
{
|
||||||
|
pathSeparator = Convert.ToString(ds.Tables[0].Rows[0][0], System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(pathSeparator))
|
||||||
|
{
|
||||||
|
pathSeparator = @"\";
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathSeparator[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates the FileInfo objects associated with drives
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enumerator"></param>
|
||||||
|
/// <param name="connection"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal static IEnumerable<FileInfo> EnumerateDrives(Enumerator enumerator, SqlConnection connection)
|
||||||
|
{
|
||||||
|
// if not supplied, server name will be obtained from urn
|
||||||
|
Request req = new Request();
|
||||||
|
bool clustered = false;
|
||||||
|
|
||||||
|
req.Urn = "Server/Information";
|
||||||
|
req.Fields = new string[] { "IsClustered", "PathSeparator", "HostPlatform" };
|
||||||
|
|
||||||
|
var pathSeparator = @"\";
|
||||||
|
var hostPlatform = HostPlatformNames.Windows;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (DataSet ds = enumerator.Process(connection, req))
|
||||||
|
{
|
||||||
|
if (IsValidDataSet(ds))
|
||||||
|
{
|
||||||
|
clustered = Convert.ToBoolean(ds.Tables[0].Rows[0][0],
|
||||||
|
CultureInfo.InvariantCulture);
|
||||||
|
pathSeparator = Convert.ToString(ds.Tables[0].Rows[0][1], CultureInfo.InvariantCulture);
|
||||||
|
hostPlatform = Convert.ToString(ds.Tables[0].Rows[0][2], CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UnknownPropertyEnumeratorException)
|
||||||
|
{
|
||||||
|
//there can be no clusters on 7.0 server
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to issue different queries to get all fixed drives on a normal server, and
|
||||||
|
// shared drives on a cluster
|
||||||
|
req.Urn = clustered ? "Server/AvailableMedia[@SharedDrive=true()]" : "Server/Drive";
|
||||||
|
req.Fields = new[] { "Name" };
|
||||||
|
|
||||||
|
using (DataSet ds = enumerator.Process(connection, req))
|
||||||
|
{
|
||||||
|
if (IsValidDataSet(ds))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo
|
||||||
|
{
|
||||||
|
fileName = string.Empty,
|
||||||
|
path = Convert.ToString(ds.Tables[0].Rows[i][0], System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
};
|
||||||
|
|
||||||
|
// if we're looking at shared devices on a clustered server
|
||||||
|
// they already have \ on the drive
|
||||||
|
// sys.dm_os_enumerate_fixed_drives appends a \ on Windows for sql17+
|
||||||
|
if (!clustered && hostPlatform == HostPlatformNames.Windows && !fileInfo.path.EndsWith(pathSeparator))
|
||||||
|
{
|
||||||
|
fileInfo.path += pathSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return fileInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates files and folders that are immediate children of the given path on the server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enumerator"></param>
|
||||||
|
/// <param name="connection"></param>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal static IEnumerable<FileInfo> EnumerateFilesInFolder(Enumerator enumerator, SqlConnection connection, string path)
|
||||||
|
{
|
||||||
|
var request = new Request
|
||||||
|
{
|
||||||
|
Urn = "Server/File[@Path='" + Urn.EscapeString(path) + "']",
|
||||||
|
Fields = new[] { "Name", "IsFile", "FullName" },
|
||||||
|
OrderByList = new[]
|
||||||
|
{
|
||||||
|
new OrderBy
|
||||||
|
{
|
||||||
|
Field = "IsFile"
|
||||||
|
},
|
||||||
|
new OrderBy
|
||||||
|
{
|
||||||
|
Field = "Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using (DataSet ds = enumerator.Process(connection, request))
|
||||||
|
{
|
||||||
|
if (IsValidDataSet(ds))
|
||||||
|
{
|
||||||
|
foreach (DataRow row in ds.Tables[0].Rows)
|
||||||
|
{
|
||||||
|
bool isFile = Convert.ToBoolean((object)row[1], CultureInfo.InvariantCulture);
|
||||||
|
yield return new FileInfo
|
||||||
|
{
|
||||||
|
path = isFile ? path : Convert.ToString((object)row[2], CultureInfo.InvariantCulture),
|
||||||
|
fileName = isFile ? Convert.ToString((object)row[0], CultureInfo.InvariantCulture) : String.Empty,
|
||||||
|
folderName = isFile ? String.Empty : Convert.ToString((object)row[0], CultureInfo.InvariantCulture),
|
||||||
|
fullPath = isFile ? Convert.ToString((object)row[2], CultureInfo.InvariantCulture) : String.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsValidDataSet(DataSet ds)
|
||||||
|
{
|
||||||
|
return (ds != null
|
||||||
|
&& ds.Tables != null
|
||||||
|
&& ds.Tables.Count > 0
|
||||||
|
&& ds.Tables[0].Rows != null
|
||||||
|
&& ds.Tables[0].Rows.Count > 0) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Exception raised from file browser operation
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class FileBrowserException : Exception
|
||||||
|
{
|
||||||
|
internal FileBrowserException(string m) : base(m)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
//
|
||||||
|
// 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.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation for file browser operation
|
||||||
|
/// </summary>
|
||||||
|
internal class FileBrowserOperation : FileBrowserBase
|
||||||
|
{
|
||||||
|
private FileTree fileTree;
|
||||||
|
private string expandPath;
|
||||||
|
private string[] fileFilters;
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FileBrowser"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public FileBrowserOperation()
|
||||||
|
{
|
||||||
|
this.fileTree = new FileTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FileBrowser"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionInfo">The connection info</param>
|
||||||
|
/// <param name="fileFilters">The file extension filters</param>
|
||||||
|
public FileBrowserOperation(SqlConnection connectionInfo, string expandPath, string[] fileFilters = null): this()
|
||||||
|
{
|
||||||
|
this.sqlConnection = connectionInfo;
|
||||||
|
this.expandPath = expandPath;
|
||||||
|
if (fileFilters == null)
|
||||||
|
{
|
||||||
|
this.fileFilters = new string[1] { "*" };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.fileFilters = fileFilters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region public properties and methods
|
||||||
|
|
||||||
|
public FileTree FileTree
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.fileTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string[] FileFilters
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.fileFilters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopulateFileTree()
|
||||||
|
{
|
||||||
|
this.PathSeparator = GetPathSeparator(this.Enumerator, this.sqlConnection);
|
||||||
|
PopulateDrives();
|
||||||
|
ExpandSelectedNode(this.expandPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expand nodes for the selected path.
|
||||||
|
/// </summary>
|
||||||
|
public void ExpandSelectedNode(string expandPath)
|
||||||
|
{
|
||||||
|
this.expandPath = expandPath;
|
||||||
|
if (!string.IsNullOrEmpty(this.expandPath))
|
||||||
|
{
|
||||||
|
var dirs = this.expandPath.TrimEnd(this.PathSeparator).Split(this.PathSeparator);
|
||||||
|
List<FileTreeNode> currentChildren = this.fileTree.RootNode.Children;
|
||||||
|
FileTreeNode lastNode = null;
|
||||||
|
string pathSeparatorString = Convert.ToString(this.PathSeparator);
|
||||||
|
|
||||||
|
foreach (string dir in dirs)
|
||||||
|
{
|
||||||
|
FileTreeNode currentNode = null;
|
||||||
|
foreach (FileTreeNode node in currentChildren)
|
||||||
|
{
|
||||||
|
if (node.Name == pathSeparatorString || string.Equals(node.Name, dir, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
currentNode = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentNode != null)
|
||||||
|
{
|
||||||
|
currentNode.IsExpanded = true;
|
||||||
|
if (!currentNode.IsFile)
|
||||||
|
{
|
||||||
|
PopulateFileNode(currentNode);
|
||||||
|
}
|
||||||
|
currentChildren = currentNode.Children;
|
||||||
|
lastNode = currentNode;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (lastNode != null)
|
||||||
|
{
|
||||||
|
this.fileTree.SelectedNode = lastNode;
|
||||||
|
}
|
||||||
|
throw new FileBrowserException(string.Format(SR.InvalidPathError, this.expandPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastNode != null)
|
||||||
|
{
|
||||||
|
this.fileTree.SelectedNode = lastNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void PopulateDrives()
|
||||||
|
{
|
||||||
|
bool first = true;
|
||||||
|
foreach (var fileInfo in EnumerateDrives(Enumerator, sqlConnection))
|
||||||
|
{
|
||||||
|
// Windows drive letter paths have a '\' at the end. Linux drive paths won't have a '\'.
|
||||||
|
var node = new FileTreeNode
|
||||||
|
{
|
||||||
|
Name = Convert.ToString(fileInfo.path, CultureInfo.InvariantCulture).TrimEnd('\\'),
|
||||||
|
FullPath = fileInfo.path
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fileTree.RootNode.AddChildNode(node);
|
||||||
|
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
this.fileTree.SelectedNode = node;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopulateFileNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateFileNode(FileTreeNode parentNode)
|
||||||
|
{
|
||||||
|
string parentPath = parentNode.FullPath;
|
||||||
|
parentNode.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var file in EnumerateFilesInFolder(Enumerator, sqlConnection, parentPath))
|
||||||
|
{
|
||||||
|
bool isFile = !string.IsNullOrEmpty(file.fileName);
|
||||||
|
FileTreeNode treeNode = new FileTreeNode();
|
||||||
|
if (isFile)
|
||||||
|
{
|
||||||
|
treeNode.Name = file.fileName;
|
||||||
|
treeNode.FullPath = file.fullPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
treeNode.Name = file.folderName;
|
||||||
|
treeNode.FullPath = file.path;
|
||||||
|
}
|
||||||
|
treeNode.IsFile = isFile;
|
||||||
|
|
||||||
|
// if the node is a directory, or if we are browsing for files and the file name is allowed,
|
||||||
|
// add the node to the tree
|
||||||
|
if (!isFile || (this.FilterFile(treeNode.Name, this.fileFilters)))
|
||||||
|
{
|
||||||
|
parentNode.AddChildNode(treeNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filter a filename based on the full mask provide. The full mask may be a collection a masks seperated by semi-colons.
|
||||||
|
/// For example: *; *.txt
|
||||||
|
/// </summary>
|
||||||
|
internal bool FilterFile(string fileName, string[] masks)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < masks.Length; index++)
|
||||||
|
{
|
||||||
|
if (MatchFileToSubMask(fileName, masks[index]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares a file name to the user specified mask using a regular expression
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <param name="mask"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private bool MatchFileToSubMask(string fileName, string mask)
|
||||||
|
{
|
||||||
|
Regex regex;
|
||||||
|
|
||||||
|
// If this mask is for all files (*) then just return true.
|
||||||
|
if (mask == "*")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mask = mask.Replace(".", "\\.");
|
||||||
|
mask = mask.Replace("*", ".*");
|
||||||
|
mask = mask.Replace("?", ".");
|
||||||
|
|
||||||
|
// Perform case insensitive RegEx
|
||||||
|
{
|
||||||
|
regex = new Regex(mask, RegexOptions.IgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!regex.IsMatch(fileName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
//
|
||||||
|
// 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.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for file browser service
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FileBrowserService
|
||||||
|
{
|
||||||
|
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 Dictionary<string, FileBrowserOperation> ownerToFileBrowserMap = new Dictionary<string, FileBrowserOperation>();
|
||||||
|
private Dictionary<string, ValidatePathsCallback> validatePathsCallbackMap = new Dictionary<string, ValidatePathsCallback>();
|
||||||
|
private ConnectionService connectionService = null;
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
return connectionService;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
connectionService = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service host object for sending/receiving requests/events.
|
||||||
|
/// Internal for testing purposes.
|
||||||
|
/// </summary>
|
||||||
|
internal IProtocolEndpoint ServiceHost
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public FileBrowserService()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register validate path callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service"></param>
|
||||||
|
/// <param name="callback"></param>
|
||||||
|
public void RegisterValidatePathsCallback(string service, ValidatePathsCallback callback)
|
||||||
|
{
|
||||||
|
if (this.validatePathsCallbackMap.ContainsKey(service))
|
||||||
|
{
|
||||||
|
this.validatePathsCallbackMap.Remove(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.validatePathsCallbackMap.Add(service, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the service instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceHost"></param>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
public void InitializeService(ServiceHost serviceHost)
|
||||||
|
{
|
||||||
|
this.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
|
||||||
|
{
|
||||||
|
Task.Run(() => RunFileBrowserOpenTask(fileBrowserParams));
|
||||||
|
await requestContext.SendResult(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await requestContext.SendResult(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task HandleFileBrowserExpandRequest(
|
||||||
|
FileBrowserExpandParams fileBrowserParams,
|
||||||
|
RequestContext<bool> requestContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task.Run(() => RunFileBrowserExpandTask(fileBrowserParams));
|
||||||
|
await requestContext.SendResult(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await requestContext.SendResult(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task HandleFileBrowserValidateRequest(
|
||||||
|
FileBrowserValidateParams fileBrowserParams,
|
||||||
|
RequestContext<bool> requestContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task.Run(() => RunFileBrowserValidateTask(fileBrowserParams));
|
||||||
|
await requestContext.SendResult(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await requestContext.SendResult(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task HandleFileBrowserCloseRequest(
|
||||||
|
FileBrowserCloseParams fileBrowserParams,
|
||||||
|
RequestContext<FileBrowserCloseResponse> requestContext)
|
||||||
|
{
|
||||||
|
FileBrowserCloseResponse response = new FileBrowserCloseResponse();
|
||||||
|
if (this.ownerToFileBrowserMap.ContainsKey(fileBrowserParams.OwnerUri))
|
||||||
|
{
|
||||||
|
this.ownerToFileBrowserMap.Remove(fileBrowserParams.OwnerUri);
|
||||||
|
response.Succeeded = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.Succeeded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestContext.SendResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
internal async Task RunFileBrowserOpenTask(FileBrowserOpenParams fileBrowserParams)
|
||||||
|
{
|
||||||
|
FileBrowserOpenCompleteParams result = new FileBrowserOpenCompleteParams();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConnectionInfo connInfo;
|
||||||
|
this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo);
|
||||||
|
SqlConnection conn = null;
|
||||||
|
|
||||||
|
if (connInfo != null)
|
||||||
|
{
|
||||||
|
DbConnection dbConn;
|
||||||
|
connInfo.TryGetConnection(ConnectionType.Default, out dbConn);
|
||||||
|
if (dbConn != null)
|
||||||
|
{
|
||||||
|
conn = ReliableConnectionHelper.GetAsSqlConnection((IDbConnection)dbConn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn != null)
|
||||||
|
{
|
||||||
|
FileBrowserOperation browser = new FileBrowserOperation(conn, fileBrowserParams.ExpandPath, fileBrowserParams.FileFilters);
|
||||||
|
browser.PopulateFileTree();
|
||||||
|
this.ownerToFileBrowserMap.Add(fileBrowserParams.OwnerUri, browser);
|
||||||
|
result.FileTree = browser.FileTree;
|
||||||
|
result.Succeeded = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Succeeded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Succeeded = false;
|
||||||
|
result.Message = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ServiceHost.SendEvent(FileBrowserOpenCompleteNotification.Type, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task RunFileBrowserExpandTask(FileBrowserExpandParams fileBrowserParams)
|
||||||
|
{
|
||||||
|
FileBrowserExpandCompleteParams result = new FileBrowserExpandCompleteParams();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this.ownerToFileBrowserMap.ContainsKey(fileBrowserParams.OwnerUri))
|
||||||
|
{
|
||||||
|
FileBrowserOperation browser = this.ownerToFileBrowserMap[fileBrowserParams.OwnerUri];
|
||||||
|
browser.ExpandSelectedNode(fileBrowserParams.ExpandPath);
|
||||||
|
result.ExpandedNode = browser.FileTree.SelectedNode;
|
||||||
|
result.Succeeded = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Succeeded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Succeeded = false;
|
||||||
|
result.Message = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ServiceHost.SendEvent(FileBrowserExpandCompleteNotification.Type, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task RunFileBrowserValidateTask(FileBrowserValidateParams fileBrowserParams)
|
||||||
|
{
|
||||||
|
FileBrowserValidateCompleteParams result = new FileBrowserValidateCompleteParams();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this.validatePathsCallbackMap.ContainsKey(fileBrowserParams.ServiceType)
|
||||||
|
&& this.validatePathsCallbackMap[fileBrowserParams.ServiceType] != null
|
||||||
|
&& fileBrowserParams.SelectedFiles != null
|
||||||
|
&& fileBrowserParams.SelectedFiles.Length > 0)
|
||||||
|
{
|
||||||
|
string errorMessage;
|
||||||
|
result.Succeeded = this.validatePathsCallbackMap[fileBrowserParams.ServiceType](new FileBrowserValidateEventArgs
|
||||||
|
{
|
||||||
|
ServiceType = fileBrowserParams.ServiceType,
|
||||||
|
OwnerUri = fileBrowserParams.OwnerUri,
|
||||||
|
FilePaths = fileBrowserParams.SelectedFiles
|
||||||
|
}, out errorMessage);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(errorMessage))
|
||||||
|
{
|
||||||
|
result.Message = errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Succeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Succeeded = false;
|
||||||
|
result.Message = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ServiceHost.SendEvent(FileBrowserValidateCompleteNotification.Type, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event arguments for validating selected files in file browser
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FileBrowserValidateEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connection uri
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service which provide validation callback
|
||||||
|
/// </summary>
|
||||||
|
public string ServiceType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selected files
|
||||||
|
/// </summary>
|
||||||
|
public string[] FilePaths { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.FileBrowser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List of services that provide file validation callback to file browser service
|
||||||
|
/// </summary>
|
||||||
|
public static class FileValidationServiceConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Backup
|
||||||
|
/// </summary>
|
||||||
|
public const string Backup = "Backup";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restore
|
||||||
|
/// </summary>
|
||||||
|
public const string Restore = "Restore";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3285,6 +3285,22 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string BackupPathIsFolderError
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Keys.GetString(Keys.BackupPathIsFolderError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string InvalidBackupPathError
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Keys.GetString(Keys.InvalidBackupPathError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string TaskInProgress
|
public static string TaskInProgress
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -3493,6 +3509,14 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string InvalidPathError
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Keys.GetString(Keys.InvalidPathError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string ConnectionServiceListDbErrorNotConnected(string uri)
|
public static string ConnectionServiceListDbErrorNotConnected(string uri)
|
||||||
{
|
{
|
||||||
return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri);
|
return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri);
|
||||||
@@ -4834,6 +4858,12 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
public const string BackupTaskName = "BackupTaskName";
|
public const string BackupTaskName = "BackupTaskName";
|
||||||
|
|
||||||
|
|
||||||
|
public const string BackupPathIsFolderError = "BackupPathIsFolderError";
|
||||||
|
|
||||||
|
|
||||||
|
public const string InvalidBackupPathError = "InvalidBackupPathError";
|
||||||
|
|
||||||
|
|
||||||
public const string TaskInProgress = "TaskInProgress";
|
public const string TaskInProgress = "TaskInProgress";
|
||||||
|
|
||||||
|
|
||||||
@@ -4912,6 +4942,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
public const string ScriptTaskName = "ScriptTaskName";
|
public const string ScriptTaskName = "ScriptTaskName";
|
||||||
|
|
||||||
|
|
||||||
|
public const string InvalidPathError = "InvalidPathError";
|
||||||
|
|
||||||
|
|
||||||
private Keys()
|
private Keys()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|||||||
@@ -1815,6 +1815,14 @@
|
|||||||
<value>Backup Database</value>
|
<value>Backup Database</value>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BackupPathIsFolderError" xml:space="preserve">
|
||||||
|
<value>The provided path specifies a directory but a file path is required: {0}</value>
|
||||||
|
<comment></comment>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidBackupPathError" xml:space="preserve">
|
||||||
|
<value> Cannot verify the existence of the backup file location: {0}</value>
|
||||||
|
<comment></comment>
|
||||||
|
</data>
|
||||||
<data name="TaskInProgress" xml:space="preserve">
|
<data name="TaskInProgress" xml:space="preserve">
|
||||||
<value>In progress</value>
|
<value>In progress</value>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
@@ -1919,4 +1927,8 @@
|
|||||||
<value>scripting</value>
|
<value>scripting</value>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidPathError" xml:space="preserve">
|
||||||
|
<value>Cannot access the specified path on the server: {0}</value>
|
||||||
|
<comment></comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -807,6 +807,10 @@ prototype_file_noApplicableFileGroup = No Applicable Filegroup
|
|||||||
# Backup Service
|
# Backup Service
|
||||||
BackupTaskName = Backup Database
|
BackupTaskName = Backup Database
|
||||||
|
|
||||||
|
# Backup File Validation Errors
|
||||||
|
BackupPathIsFolderError = The provided path specifies a directory but a file path is required: {0}
|
||||||
|
InvalidBackupPathError = Cannot verify the existence of the backup file location: {0}
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
# Task Service
|
# Task Service
|
||||||
TaskInProgress = In progress
|
TaskInProgress = In progress
|
||||||
@@ -841,3 +845,7 @@ TheLastBackupTaken = The last backup taken ({0})
|
|||||||
############################################################################
|
############################################################################
|
||||||
# Generate Script
|
# Generate Script
|
||||||
ScriptTaskName = scripting
|
ScriptTaskName = scripting
|
||||||
|
|
||||||
|
############################################################################
|
||||||
|
# File Browser Validation Errors
|
||||||
|
InvalidPathError = Cannot access the specified path on the server: {0}
|
||||||
@@ -2250,6 +2250,21 @@
|
|||||||
<target state="new">scripting</target>
|
<target state="new">scripting</target>
|
||||||
<note></note>
|
<note></note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="BackupPathIsFolderError">
|
||||||
|
<source>The provided path specifies a directory but a file path is required: {0}</source>
|
||||||
|
<target state="new">The file name specified is also a directory name: {0}</target>
|
||||||
|
<note></note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="InvalidBackupPathError">
|
||||||
|
<source> Cannot verify the existence of the backup file location: {0}</source>
|
||||||
|
<target state="new"> Cannot verify the existence of the backup file location: {0}</target>
|
||||||
|
<note></note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="InvalidPathError">
|
||||||
|
<source>Cannot access the specified path on the server: {0}</source>
|
||||||
|
<target state="new">Cannot access the specified path on the server: {0}</target>
|
||||||
|
<note></note>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
@@ -14,6 +14,8 @@ using Microsoft.SqlTools.ServiceLayer.Admin;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
||||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -77,7 +79,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
|||||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
|
|
||||||
string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
|
string backupPath = GetDefaultBackupFullPath(service, databaseName, helper.DataContainer, sqlConn);
|
||||||
|
|
||||||
BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
|
BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
|
||||||
BackupType.Full,
|
BackupType.Full,
|
||||||
@@ -101,7 +103,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
|||||||
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
||||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
|
string backupPath = GetDefaultBackupFullPath(service, databaseName, helper.DataContainer, sqlConn);
|
||||||
|
|
||||||
BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
|
BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
|
||||||
BackupType.Full,
|
BackupType.Full,
|
||||||
@@ -134,7 +136,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
|||||||
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
||||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
|
string backupPath = GetDefaultBackupFullPath(service, databaseName, helper.DataContainer, sqlConn);
|
||||||
|
|
||||||
string certificateName = CreateCertificate(testDb);
|
string certificateName = CreateCertificate(testDb);
|
||||||
string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
|
string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
|
||||||
@@ -186,7 +188,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
|||||||
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
||||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
|
string backupPath = GetDefaultBackupFullPath(service, databaseName, helper.DataContainer, sqlConn);
|
||||||
|
|
||||||
string certificateName = CreateCertificate(testDb);
|
string certificateName = CreateCertificate(testDb);
|
||||||
string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
|
string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
|
||||||
@@ -228,6 +230,86 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
|||||||
testDb.Cleanup();
|
testDb.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void BackupFileBrowserTest()
|
||||||
|
{
|
||||||
|
string databaseName = "testfilebrowser_" + new Random().Next(10000000, 99999999);
|
||||||
|
SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName);
|
||||||
|
|
||||||
|
// Initialize backup service
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
||||||
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
|
DisasterRecoveryService disasterRecoveryService = new DisasterRecoveryService();
|
||||||
|
BackupConfigInfo backupConfigInfo = disasterRecoveryService.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
|
||||||
|
|
||||||
|
// Create backup file
|
||||||
|
string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak");
|
||||||
|
string query = $"BACKUP DATABASE [{databaseName}] TO DISK = N'{backupPath}' WITH NOFORMAT, NOINIT, NAME = N'{databaseName}-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10";
|
||||||
|
await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", query);
|
||||||
|
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
|
||||||
|
string[] backupFilters = new string[2] { "*.bak", "*.trn" };
|
||||||
|
var openParams = new FileBrowserOpenParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
ExpandPath = backupConfigInfo.DefaultBackupFolder,
|
||||||
|
FileFilters = backupFilters
|
||||||
|
};
|
||||||
|
|
||||||
|
var serviceHostMock = new Mock<IProtocolEndpoint>();
|
||||||
|
service.ServiceHost = serviceHostMock.Object;
|
||||||
|
|
||||||
|
await service.RunFileBrowserOpenTask(openParams);
|
||||||
|
|
||||||
|
// Verify complete notification event was fired and the result
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(FileBrowserOpenCompleteNotification.Type,
|
||||||
|
It.Is<FileBrowserOpenCompleteParams>(p => p.Succeeded == true
|
||||||
|
&& p.FileTree != null
|
||||||
|
&& p.FileTree.RootNode != null
|
||||||
|
&& p.FileTree.RootNode.Children != null
|
||||||
|
&& p.FileTree.RootNode.Children.Count > 0
|
||||||
|
&& p.FileTree.SelectedNode.FullPath == backupConfigInfo.DefaultBackupFolder
|
||||||
|
&& p.FileTree.SelectedNode.Children.Count > 0
|
||||||
|
&& ContainsFileInTheFolder(p.FileTree.SelectedNode, backupPath))),
|
||||||
|
Times.Once());
|
||||||
|
|
||||||
|
var expandParams = new FileBrowserExpandParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
ExpandPath = backupConfigInfo.DefaultBackupFolder
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expand the node in file browser
|
||||||
|
await service.RunFileBrowserExpandTask(expandParams);
|
||||||
|
|
||||||
|
// Verify result
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(FileBrowserExpandCompleteNotification.Type,
|
||||||
|
It.Is<FileBrowserExpandCompleteParams>(p => p.Succeeded == true
|
||||||
|
&& p.ExpandedNode.FullPath == backupConfigInfo.DefaultBackupFolder)),
|
||||||
|
Times.Once());
|
||||||
|
|
||||||
|
var validateParams = new FileBrowserValidateParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
ServiceType = FileValidationServiceConstants.Backup,
|
||||||
|
SelectedFiles = new string[] { backupPath }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate selected files in the browser
|
||||||
|
await service.RunFileBrowserValidateTask(validateParams);
|
||||||
|
|
||||||
|
// Verify complete notification event was fired and the result
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(FileBrowserValidateCompleteNotification.Type, It.Is<FileBrowserValidateCompleteParams>(p => p.Succeeded == true)), Times.Once());
|
||||||
|
|
||||||
|
// Remove the backup file
|
||||||
|
if (File.Exists(backupPath))
|
||||||
|
{
|
||||||
|
File.Delete(backupPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region private methods
|
#region private methods
|
||||||
|
|
||||||
private string CreateCertificate(SqlTestDb testDb)
|
private string CreateCertificate(SqlTestDb testDb)
|
||||||
@@ -258,7 +340,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
|||||||
return backupInfo;
|
return backupInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDefaultBackupPath(DisasterRecoveryService service, string databaseName, CDataContainer dataContainer, SqlConnection sqlConn)
|
private string GetDefaultBackupFullPath(DisasterRecoveryService service, string databaseName, CDataContainer dataContainer, SqlConnection sqlConn)
|
||||||
{
|
{
|
||||||
BackupConfigInfo backupConfigInfo = service.GetBackupConfigInfo(dataContainer, sqlConn, sqlConn.Database);
|
BackupConfigInfo backupConfigInfo = service.GetBackupConfigInfo(dataContainer, sqlConn, sqlConn.Database);
|
||||||
return Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak");
|
return Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak");
|
||||||
@@ -287,6 +369,17 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ContainsFileInTheFolder(FileTreeNode folderNode, string filePath)
|
||||||
|
{
|
||||||
|
foreach (FileTreeNode node in folderNode.Children)
|
||||||
|
{
|
||||||
|
if (node.FullPath == filePath)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
||||||
|
using Xunit;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Integration tests for disaster recovery file validator
|
||||||
|
/// </summary>
|
||||||
|
public class DisasterRecoveryFileValidatorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ValidateDefaultBackupFullFilePath()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master");
|
||||||
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
|
string backupPath = Path.Combine(GetDefaultBackupFolderPath(helper.DataContainer, sqlConn), "master.bak");
|
||||||
|
|
||||||
|
string message;
|
||||||
|
bool result = DisasterRecoveryFileValidator.ValidatePaths(new FileBrowserValidateEventArgs
|
||||||
|
{
|
||||||
|
ServiceType = FileValidationServiceConstants.Backup,
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
FilePaths = new string[] { backupPath }
|
||||||
|
}, out message);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Empty(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateDefaultBackupFolderPath()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master");
|
||||||
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
|
string backupPath = GetDefaultBackupFolderPath(helper.DataContainer, sqlConn);
|
||||||
|
|
||||||
|
bool isFolder;
|
||||||
|
bool result = DisasterRecoveryFileValidator.IsPathExisting(sqlConn, backupPath, out isFolder);
|
||||||
|
Assert.True(isFolder);
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidatorShouldReturnFalseForInvalidPath()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master");
|
||||||
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
|
|
||||||
|
string message;
|
||||||
|
bool result = DisasterRecoveryFileValidator.ValidatePaths(new FileBrowserValidateEventArgs
|
||||||
|
{
|
||||||
|
ServiceType = FileValidationServiceConstants.Backup,
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
FilePaths = new string[] { Guid.NewGuid().ToString() }
|
||||||
|
}, out message);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
Assert.True(!string.IsNullOrEmpty(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region private methods
|
||||||
|
|
||||||
|
private string GetDefaultBackupFolderPath(CDataContainer dataContainer, SqlConnection sqlConn)
|
||||||
|
{
|
||||||
|
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||||
|
BackupConfigInfo backupConfigInfo = service.GetBackupConfigInfo(dataContainer, sqlConn, sqlConn.Database);
|
||||||
|
return backupConfigInfo.DefaultBackupFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.FileBrowser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// File browser service tests
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserServiceTests
|
||||||
|
{
|
||||||
|
#region Request handle tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void HandleFileBrowserOpenRequestTest()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo();
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
var openRequestContext = new Mock<RequestContext<bool>>();
|
||||||
|
openRequestContext.Setup(x => x.SendResult(It.IsAny<bool>()))
|
||||||
|
.Returns(Task.FromResult(new object()));
|
||||||
|
|
||||||
|
var openParams = new FileBrowserOpenParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
ExpandPath = "",
|
||||||
|
FileFilters = new string[1] {"*"}
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.HandleFileBrowserOpenRequest(openParams, openRequestContext.Object);
|
||||||
|
openRequestContext.Verify(x => x.SendResult(It.Is<bool>(p => p == true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void HandleFileBrowserExpandRequestTest()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo();
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
var requestContext = new Mock<RequestContext<bool>>();
|
||||||
|
requestContext.Setup(x => x.SendResult(It.IsAny<bool>())).Returns(Task.FromResult(new object()));
|
||||||
|
|
||||||
|
var inputParams = new FileBrowserExpandParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
ExpandPath = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.HandleFileBrowserExpandRequest(inputParams, requestContext.Object);
|
||||||
|
requestContext.Verify(x => x.SendResult(It.Is<bool>(p => p == true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void HandleFileBrowserValidateRequestTest()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo();
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
var requestContext = new Mock<RequestContext<bool>>();
|
||||||
|
requestContext.Setup(x => x.SendResult(It.IsAny<bool>())).Returns(Task.FromResult(new object()));
|
||||||
|
|
||||||
|
var inputParams = new FileBrowserValidateParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
ServiceType = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.HandleFileBrowserValidateRequest(inputParams, requestContext.Object);
|
||||||
|
requestContext.Verify(x => x.SendResult(It.Is<bool>(p => p == true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void HandleFileBrowserCloseRequestTest()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo();
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
var requestContext = new Mock<RequestContext<FileBrowserCloseResponse>>();
|
||||||
|
requestContext.Setup(x => x.SendResult(It.IsAny<FileBrowserCloseResponse>())).Returns(Task.FromResult(new object()));
|
||||||
|
|
||||||
|
var inputParams = new FileBrowserCloseParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri
|
||||||
|
};
|
||||||
|
|
||||||
|
await service.HandleFileBrowserCloseRequest(inputParams, requestContext.Object);
|
||||||
|
|
||||||
|
// Result should return false since it's trying to close a filebrowser that was never opened
|
||||||
|
requestContext.Verify(x => x.SendResult(It.Is<FileBrowserCloseResponse>(p => p.Succeeded == false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void OpenFileBrowserTest()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo();
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
|
||||||
|
var openParams = new FileBrowserOpenParams
|
||||||
|
{
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
ExpandPath = "",
|
||||||
|
FileFilters = new string[1] { "*" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var serviceHostMock = new Mock<IProtocolEndpoint>();
|
||||||
|
service.ServiceHost = serviceHostMock.Object;
|
||||||
|
await service.RunFileBrowserOpenTask(openParams);
|
||||||
|
|
||||||
|
// Verify complete notification event was fired and the result
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(FileBrowserOpenCompleteNotification.Type,
|
||||||
|
It.Is<FileBrowserOpenCompleteParams>(p => p.Succeeded == true
|
||||||
|
&& p.FileTree != null
|
||||||
|
&& p.FileTree.RootNode != null
|
||||||
|
&& p.FileTree.RootNode.Children != null
|
||||||
|
&& p.FileTree.RootNode.Children.Count > 0)),
|
||||||
|
Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ValidateSelectedFilesWithNullValidatorTest()
|
||||||
|
{
|
||||||
|
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo();
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
|
||||||
|
var validateParams = new FileBrowserValidateParams
|
||||||
|
{
|
||||||
|
// Do not pass any service so that the file validator will be null
|
||||||
|
ServiceType = "",
|
||||||
|
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
|
||||||
|
SelectedFiles = new string[] { "" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var serviceHostMock = new Mock<IProtocolEndpoint>();
|
||||||
|
service.ServiceHost = serviceHostMock.Object;
|
||||||
|
|
||||||
|
// Validate files with null file validator
|
||||||
|
await service.RunFileBrowserValidateTask(validateParams);
|
||||||
|
|
||||||
|
// Verify complete notification event was fired and the result
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(FileBrowserValidateCompleteNotification.Type, It.Is<FileBrowserValidateCompleteParams>(p => p.Succeeded == true)), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void InvalidFileValidationTest()
|
||||||
|
{
|
||||||
|
FileBrowserService service = new FileBrowserService();
|
||||||
|
service.RegisterValidatePathsCallback("TestService", ValidatePaths);
|
||||||
|
|
||||||
|
var validateParams = new FileBrowserValidateParams
|
||||||
|
{
|
||||||
|
// Do not pass any service so that the file validator will be null
|
||||||
|
ServiceType = "TestService",
|
||||||
|
SelectedFiles = new string[] { "" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var serviceHostMock = new Mock<IProtocolEndpoint>();
|
||||||
|
service.ServiceHost = serviceHostMock.Object;
|
||||||
|
|
||||||
|
// Validate files with null file validator
|
||||||
|
await service.RunFileBrowserValidateTask(validateParams);
|
||||||
|
|
||||||
|
// Verify complete notification event was fired and the result
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(FileBrowserValidateCompleteNotification.Type, It.Is<FileBrowserValidateCompleteParams>(p => p.Succeeded == false)), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
#region private methods
|
||||||
|
|
||||||
|
private bool ValidatePaths(FileBrowserValidateEventArgs eventArgs, out string message)
|
||||||
|
{
|
||||||
|
message = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
{
|
{
|
||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
|
||||||
var mockBackupOperation = new Mock<IBackupOperation>();
|
var mockBackupOperation = new Mock<IBackupOperation>();
|
||||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
||||||
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
||||||
@@ -45,7 +44,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
{
|
{
|
||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
|
||||||
var mockBackupOperation = new Mock<IBackupOperation>();
|
var mockBackupOperation = new Mock<IBackupOperation>();
|
||||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
||||||
taskMetaData.TaskExecutionMode = TaskExecutionMode.Script;
|
taskMetaData.TaskExecutionMode = TaskExecutionMode.Script;
|
||||||
@@ -70,7 +68,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
{
|
{
|
||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
|
||||||
var mockBackupOperation = new Mock<IBackupOperation>();
|
var mockBackupOperation = new Mock<IBackupOperation>();
|
||||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
||||||
|
|
||||||
@@ -103,7 +100,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
IBackupOperation backupOperation = new BackupOperationStub();
|
IBackupOperation backupOperation = new BackupOperationStub();
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
|
||||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
||||||
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
||||||
Assert.NotNull(sqlTask);
|
Assert.NotNull(sqlTask);
|
||||||
@@ -129,7 +125,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
{
|
{
|
||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
|
||||||
IBackupOperation backupOperation = new BackupOperationStub();
|
IBackupOperation backupOperation = new BackupOperationStub();
|
||||||
IBackupOperation backupOperation2 = new BackupOperationStub();
|
IBackupOperation backupOperation2 = new BackupOperationStub();
|
||||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
||||||
@@ -171,7 +166,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
{
|
{
|
||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
|
||||||
IBackupOperation backupOperation = new BackupOperationStub();
|
IBackupOperation backupOperation = new BackupOperationStub();
|
||||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
||||||
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for disaster recovery file validator
|
||||||
|
/// </summary>
|
||||||
|
public class DisasterRecoveryFileValidatorUnitTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ValidatorShouldReturnFalseForNullArgument()
|
||||||
|
{
|
||||||
|
string message;
|
||||||
|
bool result = DisasterRecoveryFileValidator.ValidatePaths(null, out message);
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMachineNameForLocalServer()
|
||||||
|
{
|
||||||
|
string machineName = DisasterRecoveryFileValidator.GetMachineName(DisasterRecoveryFileValidator.LocalSqlServer);
|
||||||
|
Assert.True(System.Environment.MachineName == machineName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMachineNameForNamedServer()
|
||||||
|
{
|
||||||
|
string testMachineName = "testmachine";
|
||||||
|
string machineName = DisasterRecoveryFileValidator.GetMachineName(string.Format("{0}\\testserver", testMachineName));
|
||||||
|
Assert.True(testMachineName == machineName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// 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 Microsoft.SqlTools.ServiceLayer.FileBrowser;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.FileBrowser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// File browser unit tests
|
||||||
|
/// </summary>
|
||||||
|
public class FileBrowserTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateFileBrowserOperationTest()
|
||||||
|
{
|
||||||
|
FileBrowserOperation operation = new FileBrowserOperation(null, "", null);
|
||||||
|
Assert.True(operation.FileTree != null);
|
||||||
|
|
||||||
|
// It should set "*" filter as default
|
||||||
|
Assert.True(operation.FileFilters.Length == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterFilesTest()
|
||||||
|
{
|
||||||
|
FileBrowserOperation operation = new FileBrowserOperation();
|
||||||
|
string[] supportedFilePaths = new string[] {"te\\s/t1.txt", "te!s.t2.bak" };
|
||||||
|
string[] unsupportedFilePaths = new string[] { "te.s*/t3.jpg", "t_est4.trn" };
|
||||||
|
string[] filters = new string[] { "*.txt", "*.bak"};
|
||||||
|
|
||||||
|
foreach (string path in supportedFilePaths)
|
||||||
|
{
|
||||||
|
Assert.True(operation.FilterFile(path, filters));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string path in unsupportedFilePaths)
|
||||||
|
{
|
||||||
|
Assert.False(operation.FilterFile(path, filters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExpandNodeShouldThrowExceptionForInvalidPath()
|
||||||
|
{
|
||||||
|
FileBrowserOperation operation = new FileBrowserOperation();
|
||||||
|
Exception exception = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
operation.PathSeparator = '/';
|
||||||
|
operation.ExpandSelectedNode("testdrive/filebrowser/test");
|
||||||
|
}
|
||||||
|
catch (FileBrowserException ex)
|
||||||
|
{
|
||||||
|
exception = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(exception);
|
||||||
|
Assert.Null(operation.FileTree.SelectedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateFileTreeTest()
|
||||||
|
{
|
||||||
|
FileTree tree = new FileTree();
|
||||||
|
Assert.NotNull(tree.RootNode);
|
||||||
|
Assert.Null(tree.SelectedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddFileTreeNodeChildTest()
|
||||||
|
{
|
||||||
|
FileTreeNode node1 = new FileTreeNode();
|
||||||
|
FileTreeNode node2 = new FileTreeNode();
|
||||||
|
node1.AddChildNode(node2);
|
||||||
|
Assert.NotNull(node1.Children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user