/** * Create a backup of the specified package. A backup is only created if the specified * package meets specific criteria, as outlined in the comments for ShouldBackupPackage(). * * @param InPackage Package which should be backed up * * @see FAutoPackageBackup::ShouldBackupPackage() * * @return true if the package was successfully backed up; false if it was not */ bool FAutoPackageBackup::BackupPackage( const UPackage& InPackage ) { bool bPackageBackedUp = false; FString OriginalFileName; // Check if the package is valid for being backed up if ( ShouldBackupPackage( InPackage, OriginalFileName ) ) { GWarn->StatusUpdate( -1, -1, NSLOCTEXT("UnrealEd", "PackageBackup_Warning", "Backing up asset...") ); // Construct the backup file name by appending a timestamp in between the base file name and extension FString DestinationFileName = GetBackupDirectory() / FPaths::GetBaseFilename(OriginalFileName); DestinationFileName += TEXT("_"); DestinationFileName += FDateTime::Now().ToString(TEXT("%Y-%m-%d-%H-%M-%S")); DestinationFileName += FPaths::GetExtension( OriginalFileName, true ); // Copy the file to the backup file name IFileManager::Get().Copy( *DestinationFileName, *OriginalFileName ); bPackageBackedUp = true; } return bPackageBackedUp; }
/** \fn DBUtil::DoBackup(QString&) * \brief Creates a backup of the database. * * This fallback function is used only if the database backup script cannot * be found. */ bool DBUtil::DoBackup(QString &filename) { DatabaseParams dbParams = gCoreContext->GetDatabaseParams(); QString dbSchemaVer = gCoreContext->GetSetting("DBSchemaVer"); QString backupDirectory = GetBackupDirectory(); QString command; QString compressCommand(""); QString extension = ".sql"; if (QFile::exists("/bin/gzip")) compressCommand = "/bin/gzip"; else if (QFile::exists("/usr/bin/gzip")) compressCommand = "/usr/bin/gzip"; else LOG(VB_GENERAL, LOG_CRIT, "Neither /bin/gzip nor /usr/bin/gzip exist. " "The database backup will be uncompressed."); QString backupFilename = CreateBackupFilename( dbParams.dbName + "-" + dbSchemaVer, extension); QString backupPathname = backupDirectory + "/" + backupFilename; QString privateinfo = QString( "[client]\npassword=%1\n[mysqldump]\npassword=%2\n") .arg(dbParams.dbPassword).arg(dbParams.dbPassword); QString tempExtraConfFile = QString::null; if (!CreateTemporaryDBConf(privateinfo, tempExtraConfFile)) return false; QString portArg = ""; if (dbParams.dbPort > 0) portArg = QString(" --port='%1'").arg(dbParams.dbPort); command = QString("mysqldump --defaults-extra-file='%1' --host='%2'%3" " --user='******' --add-drop-table --add-locks" " --allow-keywords --complete-insert" " --extended-insert --lock-tables --no-create-db --quick" " '%5' > '%6' 2>/dev/null") .arg(tempExtraConfFile).arg(dbParams.dbHostName) .arg(portArg).arg(dbParams.dbUserName) .arg(dbParams.dbName).arg(backupPathname); LOG(VB_FILE, LOG_INFO, QString("Backing up database with command: '%1'") .arg(command)); LOG(VB_GENERAL, LOG_CRIT, QString("Backing up database to file: '%1'") .arg(backupPathname)); uint status = myth_system(command, kMSDontBlockInputDevs|kMSAnonLog); QByteArray tmpfile = tempExtraConfFile.toLocal8Bit(); unlink(tmpfile.constData()); if (status != GENERIC_EXIT_OK) { LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error backing up database: '%1' (%2)") .arg(command).arg(status)); filename = "__FAILED__"; return false; } if (compressCommand != "") { LOG(VB_GENERAL, LOG_CRIT, "Compressing database backup file."); compressCommand += " " + backupPathname; status = myth_system(compressCommand, kMSDontBlockInputDevs); if (status != GENERIC_EXIT_OK) { LOG(VB_GENERAL, LOG_CRIT, "Compression failed, backup file will remain uncompressed."); } else { backupPathname += ".gz"; LOG(VB_GENERAL, LOG_CRIT, QString("Database Backup filename: '%1'") .arg(backupPathname)); } } LOG(VB_GENERAL, LOG_CRIT, "Database Backup complete."); filename = backupPathname; return true; }
/** \fn DBUtil::DoBackup(const QString&, QString&) * \brief Creates a backup of the database by executing the backupScript. * * This function executes the specified backup script to create a database * backup. This is the preferred approach for creating the backup. */ bool DBUtil::DoBackup(const QString &backupScript, QString &filename, bool disableRotation) { DatabaseParams dbParams = gCoreContext->GetDatabaseParams(); QString dbSchemaVer = gCoreContext->GetSetting("DBSchemaVer"); QString backupDirectory = GetBackupDirectory(); QString backupFilename = CreateBackupFilename(dbParams.dbName + "-" + dbSchemaVer, ".sql"); QString scriptArgs = gCoreContext->GetSetting("BackupDBScriptArgs"); QString rotate = ""; if (disableRotation) { if (!(scriptArgs.contains("rotate", Qt::CaseInsensitive))) rotate = "rotate=-1"; } QString privateinfo = QString("DBHostName=%1\nDBPort=%2\n" "DBUserName=%3\nDBPassword=%4\n" "DBName=%5\nDBSchemaVer=%6\n" "DBBackupDirectory=%7\nDBBackupFilename=%8\n%9\n") .arg(dbParams.dbHostName).arg(dbParams.dbPort) .arg(dbParams.dbUserName).arg(dbParams.dbPassword) .arg(dbParams.dbName).arg(dbSchemaVer) .arg(backupDirectory).arg(backupFilename).arg(rotate); QString tempDatabaseConfFile = QString::null; bool hastemp = CreateTemporaryDBConf(privateinfo, tempDatabaseConfFile); if (!hastemp) LOG(VB_GENERAL, LOG_ERR, LOC + "Attempting backup, anyway."); LOG(VB_GENERAL, LOG_ERR, QString("Backing up database with script: '%1'") .arg(backupScript)); QString command = backupScript + " " + scriptArgs + " " + tempDatabaseConfFile; uint status = myth_system(command, kMSDontBlockInputDevs|kMSAnonLog); if (hastemp) { QByteArray tmpfile = tempDatabaseConfFile.toLocal8Bit(); unlink(tmpfile.constData()); } if (status != GENERIC_EXIT_OK) { LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error backing up database: %1 (%2)") .arg(command).arg(status)); filename = "__FAILED__"; return false; } LOG(VB_GENERAL, LOG_CRIT, "Database Backup complete."); QDir dir(backupDirectory, backupFilename + "*"); uint numfiles = dir.count(); if (numfiles < 1) { // If no file begins with the suggested filename, don't show the backup // filename in the GUI message -- the script probably used some other // filename filename = ""; LOG(VB_FILE, LOG_ERR, LOC + QString("No files beginning with the suggested database backup " "filename '%1' were found in '%2'.") .arg(backupFilename).arg(backupDirectory)); } else { filename = dir.path() + "/" + dir[0];; if (numfiles > 1) { LOG(VB_FILE, LOG_ERR, LOC + QString("Multiple files beginning with the suggested database " "backup filename '%1' were found in '%2'. " "Assuming the first is the backup.") .arg(backupFilename).arg(backupDirectory)); } } if (!filename.isEmpty()) { LOG(VB_GENERAL, LOG_CRIT, QString("Backed up database to file: '%1'") .arg(filename)); } return true; }
/** * Helper function designed to determine if the provided package should be backed up or not. * The function checks for many conditions, such as if the package is too large to backup, * if the package has a particular attribute that should prevent it from being backed up (such * as being marked for PIE-use), if cooking is in progress, etc. * * @param InPackage Package which should be checked to see if its valid for backing-up * @param OutFileName File name of the package on disk if the function determines the package * already existed * * @return true if the package is valid for backing-up; false otherwise */ bool FAutoPackageBackup::ShouldBackupPackage( const UPackage& InPackage, FString& OutFilename ) { // Check various conditions to see if the package is a valid candidate for backing up bool bShouldBackup = GIsEditor // Backing up packages only makes sense in the editor && !IsRunningCommandlet() // Don't backup saves resulting from commandlets && IsPackageBackupEnabled() // Ensure that the package backup is enabled in the first place && (InPackage.HasAnyPackageFlags(PKG_PlayInEditor) == false) // Don't back up PIE packages && (InPackage.HasAnyPackageFlags(PKG_ContainsScript) == false); // Don't back up script packages if( bShouldBackup ) { GWarn->StatusUpdate( -1, -1, NSLOCTEXT("UnrealEd", "PackageBackup_ValidityWarning", "Determining asset backup validity...") ); bShouldBackup = FPackageName::DoesPackageExist( InPackage.GetName(), NULL, &OutFilename ); // Make sure the file already exists (no sense in backing up a new package) } // If the package passed the initial backup checks, proceed to check more specific conditions // that might disqualify the package from being backed up const int32 FileSizeOfBackup = IFileManager::Get().FileSize( *OutFilename ); if ( bShouldBackup ) { // Ensure that the size the backup would require is less than that of the maximum allowed // space for backups bShouldBackup = FileSizeOfBackup <= GetMaxAllowedBackupSpace(); } // If all of the prior checks have passed, now see if the package has been backed up // too recently to be considered for an additional backup if ( bShouldBackup ) { // Ensure that the autosave/backup directory exists const FString& BackupSaveDir = GetBackupDirectory(); IFileManager::Get().MakeDirectory( *BackupSaveDir, 1 ); // Find all of the files in the backup directory TArray<FString> FilesInBackupDir; IFileManager::Get().FindFilesRecursive(FilesInBackupDir, *BackupSaveDir, TEXT("*.*"), true, false); // Extract the base file name and extension from the passed-in package file name FString ExistingBaseFileName = FPaths::GetBaseFilename(OutFilename); FString ExistingFileNameExtension = FPaths::GetExtension(OutFilename); bool bFoundExistingBackup = false; int32 DirectorySize = 0; FDateTime LastBackupTimeStamp = FDateTime::MinValue(); TArray<FBackupFileInfo> BackupFileInfoArray; // Check every file in the backup directory for matches against the passed-in package // (Additionally keep statistics on all backup files for potential maintenance) for ( TArray<FString>::TConstIterator FileIter( FilesInBackupDir ); FileIter; ++FileIter ) { const FString CurBackupFileName = FString( *FileIter ); // Create a new backup file info struct for keeping information about each backup file const int32 FileInfoIndex = BackupFileInfoArray.AddZeroed(); FBackupFileInfo& CurBackupFileInfo = BackupFileInfoArray[ FileInfoIndex ]; // Record the backup file's name, size, and timestamp CurBackupFileInfo.FileName = CurBackupFileName; CurBackupFileInfo.FileSize = IFileManager::Get().FileSize( *CurBackupFileName ); // If we failed to get a timestamp or a valid size, something has happened to the file and it shouldn't be considered CurBackupFileInfo.FileTimeStamp = IFileManager::Get().GetTimeStamp(*CurBackupFileName); if (CurBackupFileInfo.FileTimeStamp == FDateTime::MinValue() || CurBackupFileInfo.FileSize == -1) { BackupFileInfoArray.RemoveAt( BackupFileInfoArray.Num() - 1 ); continue; } // Calculate total directory size by adding the size of this backup file DirectorySize += CurBackupFileInfo.FileSize; FString CurBackupBaseFileName = FPaths::GetBaseFilename(CurBackupFileName); FString CurBackupFileNameExtension = FPaths::GetExtension(CurBackupFileName); // The base file name of the backup file is going to include an underscore followed by a timestamp, so they must be removed for comparison's sake CurBackupBaseFileName = CurBackupBaseFileName.Left( CurBackupBaseFileName.Find( TEXT("_"), ESearchCase::CaseSensitive, ESearchDir::FromEnd ) ); // If the base file names and extensions match, we've found a backup if ( CurBackupBaseFileName == ExistingBaseFileName && CurBackupFileNameExtension == ExistingFileNameExtension ) { bFoundExistingBackup = true; // Keep track of the most recent matching time stamp so we can check if the passed-in package // has been backed up too recently if ( CurBackupFileInfo.FileTimeStamp > LastBackupTimeStamp ) { LastBackupTimeStamp = CurBackupFileInfo.FileTimeStamp; } } } // If there was an existing backup, check to see if it was created too recently to allow another backup if ( bFoundExistingBackup ) { // Check the difference in timestamp seconds against the backup interval; if not enough time has elapsed since // the last backup, we don't want to make another one if ((FDateTime::UtcNow() - LastBackupTimeStamp).GetTotalSeconds() < GetBackupInterval()) { bShouldBackup = false; } } // If every other check against the package has succeeded for backup purposes, ensure there is enough directory space // available in the backup directory, as adding the new backup might use more space than the user allowed for backups. // If the backup file size + the current directory size exceeds the max allowed space, deleted old backups until there // is sufficient space. If enough space can't be freed for whatever reason, then no back-up will be created. if ( bShouldBackup && ( FileSizeOfBackup + DirectorySize > GetMaxAllowedBackupSpace() ) ) { bShouldBackup = PerformBackupSpaceMaintenance( BackupFileInfoArray, DirectorySize, FileSizeOfBackup ); } } return bShouldBackup; }