/// <summary>
        ///
        /// </summary>
        /// <param name="backupItem"></param>
        /// <param name="restore"></param>
        /// <remarks>
        ///     <paramref name="restore"/> must have backup device already added
        /// </remarks>
        private void AddMoveFileInstructionsIfNeeded(BackupItem backupItem, Restore restore)
        {
            Check.DoRequireArgumentNotNull(restore, "restore");
            Check.DoCheckArgument(restore.Devices.Count > 0, "Restore instance must have backup device already added");

            if (backupItem.BackupType == BackupType.Full)
            {
                var databaseFilesInBackup = ReadDatabaseFileList(restore);
                foreach (var dbFile in databaseFilesInBackup)
                {
                    var targetDir        = dbFile.IsLog ? DefaultLogPath : DefaultDataPath;
                    var physicalFilePath = FindExistingDatabaseFile(dbFile.LogicalName, dbFile.IsLog);

                    if (string.IsNullOrEmpty(physicalFilePath))
                    {
                        // no such file in the existing database; maybe it was dropped
                        physicalFilePath = Path.Combine(targetDir, Path.GetFileName(dbFile.PhysicalName));
                        physicalFilePath = PickNewFileName(physicalFilePath);
                    }

                    Log.InfoFormat("Moving {0} to {1}", dbFile.LogicalName, physicalFilePath);

                    restore.RelocateFiles.Add(new RelocateFile(dbFile.LogicalName, physicalFilePath));
                }
            }
        }
示例#2
0
        /// <summary>
        ///     Constructor.
        /// </summary>
        /// <param name="backupFilesystemNameParser">
        ///     Parser to delegate <see cref="IBackupFilesystemNameParser"/>'s method calls to.
        /// </param>
        /// <param name="endOfPeriodCalculator">
        ///     Optional; if null, <see cref="GetPeriodEnd"/> will always return null
        /// </param>
        public TimePeriodFromFilesystemNameExtractor(IBackupFilesystemNameParser backupFilesystemNameParser, Func <DateTime, DateTime> endOfPeriodCalculator)
        {
            Check.DoRequireArgumentNotNull(backupFilesystemNameParser, "BackupFilesystemNameParser");

            BackupFilesystemNameParser = backupFilesystemNameParser;
            EndOfPeriodCalculator      = endOfPeriodCalculator;
        }
示例#3
0
        /// <param name="rootPath">
        ///     Root path under which per-database backup folders are found; the folder names must match database names exactly.
        /// </param>
        /// <param name="targetTime">
        ///     Optional, point in time to restore to; latest if null
        /// </param>
        /// <param name="worker">
        ///     Optional, to report progress to and implement cancellation from.
        /// </param>
        /// <param name="targetServer">
        ///     Self-explanatory
        /// </param>
        /// <param name="namingConvention">
        ///     Defines historical backup directory structure.
        /// </param>
        public MultiRestorer(string rootPath, DateTime?targetTime, BackgroundWorker worker, string targetServer, IBackupDirectoryNamingConvention namingConvention)
        {
            Check.DoRequireArgumentNotNull(rootPath, "rootPath");
            Check.DoRequireArgumentNotNull(targetServer, "targetServer");
            Check.DoRequireArgumentNotNull(namingConvention, "namingConvention");

            NamingConvention = namingConvention;
            RootPath         = rootPath;
            TargetTime       = targetTime;
            _worker          = worker;

            var bld = new SqlConnectionStringBuilder()
            {
                IntegratedSecurity = true,
                DataSource         = targetServer,
                InitialCatalog     = "master",
                ConnectTimeout     = 2,          // seconds;
            };

            // keep same connection to be able to use single user mode for databases when restoring
            var conn = new ServerConnection(new SqlConnection(bld.ToString()));

            Server = new Server(conn);
            Server.ConnectionContext.StatementTimeout = 0;

            // read actual names
            var existingLogins = string.Join(", ", Server.Logins.Cast <Login>().Select(x => x.Name));

            Log.InfoFormat("Existing logins: {0}", existingLogins);
        }
