diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryFileValidator.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryFileValidator.cs
new file mode 100644
index 00000000..f9cb1663
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryFileValidator.cs
@@ -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
+{
+ ///
+ /// Validate selected file paths for backup/restore operations
+ ///
+ 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
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs
index b33df78f..4bae7958 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs
@@ -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 instance = new Lazy(() => new DisasterRecoveryService());
private static ConnectionService connectionService = null;
private SqlTaskManager sqlTaskManagerInstance = null;
+ private FileBrowserService fileBrowserService = null;
private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper();
///
@@ -78,6 +78,25 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
}
}
+ ///
+ /// Gets or sets the current filebrowser service instance
+ ///
+ internal FileBrowserService FileBrowserServiceInstance
+ {
+ get
+ {
+ if (fileBrowserService == null)
+ {
+ fileBrowserService = FileBrowserService.Instance;
+ }
+ return fileBrowserService;
+ }
+ set
+ {
+ fileBrowserService = value;
+ }
+ }
+
///
/// Initializes the service instance
///
@@ -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);
}
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserCloseRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserCloseRequest.cs
new file mode 100644
index 00000000..28dd3d55
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserCloseRequest.cs
@@ -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
+{
+ ///
+ /// Parameters to pass to close file browser
+ ///
+ public class FileBrowserCloseParams
+ {
+ ///
+ /// Connection uri
+ ///
+ public string OwnerUri;
+ }
+
+ ///
+ /// Response for closing the browser
+ ///
+ public class FileBrowserCloseResponse
+ {
+ ///
+ /// Result of the operation
+ ///
+ public bool Succeeded;
+
+ ///
+ /// Error message if any
+ ///
+ public string Message;
+ }
+
+ ///
+ /// Requst to close the file browser
+ ///
+ class FileBrowserCloseRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("filebrowser/close");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserExpandCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserExpandCompleteNotification.cs
new file mode 100644
index 00000000..ce246831
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserExpandCompleteNotification.cs
@@ -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
+{
+ ///
+ /// Event params for expanding a node
+ ///
+ public class FileBrowserExpandCompleteParams
+ {
+ ///
+ /// Expanded node
+ ///
+ public FileTreeNode ExpandedNode;
+
+ ///
+ /// Result of the operation
+ ///
+ public bool Succeeded;
+
+ ///
+ /// Error message if any
+ ///
+ public string Message;
+ }
+
+ ///
+ /// Notification for expand completion
+ ///
+ public class FileBrowserExpandCompleteNotification
+ {
+ public static readonly
+ EventType Type =
+ EventType.Create("filebrowser/expandcomplete");
+ }
+
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserExpandRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserExpandRequest.cs
new file mode 100644
index 00000000..5a90d981
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserExpandRequest.cs
@@ -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
+{
+ ///
+ /// Parameters for expanding a folder node
+ ///
+ public class FileBrowserExpandParams
+ {
+ ///
+ /// Connection uri
+ ///
+ public string OwnerUri;
+
+ ///
+ /// The path to expand the nodes for
+ ///
+ public string ExpandPath;
+ }
+
+ ///
+ /// Request to expand a node in the file browser
+ ///
+ public class FileBrowserExpandRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("filebrowser/expand");
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserOpenCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserOpenCompleteNotification.cs
new file mode 100644
index 00000000..39854c1d
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserOpenCompleteNotification.cs
@@ -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
+{
+ ///
+ /// Event params for opening a file browser
+ /// Returns full directory structure on the server side
+ ///
+ public class FileBrowserOpenCompleteParams
+ {
+ ///
+ /// Entire file/folder tree
+ ///
+ public FileTree FileTree;
+
+ ///
+ /// Result of the operation
+ ///
+ public bool Succeeded;
+
+ ///
+ /// Error message
+ ///
+ public string Message;
+ }
+
+ ///
+ /// Notification for completing file browser opening
+ ///
+ public class FileBrowserOpenCompleteNotification
+ {
+ public static readonly
+ EventType Type =
+ EventType.Create("filebrowser/opencomplete");
+ }
+
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserOpenRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserOpenRequest.cs
new file mode 100644
index 00000000..52cb2af5
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserOpenRequest.cs
@@ -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
+{
+ ///
+ /// Parameters for opening file browser
+ ///
+ public class FileBrowserOpenParams
+ {
+ ///
+ /// Connection uri
+ ///
+ public string OwnerUri;
+
+ ///
+ /// The initial path to expand the nodes for (e.g. Backup will set this path to default backup folder)
+ ///
+ public string ExpandPath;
+
+ ///
+ /// File extension filter (e.g. *.bak)
+ ///
+ public string[] FileFilters;
+ }
+
+ ///
+ /// Request to open a file browser
+ ///
+ public class FileBrowserOpenRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("filebrowser/open");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserValidateCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserValidateCompleteNotification.cs
new file mode 100644
index 00000000..2e23cf43
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserValidateCompleteNotification.cs
@@ -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
+{
+ ///
+ /// Event params for validation completion
+ ///
+ public class FileBrowserValidateCompleteParams
+ {
+ ///
+ /// Result of the operation
+ ///
+ public bool Succeeded;
+
+ ///
+ /// Error message if any
+ ///
+ public string Message;
+ }
+
+ ///
+ /// Notification for validation completion
+ ///
+ public class FileBrowserValidateCompleteNotification
+ {
+ public static readonly
+ EventType Type =
+ EventType.Create("filebrowser/validatecomplete");
+ }
+
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserValidateRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserValidateRequest.cs
new file mode 100644
index 00000000..f10ec1bf
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserValidateRequest.cs
@@ -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
+{
+ ///
+ /// Parameters for validating selected file paths
+ ///
+ public class FileBrowserValidateParams
+ {
+ ///
+ /// Connection uri
+ ///
+ public string OwnerUri;
+
+ ///
+ /// Type of service that uses the file browser
+ ///
+ public string ServiceType;
+
+ ///
+ /// Selected files
+ ///
+ public string[] SelectedFiles;
+ }
+
+ ///
+ /// Requst to validate the selected file paths
+ ///
+ class FileBrowserValidateRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("filebrowser/validate");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileTree.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileTree.cs
new file mode 100644
index 00000000..72ac4f2e
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileTree.cs
@@ -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
+{
+ ///
+ /// Tree to represent file/folder structure
+ ///
+ public class FileTree
+ {
+ ///
+ /// Root node of the tree
+ ///
+ public FileTreeNode RootNode { get; private set; }
+
+ ///
+ /// Selected node of the tree
+ ///
+ public FileTreeNode SelectedNode { get; set; }
+
+ ///
+ /// Constructor
+ ///
+ public FileTree()
+ {
+ this.RootNode = new FileTreeNode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileTreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileTreeNode.cs
new file mode 100644
index 00000000..da0d78d6
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileTreeNode.cs
@@ -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
+{
+ ///
+ /// Tree node to represent file or folder
+ ///
+ public class FileTreeNode
+ {
+ ///
+ /// Constructor
+ ///
+ public FileTreeNode()
+ {
+ this.Children = new List();
+ }
+
+ ///
+ /// Parent node
+ ///
+ public FileTreeNode Parent { get; private set; }
+
+ ///
+ /// List of children nodes
+ ///
+ public List 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; }
+
+ ///
+ /// File or folder name
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Full path
+ ///
+ public string FullPath { get; set; }
+
+ public void AddChildNode(FileTreeNode item)
+ {
+ item.Parent = this;
+ this.Children.Add(item);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserBase.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserBase.cs
new file mode 100644
index 00000000..0152d307
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserBase.cs
@@ -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;
+ }
+
+ ///
+ /// Base class for file browser
+ ///
+ public abstract class FileBrowserBase
+ {
+ private Enumerator enumerator = null;
+ protected SqlConnection sqlConnection = null;
+
+ protected Enumerator Enumerator
+ {
+ get
+ {
+ return this.enumerator = (this.enumerator ?? new Enumerator());
+ }
+ }
+
+ ///
+ /// Separator string for components of the file path. Defaults to \ for Windows and / for Linux
+ ///
+ internal char PathSeparator { get; set; }
+
+ ///
+ /// Returns the PathSeparator values of the Server.
+ ///
+ /// PathSeparator
+ 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];
+ }
+
+ ///
+ /// Enumerates the FileInfo objects associated with drives
+ ///
+ ///
+ ///
+ ///
+ internal static IEnumerable 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;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Enumerates files and folders that are immediate children of the given path on the server
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static IEnumerable 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) ;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserException.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserException.cs
new file mode 100644
index 00000000..f92684bc
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserException.cs
@@ -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
+{
+ ///
+ /// Exception raised from file browser operation
+ ///
+ internal sealed class FileBrowserException : Exception
+ {
+ internal FileBrowserException(string m) : base(m)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserOperation.cs
new file mode 100644
index 00000000..1e1dc3c7
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserOperation.cs
@@ -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
+{
+ ///
+ /// Implementation for file browser operation
+ ///
+ internal class FileBrowserOperation : FileBrowserBase
+ {
+ private FileTree fileTree;
+ private string expandPath;
+ private string[] fileFilters;
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public FileBrowserOperation()
+ {
+ this.fileTree = new FileTree();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The connection info
+ /// The file extension filters
+ 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);
+ }
+
+ ///
+ /// Expand nodes for the selected path.
+ ///
+ public void ExpandSelectedNode(string expandPath)
+ {
+ this.expandPath = expandPath;
+ if (!string.IsNullOrEmpty(this.expandPath))
+ {
+ var dirs = this.expandPath.TrimEnd(this.PathSeparator).Split(this.PathSeparator);
+ List 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);
+ }
+ }
+ }
+
+ ///
+ /// 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
+ ///
+ internal bool FilterFile(string fileName, string[] masks)
+ {
+ for (int index = 0; index < masks.Length; index++)
+ {
+ if (MatchFileToSubMask(fileName, masks[index]))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Compares a file name to the user specified mask using a regular expression
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserService.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserService.cs
new file mode 100644
index 00000000..468d0b27
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserService.cs
@@ -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
+{
+ ///
+ /// Main class for file browser service
+ ///
+ public sealed class FileBrowserService
+ {
+ private static readonly Lazy LazyInstance = new Lazy(() => new FileBrowserService());
+ public static FileBrowserService Instance => LazyInstance.Value;
+
+ // Cache file browser operations for expanding node request
+ private Dictionary ownerToFileBrowserMap = new Dictionary();
+ private Dictionary validatePathsCallbackMap = new Dictionary();
+ private ConnectionService connectionService = null;
+
+ ///
+ /// Signature for callback method that validates the selected file paths
+ ///
+ ///
+ public delegate bool ValidatePathsCallback(FileBrowserValidateEventArgs eventArgs, out string errorMessage);
+
+ internal ConnectionService ConnectionServiceInstance
+ {
+ get
+ {
+ if (connectionService == null)
+ {
+ connectionService = ConnectionService.Instance;
+ }
+ return connectionService;
+ }
+ set
+ {
+ connectionService = value;
+ }
+ }
+
+ ///
+ /// Service host object for sending/receiving requests/events.
+ /// Internal for testing purposes.
+ ///
+ internal IProtocolEndpoint ServiceHost
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Constructor
+ ///
+ public FileBrowserService()
+ {
+ }
+
+ ///
+ /// Register validate path callback
+ ///
+ ///
+ ///
+ public void RegisterValidatePathsCallback(string service, ValidatePathsCallback callback)
+ {
+ if (this.validatePathsCallbackMap.ContainsKey(service))
+ {
+ this.validatePathsCallbackMap.Remove(service);
+ }
+
+ this.validatePathsCallbackMap.Add(service, callback);
+ }
+
+ ///
+ /// Initializes the service instance
+ ///
+ ///
+ ///
+ 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 requestContext)
+ {
+ try
+ {
+ Task.Run(() => RunFileBrowserOpenTask(fileBrowserParams));
+ await requestContext.SendResult(true);
+ }
+ catch
+ {
+ await requestContext.SendResult(false);
+ }
+ }
+
+ internal async Task HandleFileBrowserExpandRequest(
+ FileBrowserExpandParams fileBrowserParams,
+ RequestContext requestContext)
+ {
+ try
+ {
+ Task.Run(() => RunFileBrowserExpandTask(fileBrowserParams));
+ await requestContext.SendResult(true);
+ }
+ catch
+ {
+ await requestContext.SendResult(false);
+ }
+ }
+
+ internal async Task HandleFileBrowserValidateRequest(
+ FileBrowserValidateParams fileBrowserParams,
+ RequestContext requestContext)
+ {
+ try
+ {
+ Task.Run(() => RunFileBrowserValidateTask(fileBrowserParams));
+ await requestContext.SendResult(true);
+ }
+ catch
+ {
+ await requestContext.SendResult(false);
+ }
+ }
+
+ internal async Task HandleFileBrowserCloseRequest(
+ FileBrowserCloseParams fileBrowserParams,
+ RequestContext 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserValidateEventArgs.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserValidateEventArgs.cs
new file mode 100644
index 00000000..442e0dcd
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserValidateEventArgs.cs
@@ -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
+{
+ ///
+ /// Event arguments for validating selected files in file browser
+ ///
+ public sealed class FileBrowserValidateEventArgs : EventArgs
+ {
+ ///
+ /// Connection uri
+ ///
+ public string OwnerUri { get; set; }
+
+ ///
+ /// Service which provide validation callback
+ ///
+ public string ServiceType { get; set; }
+
+ ///
+ /// Selected files
+ ///
+ public string[] FilePaths { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/ServiceConstants.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/ServiceConstants.cs
new file mode 100644
index 00000000..54cc96f1
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/ServiceConstants.cs
@@ -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
+{
+ ///
+ /// List of services that provide file validation callback to file browser service
+ ///
+ public static class FileValidationServiceConstants
+ {
+ ///
+ /// Backup
+ ///
+ public const string Backup = "Backup";
+
+ ///
+ /// Restore
+ ///
+ public const string Restore = "Restore";
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
index 7e974337..26c23f71 100755
--- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
@@ -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()
{ }
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx
index 0822ea24..0f97e781 100755
--- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx
@@ -1815,6 +1815,14 @@
Backup Database
+
+ The provided path specifies a directory but a file path is required: {0}
+
+
+
+ Cannot verify the existence of the backup file location: {0}
+
+
In progress
@@ -1919,4 +1927,8 @@
scripting
+
+ Cannot access the specified path on the server: {0}
+
+
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings
index 01122b85..3515ef10 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings
@@ -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
\ No newline at end of file
+ScriptTaskName = scripting
+
+############################################################################
+# File Browser Validation Errors
+InvalidPathError = Cannot access the specified path on the server: {0}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf
index 734bb2e8..f4312ad7 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf
@@ -2250,6 +2250,21 @@
scripting
+
+ The provided path specifies a directory but a file path is required: {0}
+ The file name specified is also a directory name: {0}
+
+
+
+ Cannot verify the existence of the backup file location: {0}
+ Cannot verify the existence of the backup file location: {0}
+
+
+
+ Cannot access the specified path on the server: {0}
+ Cannot access the specified path on the server: {0}
+
+