Ticket #4613: mythtv-database_backups_before_upgrade.4.patch

File mythtv-database_backups_before_upgrade.4.patch, 17.4 KB (added by sphery <mtdean@…>, 16 years ago)

Fixes a typo (that had no adverse effects, but was wrong)

  • libs/libmyth/libmyth.pro

     
    2323HEADERS += screensaver.h screensaver-null.h settings.h themeinfo.h
    2424HEADERS += uilistbtntype.h uitypes.h util.h util-x11.h
    2525HEADERS += volumebase.h volumecontrol.h virtualkeyboard.h visual.h xmlparse.h
    26 HEADERS += mythhdd.h mythcdrom.h storagegroup.h
     26HEADERS += mythhdd.h mythcdrom.h storagegroup.h dbutil.h
    2727HEADERS += compat.h
    2828
    2929SOURCES += audiooutput.cpp audiooutputbase.cpp audiooutputnull.cpp
     
    3939SOURCES += screensaver.cpp screensaver-null.cpp settings.cpp themeinfo.cpp
    4040SOURCES += uilistbtntype.cpp uitypes.cpp util.cpp util-x11.cpp
    4141SOURCES += volumebase.cpp volumecontrol.cpp virtualkeyboard.cpp xmlparse.cpp
    42 SOURCES += mythhdd.cpp mythcdrom.cpp storagegroup.cpp
     42SOURCES += mythhdd.cpp mythcdrom.cpp storagegroup.cpp dbutil.cpp
    4343
    4444INCLUDEPATH += ../libmythsamplerate ../libmythsoundtouch ../.. ../ ./
    4545DEPENDPATH += ../libmythsamplerate ../libmythsoundtouch ../ ../libmythui
  • libs/libmythtv/dbcheck.cpp

     
    99#include "mythcontext.h"
    1010#include "mythdbcon.h"
    1111#include "datadirect.h" // for DataDirectProcessor::FixProgramIDs
     12#include "dbutil.h"
    1213
    1314#define MINIMUM_DBMS_VERSION 5
    1415
     
    457481    if (!gContext->GetNumSetting("MythFillFixProgramIDsHasRunOnce", 0))
    458482        DataDirectProcessor::FixProgramIDs();
    459483
    460     MSqlQuery query(MSqlQuery::InitCon());
    461     query.prepare("SELECT VERSION();");
    462     if (!query.exec() || !query.next())
     484    DBUtil dbutil;
     485    int dbmsVersionCheck = dbutil.CompareDBMSVersion(MINIMUM_DBMS_VERSION);
     486    if (dbmsVersionCheck == DBUtil::kUnknownVersionNumber)
    463487    {
    464         VERBOSE(VB_IMPORTANT, "ERROR: Unable to determine MySQL version");
     488        VERBOSE(VB_IMPORTANT, "ERROR: Unable to determine MySQL version.");
    465489        return false;
    466490    }
    467491
    468     QString dbmsversion = query.value(0).toString();
    469     if (dbmsversion.isEmpty() ||
    470         dbmsversion.section('.', 0, 0).toInt() < MINIMUM_DBMS_VERSION)
     492    if (dbmsVersionCheck < 0)
    471493    {
    472494        VERBOSE(VB_IMPORTANT, QString("ERROR: This version of MythTV requires "
    473495                                      "MySQL %1.0 or later.  You seem to be "
    474496                                      "running MySQL version %2.")
    475497                                      .arg(MINIMUM_DBMS_VERSION)
    476                                       .arg(dbmsversion));
     498                                      .arg(dbutil.GetDBMSVersion()));
    477499        VERBOSE(VB_IMPORTANT, "Your database has not been changed. Please "
    478500                              "upgrade your MySQL server or use an older "
    479501                              "version of MythTV.");
     
    483505    if (dbver == currentDatabaseVersion)
    484506        return true;
    485507
     508    if (!dbutil.BackupDB())
     509        VERBOSE(VB_IMPORTANT, "Unable to backup your database.  If you have "
     510                "not already created a backup, you may want to exit before "
     511                "the database upgrade and backup your database.");
     512
    486513    switch (gContext->PromptForSchemaUpgrade(dbver, currentDatabaseVersion))
    487514    {
    488515        case MYTH_SCHEMA_USE_EXISTING: return true;  // Don't upgrade
     
    491518        case MYTH_SCHEMA_UPGRADE:      break;
    492519    }
    493520
     521    MSqlQuery query(MSqlQuery::InitCon());
    494522    query.prepare("ALTER DATABASE mythconverg DEFAULT CHARACTER SET latin1;");
    495523    query.exec();
    496524
  • libs/libmyth/storagegroup.cpp

     
    1616const QStringList StorageGroup::kSpecialGroups = QStringList()
    1717    << "LiveTV"
    1818//    << "Thumbnails"
    19 //    << "DB Backups"
     19    << "DB Backups"
    2020    ;
    2121
    2222/****************************************************************************/
  • libs/libmyth/dbutil.h

     
     1#ifndef DBUTIL_H_
     2#define DBUTIL_H_
     3
     4#include <qstringlist.h>
     5
     6#include "mythexp.h"
     7
     8/** \class DBUtil
     9 *  \brief Aggregates database and DBMS utility functions.
     10 *
     11 *   This class allows retrieving or comparing the DBMS server version, and
     12 *   backing up the database.
     13 *
     14 *   The backup functionality currently requires mysqldump to be installed on
     15 *   the system.  This may change in the future to allow backups even when
     16 *   there is no DB client installation on the system.
     17 *
     18 *  \sa HouseKeeper::RunHouseKeeping(void)
     19 */
     20class MPUBLIC DBUtil
     21{
     22  public:
     23    DBUtil();
     24    ~DBUtil() { }
     25
     26    QString GetDBMSVersion(void);
     27    int CompareDBMSVersion(int major, int minor=0, int point=0);
     28
     29    bool BackupDB(void);
     30
     31    static const int kUnknownVersionNumber;
     32
     33  private:
     34    bool QueryDBMSVersion(void);
     35    bool ParseDBMSVersion(void);
     36
     37    QStringList GetTables(void);
     38
     39    QString CreateBackupFilename(QString prefix = "mythconverg",
     40                                 QString extension = ".sql");
     41    QString GetBackupDirectory();
     42
     43    bool DoBackup(void);
     44
     45    QString m_versionString;
     46
     47    int m_versionMajor;
     48    int m_versionMinor;
     49    int m_versionPoint;
     50
     51};
     52
     53#endif
  • libs/libmyth/dbutil.cpp

     
     1#include <stdio.h>
     2#include <sys/types.h>
     3#include <sys/stat.h>
     4
     5#include <qfile.h>
     6#include <qregexp.h>
     7#include <qdatetime.h>
     8
     9#include "dbutil.h"
     10#include "mythcontext.h"
     11#include "mythdbcon.h"
     12#include "storagegroup.h"
     13#include "util.h"
     14
     15#define LOC QString("DBUtil: ")
     16#define LOC_ERR QString("DBUtil Error: ")
     17
     18const int DBUtil::kUnknownVersionNumber = INT_MIN;
     19
     20/** \fn DBUtil::DBUtil(void)
     21 *  \brief Constructs the DBUtil object.
     22 */
     23DBUtil::DBUtil(void)
     24    : m_versionString(QString::null), m_versionMajor(-1), m_versionMinor(-1),
     25      m_versionPoint(-1)
     26{
     27}
     28
     29/** \fn DBUtil::GetDBMSVersion(void)
     30 *  \brief Returns the QString version name of the DBMS or QString::null in
     31 *         the event of an error.
     32 */
     33QString DBUtil::GetDBMSVersion(void)
     34{
     35    if (m_versionString.isEmpty())
     36        QueryDBMSVersion();
     37    return m_versionString;
     38}
     39
     40/** \fn DBUtil::CompareDBMSVersion(int, int, int)
     41 *  \brief Compares the version of the active DBMS with the provided version.
     42 *
     43 *   Returns negative, 0, or positive if the active DBMS version is less than,
     44 *   equal to, or greater than the provided version or returns
     45 *   DBUtil::kUnknownVersionNumber if the version cannot be determined.
     46 *
     47 *  \param major The major version number (i.e. 5 in "5.0.22")
     48 *  \param minor The minor version number (i.e. 0 in "5.0.22")
     49 *  \param point The point version number (i.e. 22 in "5.0.22")
     50 *  \return negative, 0, or positive or DBUtil::kUnknownVersionNumber for error
     51 */
     52int DBUtil::CompareDBMSVersion(int major, int minor, int point)
     53{
     54    if (m_versionMajor < 0)
     55        if (!ParseDBMSVersion())
     56           return kUnknownVersionNumber;
     57
     58    int result = 0;
     59    int version[3] = {m_versionMajor, m_versionMinor, m_versionPoint};
     60    int compareto[3] = {major, minor, point};
     61    for (int i = 0; i < 3 && !result; i++)
     62    {
     63        if ((version[i] > -1) || (compareto[i] != 0))
     64            result = version[i] - compareto[i];
     65    }
     66
     67    return result;
     68}
     69
     70/** \fn DBUtil::BackupDB(void)
     71 *  \brief Requests a backup of the database.
     72 *
     73 *   Care should be taken in calling this function.  It has the potential to
     74 *   corrupt in-progress recordings or interfere with playback.
     75 */
     76bool DBUtil::BackupDB(void)
     77{
     78    bool result = false;
     79    MSqlQuery query(MSqlQuery::InitCon());
     80
     81    gContext->SaveSettingOnHost("BackupDBLastRunStart",
     82                                QDateTime::currentDateTime()
     83                                .toString("yyyy-MM-dd hh:mm"), NULL);
     84
     85    result = DoBackup();
     86
     87    gContext->SaveSettingOnHost("BackupDBLastRunEnd",
     88                                QDateTime::currentDateTime()
     89                                .toString("yyyy-MM-dd hh:mm"), NULL);
     90
     91    if (query.isConnected())
     92    {
     93        QString dbTag("BackupDB");
     94        query.prepare("DELETE FROM housekeeping WHERE tag = :TAG ;");
     95        query.bindValue(":TAG", dbTag);
     96        query.exec();
     97
     98        query.prepare("INSERT INTO housekeeping(tag,lastrun) "
     99                       "values(:TAG ,now()) ;");
     100        query.bindValue(":TAG", dbTag);
     101        query.exec();
     102    }
     103
     104    return result;
     105}
     106
     107/** \fn DBUtil::GetTables(void)
     108 *  \brief Retrieves a list of tables from the database.
     109 *
     110 *   The function tries to ensure that the list contains only tables and no
     111 *   views.  However, for MySQL 5.0.1, the list will also contain any views
     112 *   defined in the database.
     113 *
     114 *  \return QStringList containing table names
     115 */
     116QStringList DBUtil::GetTables(void)
     117{
     118    QStringList result;
     119    MSqlQuery query(MSqlQuery::InitCon());
     120    if (query.isConnected())
     121    {
     122        QString sql;
     123        // MySQL 5.0.2+ support "SHOW FULL TABLES;" to get the Table_type
     124        // column to distinguish between BASE TABLEs and VIEWs
     125        bool supportsTableType = (CompareDBMSVersion(5, 0, 2) >= 0);
     126        if (supportsTableType)
     127            sql = "SHOW FULL TABLES;";
     128        else
     129            sql = "SHOW TABLES;";
     130
     131        query.prepare(sql);
     132
     133        if (query.exec() && query.size() > 0)
     134        {
     135            while(query.next())
     136            {
     137                if (supportsTableType)
     138                    if (query.value(1).toString() == "VIEW")
     139                        continue;
     140                result.append(query.value(0).toString());
     141            }
     142        }
     143        else
     144            MythContext::DBError("DBUtil Finding Tables", query);
     145    }
     146    return result;
     147}
     148
     149/** \fn DBUtil::CreateBackupFilename(QString prefix, QString extension)
     150 *  \brief Creates a filename to use for the filename.
     151 *
     152 *   The filename is a concatenation of the given prefix, a hyphen, the current
     153 *   date/time, and the extension.
     154 *
     155 *  \param prefix The prefix (i.e. a database name) which should appear before
     156 *                the date/time
     157 *  \param extension The extension to use for the file, including a dot, if
     158 *                   desired
     159 *  \return QString name
     160 */
     161QString DBUtil::CreateBackupFilename(QString prefix, QString extension)
     162{
     163    QDateTime now = QDateTime::currentDateTime();
     164    QString time = now.toString("yyyyMMddhhmmss");
     165    return QString("%1-%2%3").arg(prefix).arg(time).arg(extension);
     166}
     167
     168/** \fn DBUtil::GetBackupPath(void)
     169 *  \brief Determines the appropriate path for the database backup.
     170 *
     171 *   The function requests the special "DB Backups" storage group.  In the
     172 *   event the group is not defined, the StorageGroup will fall back to using
     173 *   the "Default" group.  For users upgrading from version 0.20 or before
     174 *   (which do not support Storage Groups), the StorageGroup will fall back to
     175 *   using the old RecordFilePrefix.
     176 */
     177QString DBUtil::GetBackupDirectory()
     178{
     179    QString directory;
     180    StorageGroup sgroup("DB Backups", gContext->GetHostName());
     181    QStringList dirList = sgroup.GetDirList();
     182    if (dirList.size())
     183        directory = sgroup.FindNextDirMostFree();
     184    else
     185        // Rather than use kDefaultStorageDir, the default for
     186        // FindNextDirMostFree() when no dirs are defined for the StorageGroup,
     187        // use /tmp as it's possible that kDefaultStorageDir doesn't exist
     188        // and (at least on *nix) less possible that /tmp doesn't exist
     189        directory = "/tmp";
     190
     191    return directory;
     192}
     193
     194/** \fn DBUtil::DoBackup(void)
     195 *  \brief Creates a backup of the database.
     196 */
     197bool DBUtil::DoBackup(void)
     198{
     199    DatabaseParams dbParams = gContext->GetDatabaseParams();
     200    QString dbSchemaVer = gContext->GetSetting("DBSchemaVer");
     201    QString backupDirectory = GetBackupDirectory();
     202
     203    /* So we don't have to specify the password on the command line, use
     204       --defaults-extra-file to specify a temporary file with a [client] and
     205       [mysqldump] section that provides the password.  This will fail if the
     206       user's ~/.my.cnf (which is read after the --defaults-extra-file)
     207       specifies a different password that's incorrect for dbUserName */
     208    QString tempExtraConfFile = QDeepCopy<QString>(
     209                    createTempFile("/tmp/mythtv_db_backup_conf_XXXXXX"));
     210    FILE *fp;
     211    if (!(fp = fopen(tempExtraConfFile.ascii(), "w")))
     212    {
     213        VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Unable to create temporary "
     214                "configuration file for creating DB backup: %1")
     215                .arg(tempExtraConfFile.ascii()));
     216        VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Attempting backup, anyway. "
     217                "If the backup fails, please add the %1 user's database "
     218                "password to your MySQL option file.")
     219                .arg(dbParams.dbUserName));
     220    }
     221    else
     222    {
     223        chmod(tempExtraConfFile.ascii(), S_IRUSR);
     224        fprintf(fp, QString("[client]\npassword=%1\n"
     225                            "[mysqldump]\npassword=%2\n")
     226                            .arg(dbParams.dbPassword).arg(dbParams.dbPassword));
     227        if (fclose(fp))
     228            VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Error closing %1: %2")
     229                    .arg(tempExtraConfFile.ascii()).arg(strerror(errno)));
     230    }
     231
     232    QString command;
     233    QString compressCommand("");
     234    QString extension = ".sql";
     235    if (QFile::exists("/bin/gzip"))
     236    {
     237        compressCommand = "| /bin/gzip";
     238        extension = ".sql.gz";
     239    }
     240    else if (QFile::exists("/usr/bin/gzip"))
     241    {
     242        compressCommand = "| /usr/bin/gzip";
     243        extension = ".sql.gz";
     244    }
     245    else
     246        VERBOSE(VB_IMPORTANT, "Neither /bin/gzip nor /usr/bin/gzip exist. "
     247                              "The database backup will be uncompressed.");
     248
     249    QString backupPathname = backupDirectory + "/" +
     250                             CreateBackupFilename(dbParams.dbName + "-" +
     251                                                  dbSchemaVer, extension);
     252    command = QString("mysqldump --defaults-extra-file='%1' --host='%2'"
     253                      " --user='%3' --add-drop-table --add-locks"
     254                      " --allow-keywords --comments --complete-insert"
     255                      " --extended-insert --lock-tables --no-create-db --quick"
     256                      " --set-charset '%4' %5 > '%6' 2>/dev/null")
     257                      .arg(tempExtraConfFile).arg(dbParams.dbHostName)
     258                      .arg(dbParams.dbUserName).arg(dbParams.dbName)
     259                      .arg(compressCommand).arg(backupPathname);
     260    VERBOSE(VB_FILE, QString("Backing up database with command: %1")
     261                             .arg(command.ascii()));
     262    VERBOSE(VB_IMPORTANT, QString("Backing up database to file: %1")
     263            .arg(backupPathname.ascii()));
     264    uint status;
     265    status = myth_system(command.ascii(), MYTH_SYSTEM_DONT_BLOCK_LIRC |
     266                         MYTH_SYSTEM_DONT_BLOCK_JOYSTICK_MENU);
     267
     268    unlink(tempExtraConfFile.ascii());
     269
     270    if (status)
     271    {
     272        VERBOSE(VB_IMPORTANT, LOC_ERR +
     273                QString("Error backing up database: %1 (%2)")
     274                .arg(command.ascii()).arg(status));
     275        return false;
     276    }
     277
     278    return true;
     279}
     280
     281/** \fn DBUtil::QueryDBMSVersion(void)
     282 *  \brief Reads and returns the QString version name from the DBMS or
     283 *         returns QString::null in the event of an error.
     284 */
     285bool DBUtil::QueryDBMSVersion(void)
     286{
     287    // Allow users to override the string provided by the database server in
     288    // case the value was changed to an unrecognizable string by whomever
     289    // compiled the MySQL server
     290    QString dbmsVersion = gContext->GetSetting("DBMSVersionOverride");
     291
     292    if (dbmsVersion.isEmpty())
     293    {
     294        MSqlQuery query(MSqlQuery::InitCon());
     295        query.prepare("SELECT VERSION();");
     296        if (!query.exec() || !query.next())
     297        {
     298            VERBOSE(VB_IMPORTANT, LOC_ERR + "Unable to determine MySQL "
     299                    "version.");
     300            MythContext::DBError("DBUtil Querying DBMS version", query);
     301            dbmsVersion = QString::null;
     302        }
     303        else
     304            dbmsVersion = QString::fromUtf8(query.value(0).toString());
     305    }
     306    m_versionString = dbmsVersion;
     307
     308    return !m_versionString.isEmpty();
     309}
     310
     311/** \fn DBUtil::ParseDBMSVersion(void)
     312 *  \brief Parses m_versionString to find the major, minor, and point version.
     313 */
     314bool DBUtil::ParseDBMSVersion()
     315{
     316    if (m_versionString.isEmpty())
     317        if (!QueryDBMSVersion())
     318            return false;
     319
     320    bool ok;
     321    QString section;
     322    int pos = 0, i = 0;
     323    int version[3] = {-1, -1, -1};
     324    QRegExp digits("(\\d+)");
     325
     326    while ((i < 3) && ((pos = digits.search(m_versionString, pos)) > -1))
     327    {
     328        section = digits.cap(1);
     329        pos += digits.matchedLength();
     330        version[i] = section.toInt(&ok, 10);
     331        if (!ok)
     332            version[i] = -1;
     333        i++;
     334    }
     335
     336    m_versionMajor = version[0];
     337    m_versionMinor = version[1];
     338    m_versionPoint = version[2];
     339
     340    return m_versionMajor > -1;
     341}
     342
     343/* vim: set expandtab tabstop=4 shiftwidth=4: */