示例#4
0
        /// <returns>
        ///     null if file contains no valid backups
        /// </returns>
        private BackupFileHeader ParseBackupHeader(FileInfo fileInfo)
        {
            Check.DoRequireArgumentNotNull(fileInfo, "fileInfo");

            BackupFileHeader result = null;

            var allBackupItems = SqlServerProxy.GetBackupItems(fileInfo);
            var validItems     = GetValidBackups(allBackupItems);
            var wrongItems     = GetInvalidBackups(allBackupItems);

            if (wrongItems.Count > 0)
            {
                Log.WarnFormat("File {0} contains {1} backups from database other than {2}; ignoring them", fileInfo.FullName, wrongItems.Count, DatabaseName);
            }

            if (validItems.Count == 0)
            {
                Log.WarnFormat("File {0} contains no backups for database {1}; skipping it.", fileInfo.FullName, DatabaseName);
            }
            else
            {
                result = new BackupFileHeader(fileInfo, validItems);
            }

            return(result);
        }
示例#5
0
        public BackupFileFolderInfo GetContainingFolder(BackupFileInfo backupFile)
        {
            Check.DoRequireArgumentNotNull(backupFile, "backupFile");

            return(_backupFileFolders
                   .Where(f => f.DirectoryInfo.Name.Equals(backupFile.FileInfo.Directory.Name, StringComparison.InvariantCultureIgnoreCase))
                   .SingleOrDefault());
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Object"/> class.
        /// </summary>
        public DatabaseRestorer(IBackupFileCatalog backupCatalog, string databaseName, MultiRestorer multiRestorer)
        {
            Check.DoRequireArgumentNotNull(backupCatalog, "backupCatalog");
            _backupCatalog = backupCatalog;
            _databaseName  = databaseName;
            _multiRestorer = multiRestorer;

            _preExistingDatabase = Database != null;
        }
示例#7
0
        public RestorePlanner(IBackupFileCatalog backupFileCatalog, ISqlServerProxy sqlServerProxy, string databaseName)
        {
            Check.DoRequireArgumentNotNull(backupFileCatalog, "backupFileCatalog");
            Check.DoRequireArgumentNotNull(sqlServerProxy, "sqlServerProxy");
            Check.DoRequireArgumentNotNull(databaseName, "databaseName");

            BackupFileCatalog = backupFileCatalog;
            SqlServerProxy    = sqlServerProxy;
            DatabaseName      = databaseName;
        }
示例#8
0
        /// <summary>
        ///     Factory method creating catalog for calendar month folders and default 'month-day-day' file naming convention.
        /// </summary>
        public static BackupFileCatalog CreateDefaultMonthDayDayCatalog(string databaseBackupFolder)
        {
            Check.DoRequireArgumentNotNull(databaseBackupFolder, "databaseBackupFolder");

            var namingConvention = BackupDirectoryNamingConvention.CreateDefaultMonthDayDayConvention();

            var result = new BackupFileCatalog(namingConvention, databaseBackupFolder);

            return(result);
        }
示例#9
0
        public BackupFileHeader(FileInfo fileInfo, List <BackupItem> validItems)
        {
            Check.DoRequireArgumentNotNull(fileInfo, "fileInfo");
            Check.DoRequireArgumentNotNull(validItems, "validItems");
            Check.DoCheckArgument(validItems.Count > 0, "No valid backup items");

            FileInfo        = fileInfo;
            FirstBackupTime = validItems.First().BackupStartTime;
            LastBackupTime  = validItems[validItems.Count - 1].BackupStartTime;
            ValidItems      = new List <BackupItem>(validItems);
        }
示例#10
0
        /// <summary>
        ///     Factory method creating catalog for calendar month folders and given file naming convention.
        /// </summary>
        /// <param name="fileNamingConvention">
        ///     File naming convention to use.
        /// </param>
        public static BackupFileCatalog CreateMonthlyCatalog(string databaseBackupFolder, IBackupFileNamingConvention fileNamingConvention)
        {
            Check.DoRequireArgumentNotNull(databaseBackupFolder, "databaseBackupFolder");
            Check.DoRequireArgumentNotNull(fileNamingConvention, "fileNamingConvention");


            var namingConvention = BackupDirectoryNamingConvention.CreateMonthlyConvention(fileNamingConvention);

            var result = new BackupFileCatalog(namingConvention, databaseBackupFolder);

            return(result);
        }
示例#11
0
        public BackupFileCatalog(
            IBackupDirectoryNamingConvention namingConvention
            , string databaseBackupFolder)
        {
            Check.DoRequireArgumentNotNull(namingConvention, "namingConvention");
            Check.DoRequireArgumentNotNull(databaseBackupFolder, "databaseBackupFolder");

            NamingConvention        = namingConvention;
            DatabaseBackupDirectory = new DirectoryInfo(databaseBackupFolder);
            Check.DoCheckArgument(DatabaseBackupDirectory.Exists, () => string.Format("Backup folder {0} does not exist", databaseBackupFolder));

            _backupFileFolders = GetBackupFolders();
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="timestampExtractorExpression">
        ///     Must capture timestamp in the first capture of the first group.
        /// </param>
        /// <param name="timestampFormat">
        /// </param>
        /// <param name="databaseNameExtractorExpression">
        ///     Optional
        /// </param>
        public BackupFilesystemObjectNameParser(string timestampExtractorExpression, string timestampFormat, string databaseNameExtractorExpression)
        {
            Check.DoRequireArgumentNotNull(timestampExtractorExpression, "timestampExtractorExpression");
            Check.DoRequireArgumentNotNull(timestampFormat, "timestampFormat");

            _timestampExtractor = new Regex(timestampExtractorExpression, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
            _timestampFormat    = timestampFormat;

            if (!string.IsNullOrEmpty(databaseNameExtractorExpression))
            {
                _databaseNameExtractor = new Regex(databaseNameExtractorExpression, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
            }
        }
        /// <summary>
        ///     Factory method creating convention for calendar month folders and given file naming convention.
        /// </summary>
        /// <param name="fileNamingConvention">
        ///     File naming convention to use.
        /// </param>
        /// <returns>
        /// </returns>
        public static BackupDirectoryNamingConvention CreateMonthlyConvention(IBackupFileNamingConvention fileNamingConvention)
        {
            Check.DoRequireArgumentNotNull(fileNamingConvention, "fileNamingConvention");

            var monthDirectoryNameParser = new BackupFilesystemObjectNameParser(
                "(.+)", RestoreBackupLib.BackupFileNamingConvention.DefaultMonthTimestampFormat, null);

            var monthPeriodExtractor = TimePeriodFromFilesystemNameExtractor.CreateForCalendarMonths(monthDirectoryNameParser);

            var result = new BackupDirectoryNamingConvention(monthPeriodExtractor, fileNamingConvention);

            return(result);
        }
示例#14
0
        public BackupFileFolderInfo(IBackupFileNamingConvention fileNamingConvention, DirectoryInfo directoryInfo, DateTime startTime, DateTime?endTime)
        {
            Check.DoRequireArgumentNotNull(fileNamingConvention, "fileNamingConvention");
            Check.DoRequireArgumentNotNull(directoryInfo, "directoryInfo");
            Check.DoCheckArgument(endTime > startTime, "Time period");

            DirectoryInfo = directoryInfo;
            StartTime     = startTime;

            PeriodEndDeclaredExplicitly = endTime.HasValue;
            EndTime = endTime.HasValue ? endTime.Value : directoryInfo.LastWriteTime;

            BackupFileNamingConvention = fileNamingConvention;
        }
        private string Extract(string input, Regex regex)
        {
            Check.DoRequireArgumentNotNull(input, "input");
            Check.DoRequireArgumentNotNull(regex, "regex");

            var match = regex.Match(input);

            if (match.Success)
            {
                // first occurrence such as when capturing subexpression repeats: @"\b(\w+\s*)+\.";
                return(match.Groups[1].Captures[0].Value);
            }

            return(null);
        }
示例#16
0
        /// <summary>
        ///     Constructor of immutable instance.
        /// </summary>
        /// <param name="backupType">
        ///     Type of backups contained in this file.
        /// </param>
        /// <param name="startTime">
        ///     Explicitly defined start of the period in which backups were written into the file. If null, the start time will be taken from creation time.
        /// </param>
        /// <param name="endTime">
        ///     Explicitly defined end of the period in which backups were written into the file.
        /// </param>
        /// <param name="fileInfo">
        /// </param>
        /// <remarks>
        ///     Note that <see cref="StartTime"/> will always have a value while <see cref="EndTime"/> may be null. This is because start time is more important and
        ///     must be figured out in any case while EndTime is not essential and only beneficial for optimization.
        ///     If simultaneous log and database backups are allowed, without end time we only need to start from first log prior to last database backup.
        ///     However, if there is no simultaneous backup, things are much easier and we can work without end time.
        ///     What happens if diff backup is marked with the hour when it is made and single daily log backup is marked with start of day timestamp?
        ///     Then we need end time or have to start from first log backup file prior to the database backup.
        /// </remarks>
        public BackupFileInfo(SupportedBackupType backupType, DateTime?startTime, DateTime?endTime, FileInfo fileInfo)
        {
            Check.DoRequireArgumentNotNull(fileInfo, "fileInfo");
            Check.DoCheckArgument(backupType != SupportedBackupType.None, "Invalid file type");

            BackupType = backupType;

            FileInfo = fileInfo;

            PeriodStartDeclaredExplicitly = startTime.HasValue;
            StartTime = startTime ?? fileInfo.CreationTime;

            PeriodEndDeclaredExplicitly = endTime.HasValue;
            EndTime = endTime ?? fileInfo.LastWriteTime;
        }
示例#17
0
        public List <BackupItem> GetBackupItems(FileInfo file)
        {
            Check.DoRequireArgumentNotNull(file, "file");

            var restore = new Restore();

            restore.Devices.AddDevice(file.FullName, DeviceType.File);
            var headerTable = restore.ReadBackupHeader(Server);
            var result      = new List <BackupItem>(headerTable.Rows.Count);

            foreach (DataRow row in headerTable.Rows)
            {
                result.Add(GetBackupItem(row, file));
            }

            return(result);
        }
示例#18
0
        private List <BackupFileInfo> GetBackupFiles(List <FileInfo> files)
        {
            Check.DoRequireArgumentNotNull(files, "files");

            var result = new List <BackupFileInfo>(files.Count);

            var filteredFiles = files.Where(f => BackupFileNamingConvention.GetBackupType(f.Name) != SupportedBackupType.None);

            foreach (var info in filteredFiles)
            {
                var backupFileInfo = BackupFileNamingConvention.GetBackupFileInfo(info);

                result.Add(backupFileInfo);
            }

            result = result.OrderBy(f => f.StartTime).ToList();

            InferEndTimeWhereNotDefinedExplicitly(result);

            return(result);
        }
示例#19
0
        /// <summary>
        ///     Verified sequence of backup items to restore to achieve configured outcome.
        ///     Never returns null.
        /// </summary>
        /// <param name="lastDatabaseBackupItem"></param>
        /// <returns></returns>
        private List <BackupItem> GetLogBackupItems(BackupItem lastDatabaseBackupItem, DateTime?targetTime)
        {
            Check.DoRequireArgumentNotNull(lastDatabaseBackupItem, "lastDatabaseBackupItem");

            var result = new List <BackupItem>(1000);

            var logFiles = BackupFileCatalog.GetLogBackupsSequence(lastDatabaseBackupItem.BackupStartTime);

            // need only first log backup after target time; flag indicates that file is already processed
            var targetReached = false;
            var fileIterator  = logFiles.GetEnumerator();

            var lastBackupItem = lastDatabaseBackupItem;

            while (!targetReached && fileIterator.MoveNext())
            {
                var header = ParseBackupHeader(fileIterator.Current.FileInfo);

                if (header != null)
                {
                    var itemIterator = header.ValidItems.GetEnumerator();

                    while (!targetReached && itemIterator.MoveNext())
                    {
                        var item = itemIterator.Current;

                        Check.DoAssertLambda(CheckSequence(lastBackupItem, item, false), () =>
                                             $"Item #{item.Position} in file {item.FileInfo.Name} cannot be applied after item #{lastBackupItem.Position} in" +
                                             $" file {lastBackupItem.FileInfo.Name}");
                        lastBackupItem = item;

                        result.Add(item);

                        targetReached = (targetTime.HasValue && item.BackupStartTime > targetTime.Value);
                    }
                }
            }

            return(result);
        }
示例#20
0
        /// <summary>
        ///     Add new backup type mapping.
        /// </summary>
        /// <param name="nameSuffix">
        ///     Suffix of backup files, including extension, case insensitive
        /// </param>
        /// <param name="backupType">
        ///     Backup type
        /// </param>
        /// <param name="timePeriodExtractor">
        ///     Object implementing extraction of relevant information from file name, such as time period.
        /// </param>
        public void AddType(string nameSuffix, SupportedBackupType backupType, ITimePeriodFromFilesystemNameExtractor timePeriodExtractor)
        {
            Check.DoRequireArgumentNotNull(nameSuffix, "nameSuffix");
            Check.DoRequireArgumentNotNull(timePeriodExtractor, "timePeriodExtractor");
            Check.DoCheckArgument(null == GetTypeInfo(backupType), string.Format("Parser for {0} already mapped", backupType));
            Check.DoCheckArgument(backupType != SupportedBackupType.None, "Invalid backup type (None)");

            var intersectedType = _backupTypes.Values.Where(
                i => i.FileNameSuffix.EndsWith(nameSuffix, StringComparison.InvariantCultureIgnoreCase) ||
                nameSuffix.EndsWith(i.FileNameSuffix, StringComparison.InvariantCultureIgnoreCase))
                                  .FirstOrDefault();

            Check.DoCheckArgument(
                intersectedType == null
                , () => string.Format("Suffix {0} for {1} conflicts with suffix {2} for type {3}"
                                      , nameSuffix, backupType, intersectedType.FileNameSuffix, intersectedType.BackupType));

            var newTypeInfo = new BackupTypeInfo()
            {
                FileNameSuffix = nameSuffix, BackupType = backupType, TimePeriodExtractor = timePeriodExtractor
            };

            _backupTypes.Add(backupType, newTypeInfo);
        }
        private void Restore(BackupItem item)
        {
            Check.DoRequireArgumentNotNull(item, "item");

            Log.InfoFormat("{0}: restoring {1} from {2}", DatabaseName, item, item.FileInfo.FullName);

            var actionType = item.BackupType == BackupType.Log ? RestoreActionType.Log : RestoreActionType.Database;

            var restore = CreateRestoreInstance();

            restore.Action = actionType;
            restore.Devices.AddDevice(item.FileInfo.FullName, DeviceType.File);
            restore.FileNumber = item.Position;

            if (TargetTime.HasValue && item.BackupType == BackupType.Log)
            {
                // note that another format, yyyy-MM-ddTHH:mm:ss.fff failed with error saying "already restored past the point"
                restore.ToPointInTime = TargetTime.Value.ToString("MMM dd, yyyy hh:mm:ss tt");
                Log.InfoFormat("Setting Point In Time {0} on {1}", restore.ToPointInTime, item);
            }

            AddMoveFileInstructionsIfNeeded(item, restore);

            restore.PercentCompleteNotification = 10;
            restore.PercentComplete            += RestoreOnPercentComplete;

            try
            {
                Server.ConnectionContext.ExecuteNonQuery("use master;");
                restore.SqlRestore(Server);
            }
            finally
            {
                restore.PercentComplete -= RestoreOnPercentComplete;
            }
        }
示例#22
0
        public SqlServer2014Proxy(Server server)
        {
            Check.DoRequireArgumentNotNull(server, "server");

            Server = server;
        }
示例#23
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="fileName"></param>
 /// <returns>
 ///     null if not mapped
 /// </returns>
 private BackupTypeInfo GetTypeInfo(string fileName)
 {
     Check.DoRequireArgumentNotNull(fileName, "fileSystemName");
     return(_backupTypes.Values.Where(t => fileName.EndsWith(t.FileNameSuffix, StringComparison.InvariantCultureIgnoreCase))
            .FirstOrDefault());
 }