mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -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.Contracts;
|
||||
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.DisasterRecovery.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation;
|
||||
using Microsoft.SqlTools.ServiceLayer.FileBrowser;
|
||||
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
||||
|
||||
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 ConnectionService connectionService = null;
|
||||
private SqlTaskManager sqlTaskManagerInstance = null;
|
||||
private FileBrowserService fileBrowserService = null;
|
||||
private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper();
|
||||
|
||||
/// <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>
|
||||
/// Initializes the service instance
|
||||
/// </summary>
|
||||
@@ -89,14 +108,18 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
// Create backup
|
||||
serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest);
|
||||
|
||||
// Create respore task
|
||||
// Create restore task
|
||||
serviceHost.SetRequestHandler(RestoreRequest.Type, HandleRestoreRequest);
|
||||
|
||||
// Create respore plan
|
||||
// Create restore plan
|
||||
serviceHost.SetRequestHandler(RestorePlanRequest.Type, HandleRestorePlanRequest);
|
||||
|
||||
// Create respore config
|
||||
// Create restore config
|
||||
serviceHost.SetRequestHandler(RestoreConfigInfoRequest.Type, HandleRestoreConfigInfoRequest);
|
||||
|
||||
// Register file path validation callbacks
|
||||
FileBrowserServiceInstance.RegisterValidatePathsCallback(FileValidationServiceConstants.Backup, DisasterRecoveryFileValidator.ValidatePaths);
|
||||
FileBrowserServiceInstance.RegisterValidatePathsCallback(FileValidationServiceConstants.Restore, DisasterRecoveryFileValidator.ValidatePaths);
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
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)
|
||||
{
|
||||
return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri);
|
||||
@@ -4834,6 +4858,12 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string BackupTaskName = "BackupTaskName";
|
||||
|
||||
|
||||
public const string BackupPathIsFolderError = "BackupPathIsFolderError";
|
||||
|
||||
|
||||
public const string InvalidBackupPathError = "InvalidBackupPathError";
|
||||
|
||||
|
||||
public const string TaskInProgress = "TaskInProgress";
|
||||
|
||||
|
||||
@@ -4912,6 +4942,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string ScriptTaskName = "ScriptTaskName";
|
||||
|
||||
|
||||
public const string InvalidPathError = "InvalidPathError";
|
||||
|
||||
|
||||
private Keys()
|
||||
{ }
|
||||
|
||||
|
||||
@@ -1815,6 +1815,14 @@
|
||||
<value>Backup Database</value>
|
||||
<comment></comment>
|
||||
</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">
|
||||
<value>In progress</value>
|
||||
<comment></comment>
|
||||
@@ -1919,4 +1927,8 @@
|
||||
<value>scripting</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="InvalidPathError" xml:space="preserve">
|
||||
<value>Cannot access the specified path on the server: {0}</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -807,6 +807,10 @@ prototype_file_noApplicableFileGroup = No Applicable Filegroup
|
||||
# Backup Service
|
||||
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
|
||||
TaskInProgress = In progress
|
||||
@@ -840,4 +844,8 @@ TheLastBackupTaken = The last backup taken ({0})
|
||||
|
||||
############################################################################
|
||||
# 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>
|
||||
<note></note>
|
||||
</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>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -1,16 +1,16 @@
|
||||
// This file was generated by a T4 Template. Do not modify directly, instead update the SmoQueryModelDefinition.xml file
|
||||
// and re-run the T4 template. This can be done in Visual Studio by right-click in and choosing "Run Custom Tool",
|
||||
// or from the command-line on any platform by running "build.cmd -Target=CodeGen" or "build.sh -Target=CodeGen".
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.Smo.Broker;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
|
||||
{
|
||||
// This file was generated by a T4 Template. Do not modify directly, instead update the SmoQueryModelDefinition.xml file
|
||||
// and re-run the T4 template. This can be done in Visual Studio by right-click in and choosing "Run Custom Tool",
|
||||
// or from the command-line on any platform by running "build.cmd -Target=CodeGen" or "build.sh -Target=CodeGen".
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.Smo.Broker;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
|
||||
{
|
||||
|
||||
[Export(typeof(SmoQuerier))]
|
||||
internal partial class SqlDatabaseQuerier: SmoQuerier
|
||||
@@ -1628,5 +1628,5 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
|
||||
return Enumerable.Empty<SqlSmoObject>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
// THIS FILE IS GENERATED BY A CODEGEN TOOL. DO NOT EDIT!!!!
|
||||
// IF YOU NEED TO MAKE CHANGES, EDIT THE .TT FILE!!!
|
||||
|
||||
// To regenerate either (1) save the .TT file from Visual Studio or (2) run the build script codegen task.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
{
|
||||
internal partial class Scripter
|
||||
{
|
||||
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
// THIS FILE IS GENERATED BY A CODEGEN TOOL. DO NOT EDIT!!!!
|
||||
// IF YOU NEED TO MAKE CHANGES, EDIT THE .TT FILE!!!
|
||||
|
||||
// To regenerate either (1) save the .TT file from Visual Studio or (2) run the build script codegen task.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
{
|
||||
internal partial class Scripter
|
||||
{
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
AddSupportedType(DeclarationType.Table, GetTableScripts, "Table", "table");
|
||||
@@ -243,6 +243,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||
using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
||||
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.Test.Common;
|
||||
using Moq;
|
||||
@@ -77,7 +79,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||
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,
|
||||
BackupType.Full,
|
||||
@@ -101,7 +103,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
||||
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||
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,
|
||||
BackupType.Full,
|
||||
@@ -134,7 +136,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
||||
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||
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 cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
|
||||
@@ -186,7 +188,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
||||
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
|
||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||
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 cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
|
||||
@@ -228,6 +230,86 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
||||
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
|
||||
|
||||
private string CreateCertificate(SqlTestDb testDb)
|
||||
@@ -258,7 +340,7 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
|
||||
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);
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||
var mockBackupOperation = new Mock<IBackupOperation>();
|
||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
||||
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
||||
@@ -45,7 +44,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||
{
|
||||
using (SqlTaskManager manager = new SqlTaskManager())
|
||||
{
|
||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||
var mockBackupOperation = new Mock<IBackupOperation>();
|
||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
||||
taskMetaData.TaskExecutionMode = TaskExecutionMode.Script;
|
||||
@@ -69,8 +67,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||
public async Task VerifyRunningMultipleBackupTasks()
|
||||
{
|
||||
using (SqlTaskManager manager = new SqlTaskManager())
|
||||
{
|
||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||
{
|
||||
var mockBackupOperation = new Mock<IBackupOperation>();
|
||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
|
||||
|
||||
@@ -103,7 +100,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||
using (SqlTaskManager manager = new SqlTaskManager())
|
||||
{
|
||||
IBackupOperation backupOperation = new BackupOperationStub();
|
||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
||||
SqlTask sqlTask = manager.CreateTask<SqlTask>(taskMetaData);
|
||||
Assert.NotNull(sqlTask);
|
||||
@@ -129,7 +125,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||
{
|
||||
using (SqlTaskManager manager = new SqlTaskManager())
|
||||
{
|
||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||
IBackupOperation backupOperation = new BackupOperationStub();
|
||||
IBackupOperation backupOperation2 = new BackupOperationStub();
|
||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
||||
@@ -171,7 +166,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||
{
|
||||
using (SqlTaskManager manager = new SqlTaskManager())
|
||||
{
|
||||
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||
IBackupOperation backupOperation = new BackupOperationStub();
|
||||
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
|
||||
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