mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-21 17:24:03 -05:00
* Make nullable warnings a per file opt-in * Remove unneeded compiler directives * Remove compiler directive for User Data
1416 lines
52 KiB
C#
1416 lines
52 KiB
C#
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
//
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Microsoft.SqlServer.Management.Common;
|
|
using Microsoft.SqlServer.Management.Smo;
|
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
|
using Microsoft.SqlTools.Utility;
|
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
using System.Diagnostics;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
|
|
{
|
|
public interface IRestoreDatabaseTaskDataObject
|
|
{
|
|
string DataFilesFolder { get; set; }
|
|
string DefaultDataFileFolder { get; }
|
|
bool RelocateAllFiles { get; set; }
|
|
string LogFilesFolder { get; set; }
|
|
string DefaultLogFileFolder { get; }
|
|
List<DbFile> DbFiles { get; }
|
|
string DefaultBackupFolder { get; }
|
|
|
|
RestoreOptions RestoreOptions { get; }
|
|
|
|
string DefaultStandbyFile { get; }
|
|
bool IsTailLogBackupPossible { get; }
|
|
bool IsTailLogBackupWithNoRecoveryPossible { get; }
|
|
|
|
bool TailLogWithNoRecovery { get; set; }
|
|
|
|
string TailLogBackupFile { get; set; }
|
|
|
|
string DefaultTailLogbackupFile { get; }
|
|
|
|
RestorePlan RestorePlan { get; }
|
|
|
|
bool CloseExistingConnections { get; set; }
|
|
|
|
RestoreParams RestoreParams { get; set; }
|
|
|
|
bool BackupTailLog { get; set; }
|
|
|
|
string DefaultSourceDbName { get; }
|
|
|
|
string SourceDatabaseName { get; set; }
|
|
|
|
List<String> SourceDbNames { get; }
|
|
|
|
bool OverwriteTargetDatabase { get; set; }
|
|
|
|
string DefaultTargetDbName { get; }
|
|
string TargetDatabaseName { get; set; }
|
|
|
|
bool CanDropExistingConnections { get; }
|
|
|
|
|
|
}
|
|
/// <summary>
|
|
/// Includes the plan with all the data required to do a restore operation on server
|
|
/// </summary>
|
|
public class RestoreDatabaseTaskDataObject : SmoScriptableOperationWithFullDbAccess, IRestoreDatabaseTaskDataObject
|
|
{
|
|
|
|
private const char BackupMediaNameSeparator = ',';
|
|
private DatabaseRestorePlanner restorePlanner;
|
|
private string tailLogBackupFile;
|
|
private BackupSetsFilterInfo backupSetsFilterInfo = new BackupSetsFilterInfo();
|
|
private bool? isTailLogBackupPossible = null;
|
|
private bool? isTailLogBackupWithNoRecoveryPossible = null;
|
|
private string backupMediaList = string.Empty;
|
|
private Server server;
|
|
private static readonly DeviceType[] managedInstanceSupportedDeviceTypes = { DeviceType.Url };
|
|
private static readonly DeviceType[] defaultSupportedDeviceTypes = { DeviceType.File, DeviceType.Url };
|
|
private static readonly Dictionary<Edition, DeviceType[]> specialEngineEditionSupportedDeviceTypes = new Dictionary<Edition, DeviceType[]>
|
|
{
|
|
{ Edition.SqlManagedInstance, managedInstanceSupportedDeviceTypes },
|
|
};
|
|
|
|
public RestoreDatabaseTaskDataObject(Server server, String databaseName)
|
|
{
|
|
PlanUpdateRequired = true;
|
|
this.server = server;
|
|
this.Util = new RestoreUtil(server);
|
|
restorePlanner = new DatabaseRestorePlanner(server);
|
|
|
|
if (String.IsNullOrEmpty(databaseName))
|
|
{
|
|
this.restorePlanner = new DatabaseRestorePlanner(server);
|
|
}
|
|
else
|
|
{
|
|
this.restorePlanner = new DatabaseRestorePlanner(server, databaseName);
|
|
this.targetDbName = databaseName;
|
|
}
|
|
|
|
this.restorePlanner.TailLogBackupFile = this.Util.GetDefaultTailLogbackupFile(databaseName);
|
|
this.restoreOptions = new RestoreOptions();
|
|
//the server will send events in intervals of 5 percent
|
|
this.restoreOptions.PercentCompleteNotification = 5;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Boolean indicating whether the relocate all files checkbox was checked
|
|
/// </summary>
|
|
public bool RelocateAllFiles { get; set; }
|
|
|
|
/// <summary>
|
|
/// Restore session id
|
|
/// </summary>
|
|
public string SessionId { get; set; }
|
|
|
|
public string TargetDatabaseName
|
|
{
|
|
get
|
|
{
|
|
return string.IsNullOrEmpty(targetDbName) ? DefaultSourceDbName : targetDbName;
|
|
}
|
|
set
|
|
{
|
|
this.targetDbName = value;
|
|
}
|
|
}
|
|
|
|
public bool IsValid
|
|
{
|
|
get
|
|
{
|
|
return this.Server != null && this.RestorePlanner != null && ActiveException == null;
|
|
}
|
|
}
|
|
|
|
public RestoreParams RestoreParams { get; set; }
|
|
|
|
/// <summary>
|
|
/// Database names includes in the restore plan
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<String> SourceDbNames
|
|
{
|
|
get
|
|
{
|
|
if (RestorePlanner.ReadHeaderFromMedia)
|
|
{
|
|
return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName);
|
|
}
|
|
else
|
|
{
|
|
return Util.GetSourceDbNames();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the db names that have backupsets
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<String> GetDatabaseNamesWithBackupSets()
|
|
{
|
|
return Util.GetSourceDbNames();
|
|
}
|
|
|
|
public bool OverwriteTargetDatabase { get; set; }
|
|
|
|
public string DefaultTargetDbName
|
|
{
|
|
get
|
|
{
|
|
return Server.ConnectionContext.DatabaseName;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Current sqlserver instance
|
|
/// </summary>
|
|
public override Server Server
|
|
{
|
|
get
|
|
{
|
|
return server;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recent exception that was thrown
|
|
/// Displayed at the top of the dialog
|
|
/// </summary>
|
|
public Exception ActiveException { get; set; }
|
|
|
|
public Exception CreateOrUpdateRestorePlanException { get; set; }
|
|
|
|
/// <summary>
|
|
/// Add a backup file to restore plan media list
|
|
/// </summary>
|
|
/// <param name="filePaths"></param>
|
|
/// <param name="deviceType"></deviceType>
|
|
public void AddDevices(string filePaths, DeviceType deviceType)
|
|
{
|
|
ThrowIfUnsupportedDeviceType(this.Server.EngineEdition, deviceType);
|
|
backupMediaList = filePaths;
|
|
PlanUpdateRequired = true;
|
|
if (!string.IsNullOrWhiteSpace(filePaths))
|
|
{
|
|
string[] files = filePaths.Split(BackupMediaNameSeparator);
|
|
files = files.Select(x => x.Trim()).ToArray();
|
|
foreach (var file in files)
|
|
{
|
|
if (!this.RestorePlanner.BackupMediaList.Any(x => x.Name == file))
|
|
{
|
|
this.RestorePlanner.BackupMediaList.Add(new BackupDeviceItem
|
|
{
|
|
DeviceType = deviceType,
|
|
Name = file
|
|
});
|
|
}
|
|
}
|
|
|
|
var itemsToRemove = this.RestorePlanner.BackupMediaList.Where(x => !files.Contains(x.Name)).ToList();
|
|
foreach (var item in itemsToRemove)
|
|
{
|
|
this.RestorePlanner.BackupMediaList.Remove(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ThrowIfUnsupportedDeviceType(Edition engineEdition, DeviceType deviceType)
|
|
{
|
|
if (!IsSupportedDeviceType(engineEdition, deviceType))
|
|
{
|
|
throw new UnsupportedDeviceTypeException(engineEdition, deviceType);
|
|
}
|
|
}
|
|
|
|
private bool IsSupportedDeviceType(Edition engineEdition, DeviceType deviceType)
|
|
{
|
|
return (defaultSupportedDeviceTypes.Contains(deviceType) && !specialEngineEditionSupportedDeviceTypes.ContainsKey(engineEdition))
|
|
|| (specialEngineEditionSupportedDeviceTypes.ContainsKey(engineEdition) && specialEngineEditionSupportedDeviceTypes[engineEdition].Contains(deviceType));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns the last backup taken
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string GetLastBackupTaken()
|
|
{
|
|
string lastBackup = string.Empty;
|
|
int lastSelectedIndex = GetLastSelectedBackupSetIndex();
|
|
BackupSet lastSelectedBackupSet = lastSelectedIndex >= 0 && this.RestorePlan.RestoreOperations != null && this.RestorePlan.RestoreOperations.Count > 0 ?
|
|
this.RestorePlan.RestoreOperations[lastSelectedIndex].BackupSet : null;
|
|
if (this.RestorePlanner.RestoreToLastBackup &&
|
|
lastSelectedBackupSet != null &&
|
|
this.RestorePlan.RestoreOperations.Count > 0 &&
|
|
lastSelectedBackupSet != null)
|
|
{
|
|
bool isTheLastOneSelected = lastSelectedIndex == this.RestorePlan.RestoreOperations.Count - 1;
|
|
DateTime backupTime = lastSelectedBackupSet.BackupStartDate;
|
|
string backupTimeStr = backupTime.ToLongDateString() + " " + backupTime.ToLongTimeString();
|
|
lastBackup = isTheLastOneSelected ?
|
|
string.Format(CultureInfo.CurrentCulture, SR.TheLastBackupTaken, (backupTimeStr)) : backupTimeStr;
|
|
}
|
|
else if (GetFirstSelectedBackupSetIndex() == 0 && !this.RestorePlanner.RestoreToLastBackup)
|
|
{
|
|
lastBackup = this.CurrentRestorePointInTime.Value.ToLongDateString() +
|
|
" " + this.CurrentRestorePointInTime.Value.ToLongTimeString();
|
|
}
|
|
return lastBackup;
|
|
|
|
}
|
|
|
|
public override void Execute(TaskExecutionMode mode)
|
|
{
|
|
RestorePlanToExecute = null;
|
|
UpdateRestoreTaskObject();
|
|
if (IsValid && RestorePlan.RestoreOperations != null && RestorePlan.RestoreOperations.Any())
|
|
{
|
|
GetRestorePlanForExecutionAndScript();
|
|
}
|
|
|
|
base.Execute(mode);
|
|
}
|
|
|
|
public ConnectionInfo ConnectionInfo { get; set; }
|
|
|
|
public override string ServerName
|
|
{
|
|
get
|
|
{
|
|
if (this.ConnectionInfo != null)
|
|
{
|
|
return this.ConnectionInfo.ConnectionDetails.ServerName;
|
|
}
|
|
|
|
return this.Server.Name;
|
|
}
|
|
}
|
|
|
|
public override string DatabaseName
|
|
{
|
|
get
|
|
{
|
|
return TargetDatabaseName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the restore operations
|
|
/// </summary>
|
|
public override void Execute()
|
|
{
|
|
try
|
|
{
|
|
if (RestorePlanToExecute != null)
|
|
{
|
|
// Restore Plan should be already created and updated at this point
|
|
|
|
if (RestorePlanToExecute != null && RestorePlanToExecute.RestoreOperations.Count > 0)
|
|
{
|
|
RestorePlanToExecute.PercentComplete += (object sender, PercentCompleteEventArgs e) =>
|
|
{
|
|
OnMessageAdded(new TaskMessage { Description = $"{e.Percent}%", Status = SqlTaskStatus.InProgress });
|
|
};
|
|
RestorePlanToExecute.Execute();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException(SR.RestoreNotSupported);
|
|
}
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
Logger.Write(TraceEventType.Information, $"Failed to execute restore task. error: {ex.Message}");
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (this.Server.ConnectionContext.IsOpen)
|
|
{
|
|
this.Server.ConnectionContext.Disconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets RestorePlan to perform restore and to script
|
|
/// </summary>
|
|
public RestorePlan GetRestorePlanForExecutionAndScript()
|
|
{
|
|
// One of the reasons the existing plan is not used and a new plan is created to execute is the order of the backupsets and
|
|
// the last backupset is important. User can choose specific backupset in the plan to restore and
|
|
// if the last backup set is not selected from the list, it means the item before that should be the real last item
|
|
// with simply removing the item from the list, the order won't work correctlly. Smo considered the last item removed and
|
|
// doesn't consider the previous item as the last item. Creating a new list and adding backpsets is the only way to make it work
|
|
// and the reason the last backup set is important is that it has to restore with recovery mode while the others are restored with no recovery
|
|
|
|
this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated.
|
|
//Clear any existing exceptions as new plan is getting recreated.
|
|
this.CreateOrUpdateRestorePlanException = null;
|
|
bool tailLogBackup = this.RestorePlanner.BackupTailLog;
|
|
if (this.PlanUpdateRequired)
|
|
{
|
|
this.RestorePlan = this.RestorePlanner.CreateRestorePlan(this.RestoreOptions);
|
|
|
|
this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName);
|
|
}
|
|
RestorePlan rp = new RestorePlan(this.Server);
|
|
rp.RestoreAction = RestoreActionType.Database;
|
|
if (this.RestorePlan != null)
|
|
{
|
|
if (this.RestorePlan.TailLogBackupOperation != null && tailLogBackup)
|
|
{
|
|
rp.TailLogBackupOperation = this.RestorePlan.TailLogBackupOperation;
|
|
}
|
|
int i = 0;
|
|
foreach (Restore res in this.RestorePlan.RestoreOperations)
|
|
{
|
|
if (this.backupSetsFilterInfo.IsBackupSetSelected(res.BackupSet))
|
|
{
|
|
rp.RestoreOperations.Add(res);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
this.SetRestorePlanProperties(rp);
|
|
RestorePlanToExecute = rp;
|
|
return rp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// For test purpose only. The restore plan that's used to execute
|
|
/// </summary>
|
|
internal RestorePlan RestorePlanToExecute { get; set; }
|
|
|
|
/// <summary>
|
|
/// Restore Util
|
|
/// </summary>
|
|
public RestoreUtil Util { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
/// SMO database restore planner used to create a restore plan
|
|
/// </summary>
|
|
public DatabaseRestorePlanner RestorePlanner
|
|
{
|
|
get { return restorePlanner; }
|
|
}
|
|
|
|
public bool PlanUpdateRequired { get; private set; }
|
|
|
|
/// <summary>
|
|
/// File to backup tail log before doing the restore
|
|
/// </summary>
|
|
public string TailLogBackupFile
|
|
{
|
|
get { return tailLogBackupFile; }
|
|
set
|
|
{
|
|
if (tailLogBackupFile == null || !tailLogBackupFile.Equals(value))
|
|
{
|
|
this.RestorePlanner.TailLogBackupFile = value;
|
|
this.PlanUpdateRequired = true;
|
|
this.tailLogBackupFile = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
private RestoreOptions restoreOptions;
|
|
|
|
public RestoreOptions RestoreOptions
|
|
{
|
|
get { return restoreOptions; }
|
|
}
|
|
|
|
private string dataFilesFolder = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Folder for all data files when relocate all files option is used
|
|
/// </summary>
|
|
public string DataFilesFolder
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrEmpty(this.dataFilesFolder))
|
|
{
|
|
this.dataFilesFolder = this.DefaultDataFileFolder;
|
|
}
|
|
return this.dataFilesFolder;
|
|
}
|
|
set
|
|
{
|
|
if (this.dataFilesFolder == null || !this.dataFilesFolder.Equals(value))
|
|
{
|
|
try
|
|
{
|
|
Uri pathUri;
|
|
bool fUriCreated = Uri.TryCreate(value, UriKind.Absolute, out pathUri);
|
|
|
|
if (fUriCreated && pathUri.Scheme == "https")
|
|
{
|
|
this.dataFilesFolder = value;
|
|
}
|
|
else
|
|
{
|
|
this.dataFilesFolder = GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext));
|
|
}
|
|
if (string.IsNullOrEmpty(this.dataFilesFolder))
|
|
{
|
|
this.dataFilesFolder = this.Server.DefaultFile;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.ActiveException = ex;
|
|
}
|
|
|
|
this.UpdateDbFiles();
|
|
}
|
|
}
|
|
}
|
|
|
|
private string logFilesFolder = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Folder for all log files when relocate all files option is used
|
|
/// </summary>
|
|
public string LogFilesFolder
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrEmpty(this.logFilesFolder))
|
|
{
|
|
this.logFilesFolder = this.DefaultLogFileFolder;
|
|
}
|
|
return this.logFilesFolder;
|
|
|
|
}
|
|
set
|
|
{
|
|
if (this.logFilesFolder == null || !this.logFilesFolder.Equals(value))
|
|
{
|
|
try
|
|
{
|
|
Uri pathUri;
|
|
bool fUriCreated = Uri.TryCreate(value, UriKind.Absolute, out pathUri);
|
|
|
|
if (fUriCreated && pathUri.Scheme == "https")
|
|
{
|
|
this.logFilesFolder = value;
|
|
}
|
|
else
|
|
{
|
|
this.logFilesFolder = GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext));
|
|
}
|
|
if (string.IsNullOrEmpty(this.logFilesFolder))
|
|
{
|
|
this.logFilesFolder = Server.DefaultLog;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.ActiveException = ex;
|
|
}
|
|
this.UpdateDbFiles();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether [is tail log backup possible].
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if [is tail log backup possible]; otherwise, <c>false</c>.
|
|
/// </returns>
|
|
public bool IsTailLogBackupPossible
|
|
{
|
|
get
|
|
{
|
|
if (!isTailLogBackupPossible.HasValue)
|
|
{
|
|
//TODO: get the value from DatabaseRestorePlanner.IsTailLogBackupPossible
|
|
if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName))
|
|
{
|
|
isTailLogBackupPossible = false;
|
|
}
|
|
else
|
|
{
|
|
Database db = this.Server.Databases[this.RestorePlanner.DatabaseName];
|
|
if (db == null)
|
|
{
|
|
isTailLogBackupPossible = false;
|
|
}
|
|
else
|
|
{
|
|
db.Refresh();
|
|
if (db.Status != DatabaseStatus.Normal && db.Status != DatabaseStatus.Suspect && db.Status != DatabaseStatus.EmergencyMode)
|
|
{
|
|
isTailLogBackupPossible = false;
|
|
}
|
|
else if (db.RecoveryModel == RecoveryModel.Full || db.RecoveryModel == RecoveryModel.BulkLogged)
|
|
{
|
|
isTailLogBackupPossible = true;
|
|
}
|
|
else
|
|
{
|
|
isTailLogBackupPossible = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return isTailLogBackupPossible.Value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether [is tail log backup with NORECOVERY possible].
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if [is tail log backup with NORECOVERY possible]; otherwise, <c>false</c>.
|
|
/// </returns>
|
|
public bool IsTailLogBackupWithNoRecoveryPossible
|
|
{
|
|
get
|
|
{
|
|
if (!isTailLogBackupWithNoRecoveryPossible.HasValue)
|
|
{
|
|
//TODO: get the value from DatabaseRestorePlanner.IsTailLogBackupWithNoRecoveryPossible
|
|
string databaseName = this.RestorePlanner.DatabaseName;
|
|
if (!IsTailLogBackupPossible)
|
|
{
|
|
isTailLogBackupWithNoRecoveryPossible = false;
|
|
}
|
|
else
|
|
{
|
|
Database db = this.Server.Databases[databaseName];
|
|
if (db == null)
|
|
{
|
|
isTailLogBackupWithNoRecoveryPossible = false;
|
|
}
|
|
else if (Server.Version.Major > 10 && db.DatabaseEngineType == DatabaseEngineType.Standalone && !String.IsNullOrEmpty(db.AvailabilityGroupName))
|
|
{
|
|
isTailLogBackupWithNoRecoveryPossible = false;
|
|
}
|
|
else if (db.DatabaseEngineType == DatabaseEngineType.Standalone && db.IsMirroringEnabled)
|
|
{
|
|
isTailLogBackupWithNoRecoveryPossible = false;
|
|
}
|
|
else
|
|
{
|
|
isTailLogBackupWithNoRecoveryPossible = true;
|
|
}
|
|
}
|
|
}
|
|
return isTailLogBackupWithNoRecoveryPossible.Value;
|
|
}
|
|
}
|
|
|
|
public string DefaultStandbyFile
|
|
{
|
|
get
|
|
{
|
|
return Util.GetDefaultStandbyFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty);
|
|
}
|
|
}
|
|
|
|
public string DefaultTailLogbackupFile
|
|
{
|
|
get
|
|
{
|
|
return Util.GetDefaultTailLogbackupFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether [prompt before each backup].
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if [prompt before each backup]; otherwise, <c>false</c>.
|
|
/// </value>
|
|
public bool PromptBeforeEachBackup { get; set; }
|
|
|
|
private void UpdateDbFiles()
|
|
{
|
|
try
|
|
{
|
|
foreach (DbFile dbFile in this.DbFiles)
|
|
{
|
|
string fileName = this.GetTargetDbFilePhysicalName(dbFile.PhysicalName);
|
|
if (!dbFile.DbFileType.Equals("Log"))
|
|
{
|
|
if (!string.IsNullOrEmpty(this.DataFilesFolder))
|
|
{
|
|
dbFile.PhysicalNameRelocate = CombineToServerConnectionPath(this.DataFilesFolder, fileName);
|
|
}
|
|
else
|
|
{
|
|
dbFile.PhysicalNameRelocate = fileName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!string.IsNullOrEmpty(this.LogFilesFolder))
|
|
{
|
|
dbFile.PhysicalNameRelocate = CombineToServerConnectionPath(this.LogFilesFolder, fileName);
|
|
}
|
|
else
|
|
{
|
|
dbFile.PhysicalNameRelocate = fileName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.ActiveException = ex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Combining the root and file name using the server connection file path seperator
|
|
/// </summary>
|
|
private string CombineToServerConnectionPath(string root, string filePath)
|
|
{
|
|
string pathSeparatorFromServerConnection = PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext);
|
|
string combinedPath = PathWrapper.Combine(root, filePath);
|
|
|
|
// Make sure all path seperators are server connection separator
|
|
string result = combinedPath.Replace(Path.DirectorySeparatorChar.ToString(), pathSeparatorFromServerConnection);
|
|
|
|
// Make sure there's not any double file seperator in the path
|
|
result = result.Replace(pathSeparatorFromServerConnection + pathSeparatorFromServerConnection,
|
|
pathSeparatorFromServerConnection);
|
|
return result;
|
|
}
|
|
|
|
private string GetDirectoryName(string filePath)
|
|
{
|
|
string localPath = ConvertToLocalMachinePath(filePath);
|
|
localPath = PathWrapper.GetDirectoryName(localPath);
|
|
return ConvertToServerConnectionPath(localPath);
|
|
}
|
|
|
|
private string ConvertToLocalMachinePath(string filePath) {
|
|
string pathSeparator = Path.DirectorySeparatorChar.ToString();
|
|
string localPath = filePath.Replace("/", pathSeparator);
|
|
localPath = localPath.Replace("\\", pathSeparator);
|
|
return localPath;
|
|
}
|
|
|
|
private string ConvertToServerConnectionPath(string filePath) {
|
|
string pathSeparator = PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext);
|
|
string serverPath = filePath.Replace("/", pathSeparator);
|
|
serverPath = serverPath.Replace("\\", pathSeparator);
|
|
return serverPath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the Restore folder location of those db files whose orginal directory location
|
|
/// is not present in the destination computer.
|
|
/// </summary>
|
|
internal void UpdateDBFilesPhysicalRelocate()
|
|
{
|
|
foreach (DbFile item in DbFiles)
|
|
{
|
|
string fileName = this.GetTargetDbFilePhysicalName(item.PhysicalName);
|
|
item.PhysicalNameRelocate = PathWrapper.Combine(PathWrapper.GetDirectoryName(item.PhysicalName),
|
|
fileName);
|
|
Uri pathUri;
|
|
bool fUriCreated = Uri.TryCreate(item.PhysicalNameRelocate, UriKind.Absolute, out pathUri);
|
|
if ((!fUriCreated || pathUri.Scheme != Uri.UriSchemeHttps) &&
|
|
!Directory.Exists(Path.GetDirectoryName(item.PhysicalNameRelocate)))
|
|
{
|
|
string directoryPath = string.Empty;
|
|
if (string.Compare(item.DbFileType, SR.Log, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
directoryPath = Util.GetDefaultLogFileFolder();
|
|
}
|
|
else
|
|
{
|
|
directoryPath = Util.GetDefaultDataFileFolder();
|
|
}
|
|
|
|
item.PhysicalNameRelocate = PathWrapper.Combine(directoryPath, fileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<DbFile> dbFiles = new List<DbFile>();
|
|
|
|
/// <summary>
|
|
/// List of files of the source database or in the backup file
|
|
/// </summary>
|
|
public List<DbFile> DbFiles
|
|
{
|
|
get { return dbFiles; }
|
|
}
|
|
|
|
internal RestorePlan restorePlan;
|
|
|
|
/// <summary>
|
|
/// Restore plan to do the restore
|
|
/// </summary>
|
|
public RestorePlan RestorePlan
|
|
{
|
|
get
|
|
{
|
|
return this.restorePlan;
|
|
}
|
|
internal set
|
|
{
|
|
this.restorePlan = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The database being restored
|
|
/// </summary>
|
|
public string targetDbName = string.Empty;
|
|
|
|
/// <summary>
|
|
/// The database from the backup file used to restore to by default
|
|
/// </summary>
|
|
public string DefaultSourceDbName
|
|
{
|
|
get
|
|
{
|
|
var dbNames = SourceDbNames;
|
|
string dbName = dbNames.FirstOrDefault();
|
|
return dbName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current value of source db name in the planner
|
|
/// </summary>
|
|
public string SourceDatabaseName
|
|
{
|
|
get
|
|
{
|
|
return this.RestorePlanner == null ? string.Empty : this.RestorePlanner.DatabaseName;
|
|
}
|
|
set
|
|
{
|
|
if(this.RestorePlanner != null)
|
|
{
|
|
string currentDatabaseName = this.RestorePlanner.DatabaseName;
|
|
this.RestorePlanner.DatabaseName = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether [close existing connections].
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if [close existing connections]; otherwise, <c>false</c>.
|
|
/// </value>
|
|
public bool CloseExistingConnections { get; set; }
|
|
|
|
/*
|
|
private BackupTimeLine.TimeLineDuration timeLineDuration = BackupTimeLine.TimeLineDuration.Day;
|
|
|
|
public BackupTimeLine.TimeLineDuration TimeLineDuration
|
|
{
|
|
get { return this.timeLineDuration; }
|
|
set { this.timeLineDuration = value; }
|
|
}
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Sql server credential name used to restore from Microsoft Azure url
|
|
/// </summary>
|
|
internal string CredentialName = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Azure container SAS policy
|
|
/// </summary>
|
|
internal string ContainerSharedAccessPolicy = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Returns the physical name for the target Db file.
|
|
/// It is the sourceDbName replaced with targetDbName in sourceFilename.
|
|
/// If either sourceDbName or TargetDbName is empty, the source Db filename is returned.
|
|
/// </summary>
|
|
/// <param name="sourceDbFilePhysicalLocation">source DbFile physical location</param>
|
|
/// <returns></returns>
|
|
private string GetTargetDbFilePhysicalName(string sourceDbFilePhysicalLocation)
|
|
{
|
|
string filePath = ConvertToLocalMachinePath(sourceDbFilePhysicalLocation);
|
|
string fileName = Path.GetFileName(filePath);
|
|
if (!string.IsNullOrEmpty(this.SourceDatabaseName) && !string.IsNullOrEmpty(this.targetDbName))
|
|
{
|
|
string sourceFilename = fileName;
|
|
fileName = sourceFilename.Replace(this.SourceDatabaseName, this.targetDbName);
|
|
}
|
|
return fileName;
|
|
}
|
|
|
|
public IEnumerable<BackupSetInfo> GetBackupSetInfo()
|
|
{
|
|
List<BackupSetInfo> result = new List<BackupSetInfo>();
|
|
foreach (Restore restore in RestorePlan.RestoreOperations)
|
|
{
|
|
BackupSetInfo backupSetInfo = BackupSetInfo.Create(restore, Server);
|
|
result.Add(backupSetInfo);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// List of selected backupsets
|
|
/// </summary>
|
|
public DatabaseFileInfo[] GetSelectedBakupSets()
|
|
{
|
|
List<DatabaseFileInfo> result = new List<DatabaseFileInfo>();
|
|
IEnumerable<BackupSetInfo> backupSetInfos = GetBackupSetInfo();
|
|
foreach (var backupSetInfo in backupSetInfos)
|
|
{
|
|
var item = new DatabaseFileInfo(backupSetInfo.ConvertPropertiesToArray());
|
|
Guid backupSetGuid;
|
|
if(!Guid.TryParse(item.Id, out backupSetGuid))
|
|
{
|
|
backupSetGuid = Guid.Empty;
|
|
}
|
|
item.IsSelected = this.backupSetsFilterInfo.IsBackupSetSelected(backupSetGuid);
|
|
result.Add(item);
|
|
}
|
|
return result.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the files of the database
|
|
/// </summary>
|
|
public List<DbFile> GetDbFiles()
|
|
{
|
|
Database db = null;
|
|
List<DbFile> ret = new List<DbFile>();
|
|
try
|
|
{
|
|
if (!this.RestorePlanner.ReadHeaderFromMedia)
|
|
{
|
|
db = this.Server.Databases[this.RestorePlanner.DatabaseName];
|
|
}
|
|
if (restorePlan != null && restorePlan.RestoreOperations.Count > 0)
|
|
{
|
|
if (db != null && db.Status == DatabaseStatus.Normal)
|
|
{
|
|
ret = this.Util.GetDbFiles(db);
|
|
}
|
|
else
|
|
{
|
|
ret = this.Util.GetDbFiles(restorePlan.RestoreOperations[0]);
|
|
}
|
|
}
|
|
}
|
|
catch(Exception ex )
|
|
{
|
|
Logger.Write(TraceEventType.Information, $"Failed to get restore db files. error: {ex.Message}");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public string DefaultDataFileFolder
|
|
{
|
|
get
|
|
{
|
|
return Util.GetDefaultDataFileFolder();
|
|
}
|
|
}
|
|
|
|
public string DefaultLogFileFolder
|
|
{
|
|
get
|
|
{
|
|
return Util.GetDefaultLogFileFolder();
|
|
}
|
|
}
|
|
|
|
public string DefaultBackupFolder
|
|
{
|
|
get
|
|
{
|
|
return CommonUtilities.GetDefaultBackupFolder(this.server.ConnectionContext);
|
|
}
|
|
}
|
|
|
|
internal RestorePlan CreateRestorePlan(DatabaseRestorePlanner planner, RestoreOptions restoreOptions)
|
|
{
|
|
this.CreateOrUpdateRestorePlanException = null;
|
|
RestorePlan ret = null;
|
|
|
|
try
|
|
{
|
|
ret = planner.CreateRestorePlan(restoreOptions);
|
|
if (ret == null || ret.RestoreOperations.Count == 0)
|
|
{
|
|
this.ActiveException = planner.GetBackupDeviceReadErrors();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.ActiveException = ex;
|
|
this.CreateOrUpdateRestorePlanException = this.ActiveException;
|
|
}
|
|
finally
|
|
{
|
|
}
|
|
|
|
|
|
return ret;
|
|
}
|
|
|
|
private void ResetOptions()
|
|
{
|
|
isTailLogBackupPossible = null;
|
|
isTailLogBackupWithNoRecoveryPossible = null;
|
|
bool isTailLogBackupPossibleValue = IsTailLogBackupPossible;
|
|
bool isTailLogBackupWithNoRecoveryPossibleValue = isTailLogBackupPossibleValue;
|
|
}
|
|
|
|
public void CreateNewRestorePlan()
|
|
{
|
|
this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated.
|
|
//Clear any existing exceptions as new plan is getting recreated.
|
|
this.CreateOrUpdateRestorePlanException = null;
|
|
this.DbFiles.Clear();
|
|
this.PlanUpdateRequired = false;
|
|
this.restorePlan = null;
|
|
if (String.IsNullOrEmpty(this.RestorePlanner.DatabaseName))
|
|
{
|
|
this.RestorePlan = new RestorePlan(this.Server);
|
|
this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName);
|
|
}
|
|
else
|
|
{
|
|
this.RestorePlan = this.CreateRestorePlan(this.RestorePlanner, this.RestoreOptions);
|
|
this.Util.AddCredentialNameForUrlBackupSet(this.restorePlan, this.CredentialName);
|
|
|
|
if (this.ActiveException == null)
|
|
{
|
|
this.dbFiles = this.GetDbFiles();
|
|
|
|
UpdateDBFilesPhysicalRelocate();
|
|
|
|
|
|
}
|
|
}
|
|
if (this.restorePlan == null)
|
|
{
|
|
this.RestorePlan = new RestorePlan(this.Server);
|
|
this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName);
|
|
}
|
|
else
|
|
{
|
|
ResetOptions();
|
|
}
|
|
}
|
|
|
|
public bool ShouldCreateNewPlan()
|
|
{
|
|
return RestorePlan == null ||
|
|
string.Compare(RestorePlanner.DatabaseName, this.RestoreParams.GetOptionValue<string>(RestoreOptionsHelper.SourceDatabaseName), StringComparison.InvariantCultureIgnoreCase) != 0 ||
|
|
RestorePlanner.ReadHeaderFromMedia != this.RestoreParams.ReadHeaderFromMedia ||
|
|
this.RelocateAllFiles != this.RestoreParams.GetOptionValue<bool>(RestoreOptionsHelper.RelocateDbFiles) ||
|
|
string.Compare(TargetDatabaseName, this.RestoreParams.GetOptionValue<string>(RestoreOptionsHelper.TargetDatabaseName), StringComparison.InvariantCultureIgnoreCase) != 0 ||
|
|
string.Compare(this.backupMediaList, RestoreParams.BackupFilePaths, StringComparison.InvariantCultureIgnoreCase) != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates restore plan
|
|
/// </summary>
|
|
public void UpdateRestorePlan()
|
|
{
|
|
RestoreOptionsHelper.UpdateOptionsInPlan(this);
|
|
if (RelocateAllFiles)
|
|
{
|
|
UpdateDbFiles();
|
|
}
|
|
this.SetRestorePlanProperties(this.restorePlan);
|
|
|
|
UpdateSelectedBackupSets();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine if restore plan of selected database does have Url
|
|
/// </summary>
|
|
private bool IfRestorePlanHasUrl()
|
|
{
|
|
return (restorePlan.RestoreOperations.Any(
|
|
res => res.BackupSet.BackupMediaSet.BackupMediaList.Any(t => t.MediaType == DeviceType.Url)));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sets restore plan properties
|
|
/// </summary>
|
|
private void SetRestorePlanProperties(RestorePlan rp)
|
|
{
|
|
if (rp == null || rp.RestoreOperations.Count < 1)
|
|
{
|
|
return;
|
|
}
|
|
rp.SetRestoreOptions(this.RestoreOptions);
|
|
rp.CloseExistingConnections = this.CloseExistingConnections;
|
|
if (this.TargetDatabaseName != null && !this.TargetDatabaseName.Equals(string.Empty))
|
|
{
|
|
rp.DatabaseName = TargetDatabaseName;
|
|
}
|
|
rp.RestoreOperations[0].RelocateFiles.Clear();
|
|
foreach (DbFile dbFile in this.DbFiles)
|
|
{
|
|
// For XStore path, we don't want to try the getFullPath.
|
|
string newPhysicalPath;
|
|
Uri pathUri;
|
|
bool uriCreated = Uri.TryCreate(dbFile.PhysicalNameRelocate, UriKind.Absolute, out pathUri);
|
|
if (uriCreated && pathUri.Scheme == "https")
|
|
{
|
|
newPhysicalPath = dbFile.PhysicalNameRelocate;
|
|
}
|
|
else
|
|
{
|
|
newPhysicalPath = Path.GetFullPath(dbFile.PhysicalNameRelocate);
|
|
}
|
|
if (!dbFile.PhysicalName.Equals(newPhysicalPath))
|
|
{
|
|
RelocateFile relocFile = new RelocateFile(dbFile.LogicalName, dbFile.PhysicalNameRelocate);
|
|
rp.RestoreOperations[0].RelocateFiles.Add(relocFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bool indicating whether a tail log backup will be taken
|
|
/// </summary>
|
|
public bool BackupTailLog
|
|
{
|
|
get
|
|
{
|
|
return this.RestorePlanner.BackupTailLog;
|
|
}
|
|
set
|
|
{
|
|
if (this.RestorePlanner.BackupTailLog != value)
|
|
{
|
|
this.RestorePlanner.BackupTailLog = value;
|
|
this.PlanUpdateRequired = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// bool indicating whether the database will be left in restoring state
|
|
/// </summary>
|
|
public bool TailLogWithNoRecovery
|
|
{
|
|
get
|
|
{
|
|
return this.RestorePlanner.TailLogWithNoRecovery;
|
|
}
|
|
set
|
|
{
|
|
if (this.RestorePlanner.TailLogWithNoRecovery != value)
|
|
{
|
|
this.RestorePlanner.TailLogWithNoRecovery = value;
|
|
this.PlanUpdateRequired = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public DateTime? CurrentRestorePointInTime
|
|
{
|
|
get
|
|
{
|
|
if (!IsAnyFullBackupSetSelected())
|
|
{
|
|
return null;
|
|
}
|
|
for (int i = this.RestorePlan.RestoreOperations.Count - 1; i >= 0; i--)
|
|
{
|
|
BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
|
|
if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet))
|
|
{
|
|
if (backupSet == null
|
|
|| (backupSet.BackupSetType == BackupSetType.Log &&
|
|
this.RestorePlan.RestoreOperations[i].ToPointInTime != null))
|
|
{
|
|
return this.RestorePlanner.RestoreToPointInTime;
|
|
}
|
|
return backupSet.BackupStartDate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the restore plan error message
|
|
/// </summary>
|
|
public override string ErrorMessage
|
|
{
|
|
get
|
|
{
|
|
if (ActiveException != null)
|
|
{
|
|
return ActiveException.Message;
|
|
}
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
|
|
private bool IsAnyFullBackupSetSelected()
|
|
{
|
|
bool isSelected = false;
|
|
|
|
if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected)
|
|
{
|
|
var fullBackupSet = this.RestorePlan.RestoreOperations.FirstOrDefault(x => x.BackupSet.BackupSetType == BackupSetType.Database);
|
|
isSelected = fullBackupSet != null && this.backupSetsFilterInfo.IsBackupSetSelected(fullBackupSet.BackupSet.BackupSetGuid);
|
|
}
|
|
|
|
return isSelected;
|
|
}
|
|
|
|
private int GetLastSelectedBackupSetIndex()
|
|
{
|
|
if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected)
|
|
{
|
|
for (int i = this.RestorePlan.RestoreOperations.Count -1; i >= 0; i--)
|
|
{
|
|
BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
|
|
if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private int GetFirstSelectedBackupSetIndex()
|
|
{
|
|
if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected)
|
|
{
|
|
for (int i = 0; i < this.RestorePlan.RestoreOperations.Count - 1; i++)
|
|
{
|
|
BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
|
|
if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
public void UpdateSelectedBackupSets()
|
|
{
|
|
this.backupSetsFilterInfo.Clear();
|
|
var selectedBackupSetsFromClient = this.RestoreParams.SelectedBackupSets;
|
|
|
|
if (this.RestorePlan != null && this.RestorePlan.RestoreOperations != null)
|
|
{
|
|
for (int index = 0; index < this.RestorePlan.RestoreOperations.Count; index++)
|
|
{
|
|
BackupSet backupSet = this.RestorePlan.RestoreOperations[index].BackupSet;
|
|
if (backupSet != null)
|
|
{
|
|
// If the collection client sent is null, select everything; otherwise select the items that are selected in client
|
|
bool backupSetSelected = selectedBackupSetsFromClient == null || selectedBackupSetsFromClient.Any(x => BackUpSetGuidEqualsId(backupSet, x));
|
|
|
|
|
|
if (backupSetSelected)
|
|
{
|
|
AddBackupSetsToSelected(index, index);
|
|
|
|
//the last index - this will include tail-Log restore operation if present
|
|
//If the last item is selected, select all the items before that because the last item
|
|
//is tail-log
|
|
if (index == this.RestorePlan.RestoreOperations.Count - 1)
|
|
{
|
|
AddBackupSetsToSelected(0, index);
|
|
}
|
|
|
|
//If the second item is selected and it's a diff backup, the fist item (full backup) has to be selected
|
|
if (index == 1 && backupSet.BackupSetType == BackupSetType.Differential)
|
|
{
|
|
AddBackupSetsToSelected(0, 0);
|
|
}
|
|
|
|
//If the selected item is a log backup, select all the items before that
|
|
if (backupSet.BackupSetType == BackupSetType.Log)
|
|
{
|
|
AddBackupSetsToSelected(0, index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the first item is not selected which is always the full backup, other backupsets cannot be selected
|
|
if (index == 0)
|
|
{
|
|
selectedBackupSetsFromClient = new string[] { };
|
|
}
|
|
|
|
//The a log is not selected, the logs after that cannot be selected
|
|
if (backupSet.BackupSetType == BackupSetType.Log)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//If the second item is not selected and it's a diff backup
|
|
if (index == 1 && backupSet.BackupSetType == BackupSetType.Differential)
|
|
{
|
|
if (this.Server.Version.Major < 9 ||
|
|
(this.RestorePlan.RestoreOperations.Count >= 3 &&
|
|
BackupSet.IsBackupSetsInSequence(this.RestorePlan.RestoreOperations[0].BackupSet, this.RestorePlan.RestoreOperations[2].BackupSet)))
|
|
{
|
|
// only the item at index 1 won't be selected
|
|
}
|
|
else
|
|
{
|
|
// nothing after index 1 should be selected
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool BackUpSetGuidEqualsId(BackupSet backupSet, string id)
|
|
{
|
|
return backupSet != null && string.Compare(backupSet.BackupSetGuid.ToString(), id, StringComparison.OrdinalIgnoreCase) == 0;
|
|
}
|
|
|
|
private void AddBackupSetsToSelected(int from, int to)
|
|
{
|
|
if (this.RestorePlan != null && this.RestorePlan.RestoreOperations != null
|
|
&& from < this.RestorePlan.RestoreOperations.Count && to < this.RestorePlan.RestoreOperations.Count)
|
|
{
|
|
for (int i = from; i <= to; i++)
|
|
{
|
|
BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet;
|
|
this.backupSetsFilterInfo.Add(backupSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies the backup files location.
|
|
/// </summary>
|
|
internal void CheckBackupFilesLocation()
|
|
{
|
|
if (this.RestorePlan != null)
|
|
{
|
|
foreach (Restore restore in this.RestorePlan.RestoreOperations)
|
|
{
|
|
if (restore.BackupSet != null)
|
|
{
|
|
restore.BackupSet.CheckBackupFilesExistence();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal bool DbFilesLocationAreValid()
|
|
{
|
|
foreach (DbFile dbFile in this.DbFiles)
|
|
{
|
|
string newPhysicalPath = Path.GetFullPath(dbFile.PhysicalNameRelocate);
|
|
if (string.Compare(dbFile.PhysicalName, dbFile.PhysicalNameRelocate, true) != 0)
|
|
{
|
|
bool isValidFolder = false;
|
|
bool isValidPath = Util.IsDestinationPathValid(Path.GetDirectoryName(newPhysicalPath), ref isValidFolder);
|
|
if (!(isValidFolder && isValidPath))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if can close eixisting connections for give database
|
|
/// </summary>
|
|
public bool CanDropExistingConnections
|
|
{
|
|
get
|
|
{
|
|
if (RestorePlan != null && RestorePlanner != null)
|
|
{
|
|
return RestorePlan.CanDropExistingConnections(RestorePlanner.DatabaseName);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels the restore operations
|
|
/// </summary>
|
|
public override void Cancel()
|
|
{
|
|
foreach (Restore restore in RestorePlan.RestoreOperations)
|
|
{
|
|
restore.Abort();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a restore data object that includes the plan to do the restore operation
|
|
/// </summary>
|
|
/// <param name="requestParam"></param>
|
|
/// <returns></returns>
|
|
internal void UpdateRestoreTaskObject()
|
|
{
|
|
bool shouldCreateNewPlan = ShouldCreateNewPlan();
|
|
|
|
if (!string.IsNullOrEmpty(RestoreParams.BackupFilePaths) && RestoreParams.ReadHeaderFromMedia)
|
|
{
|
|
AddDevices(RestoreParams.BackupFilePaths, (DeviceType)RestoreParams.DeviceType);
|
|
}
|
|
else
|
|
{
|
|
RestorePlanner.BackupMediaList.Clear();
|
|
}
|
|
RestorePlanner.ReadHeaderFromMedia = RestoreParams.ReadHeaderFromMedia;
|
|
|
|
RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.SourceDatabaseName, this);
|
|
RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.TargetDatabaseName, this);
|
|
|
|
if (shouldCreateNewPlan)
|
|
{
|
|
CreateNewRestorePlan();
|
|
}
|
|
|
|
UpdateRestorePlan();
|
|
|
|
}
|
|
}
|
|
}
|