MythTV  master
schemawizard.cpp
Go to the documentation of this file.
1 #include <iostream> // for cout
2 using std::cout;
3 using std::endl;
4 
5 #include <unistd.h> // for isatty() on Windows
6 
7 #include "dialogbox.h"
8 #include "mythcorecontext.h"
9 #include "schemawizard.h"
10 #include "mythdate.h"
11 
12 #include "mythtimer.h"
13 #include "mythlogging.h"
14 #include "mythmainwindow.h"
15 #include "mythprogressdialog.h"
16 #include "mythuihelper.h"
17 #include "mythmiscutil.h"
18 #include "mythdb.h"
19 
20 
21 static SchemaUpgradeWizard * c_wizard = nullptr;
22 
23 
24 SchemaUpgradeWizard::SchemaUpgradeWizard(const QString &DBSchemaSetting,
25  const QString &appName,
26  const QString &upgradeSchemaVal)
27  : DBver(), emptyDB(false), versionsBehind(-1),
28  backupStatus(kDB_Backup_Unknown),
29  m_autoUpgrade(false),
30  m_backupResult(),
31  m_busyPopup(nullptr),
32  m_expertMode(false),
33  m_schemaSetting(DBSchemaSetting),
34  m_schemaName(appName),
35  m_newSchemaVer(upgradeSchemaVal)
36 {
37  c_wizard = this;
38 
39  // Users and developers can choose to live dangerously,
40  // either to silently and automatically upgrade,
41  // or an expert option to allow use of existing:
42  switch (gCoreContext->GetNumSetting("DBSchemaAutoUpgrade"))
43  {
44  case 1: m_autoUpgrade = true; break;
45 #if ENABLE_SCHEMA_DEVELOPER_MODE
46  case -1: m_expertMode = true; break;
47 #endif
48  default: break;
49  }
50 }
51 
53 {
54  c_wizard = nullptr;
55 }
56 
58 SchemaUpgradeWizard::Get(const QString &DBSchemaSetting,
59  const QString &appName,
60  const QString &upgradeSchemaVal)
61 {
62  if (c_wizard == nullptr)
63  c_wizard = new SchemaUpgradeWizard(DBSchemaSetting, appName,
64  upgradeSchemaVal);
65  else
66  {
67  c_wizard->DBver = QString();
69  c_wizard->m_schemaSetting = DBSchemaSetting;
70  c_wizard->m_schemaName = appName;
71  c_wizard->m_newSchemaVer = upgradeSchemaVal;
72  }
73 
74  return c_wizard;
75 }
76 
80 void SchemaUpgradeWizard::BusyPopup(const QString &message)
81 {
82  if (m_busyPopup)
83  m_busyPopup->SetMessage(message);
84 
85  m_busyPopup = ShowBusyPopup(message);
86 }
87 
89 {
90  if (emptyDB)
91  {
92  LOG(VB_GENERAL, LOG_INFO,
93  "The database seems to be empty - not attempting a backup");
94  return kDB_Backup_Empty_DB;
95  }
96 
98 
99  return backupStatus;
100 }
101 
103 {
105 
106  // No current schema? Investigate further:
107  if (DBver.isEmpty() || DBver == "0")
108  {
109  LOG(VB_GENERAL, LOG_INFO, "No current database version?");
110 
111  if (DBUtil::IsNewDatabase())
112  {
113  LOG(VB_GENERAL, LOG_INFO, "Database appears to be empty/new!");
114  emptyDB = true;
115  }
116  }
117  else
118  LOG(VB_GENERAL, LOG_INFO,
119  QString("Current %1 Schema Version (%2): %3")
120  .arg(m_schemaName).arg(m_schemaSetting).arg(DBver));
121 
122 #if TESTING
123  //DBver = "9" + DBver + "-testing";
124  DBver += "-testing";
125  return 0;
126 #endif
127 
128  if (m_newSchemaVer == DBver)
129  {
130  versionsBehind = 0;
131  }
132  else
133  {
134  // Branch DB versions may not be integer version numbers.
135  bool new_ok, old_ok;
136  int new_version = m_newSchemaVer.toInt(&new_ok);
137  int old_version = DBver.toInt(&old_ok);
138  if (new_ok && old_ok)
139  versionsBehind = new_version - old_version;
140  else
141  versionsBehind = 5000;
142  }
143  return versionsBehind;
144 }
145 
147  bool upgradable, bool expert)
148 {
149  DialogBox * dlg;
151 
152  if (!win)
153  return MYTH_SCHEMA_ERROR;
154 
155  dlg = new DialogBox(win, message);
156  dlg->AddButton(tr("Exit"));
157  if (upgradable)
158  dlg->AddButton(tr("Upgrade"));
159  if (expert)
160  // Not translated. This string can't appear in released builds.
161  dlg->AddButton("Use current schema");
162 
163  DialogCode selected = dlg->exec();
164  dlg->deleteLater();
165 
166  switch (selected)
167  {
168  case kDialogCodeRejected:
169  case kDialogCodeButton0:
170  return MYTH_SCHEMA_EXIT;
171  case kDialogCodeButton1:
172  return upgradable ? MYTH_SCHEMA_UPGRADE: MYTH_SCHEMA_USE_EXISTING;
173  case kDialogCodeButton2:
175  default:
176  return MYTH_SCHEMA_ERROR;
177  }
178 }
179 
204  const bool upgradeAllowed,
205  const bool upgradeIfNoUI,
206  const int minDBMSmajor,
207  const int minDBMSminor,
208  const int minDBMSpoint)
209 {
210  bool connections; // Are (other) FE/BEs connected?
211  bool gui; // Was gContext Init'ed gui=true?
212  bool upgradable; // Can/should we upgrade?
213  bool validDBMS; // Do we measure up to minDBMS* ?
214  QString warnOldDBMS;
215  QString warnOtherCl;
216 
217 
218 
219  if (versionsBehind == -1)
220  Compare();
221 
222 #if minDBMS_is_only_for_schema_upgrades
223  if (versionsBehind == 0) // Why was this method even called?
225 #endif
226 
227  // Only back up the database if we haven't already successfully made a
228  // backup and the database is old/about to be upgraded (not if it's too
229  // new) or if a user is doing something they probably shouldn't ("expert
230  // mode")
231  if (((backupStatus == kDB_Backup_Unknown) ||
233  ((upgradeAllowed && (versionsBehind > 0)) ||
234  m_expertMode))
235  BackupDB();
236 
237  connections = CountClients() > 1;
238  gui = GetMythUI()->IsScreenSetup() && GetMythMainWindow();
239  validDBMS = (minDBMSmajor == 0) // If the caller provided no version,
240  ? true // the upgrade code can't be fussy!
241  : CompareDBMSVersion(minDBMSmajor,
242  minDBMSminor, minDBMSpoint) >= 0;
243  upgradable = validDBMS && (versionsBehind > 0)
244  && (upgradeAllowed || m_expertMode);
245 
246 
247  // Build up strings used both in GUI and command shell contexts:
248  if (connections)
249  warnOtherCl = tr("There are also other clients using this"
250  " database. They should be shut down first.");
251  if (!validDBMS)
252  warnOldDBMS = tr("Error: This version of Myth%1"
253  " requires MySQL %2.%3.%4 or later."
254  " You seem to be running MySQL version %5.")
255  .arg(name).arg(minDBMSmajor).arg(minDBMSminor)
256  .arg(minDBMSpoint).arg(GetDBMSVersion());
257 
258 
259 
260  //
261  // 1. Deal with the trivial cases (No user prompting required)
262  //
263  if (validDBMS)
264  {
265  // Empty database? Always upgrade, to create tables
266  if (emptyDB)
267  return MYTH_SCHEMA_UPGRADE;
268 
269  if (m_autoUpgrade && !connections && upgradable)
270  return MYTH_SCHEMA_UPGRADE;
271  }
272 
273  if (!gui && (!isatty(fileno(stdin)) || !isatty(fileno(stdout))))
274  {
275  LOG(VB_GENERAL, LOG_INFO,
276  "Console is non-interactive, can't prompt user...");
277 
278  if (m_expertMode)
279  {
280  LOG(VB_GENERAL, LOG_CRIT, "Using existing schema.");
282  }
283 
284  if (!validDBMS)
285  {
286  LOG(VB_GENERAL, LOG_CRIT, warnOldDBMS);
287  return MYTH_SCHEMA_EXIT;
288  }
289 
290  if (versionsBehind < 0)
291  {
292  LOG(VB_GENERAL, LOG_CRIT,
293  QString("Error: MythTV database has newer %1 schema (%2) "
294  "than expected (%3).")
295  .arg(name).arg(DBver).arg(m_newSchemaVer));
296  return MYTH_SCHEMA_ERROR;
297  }
298 
299  if (upgradeIfNoUI && validDBMS)
300  {
301  LOG(VB_GENERAL, LOG_CRIT, "Upgrading.");
302  return MYTH_SCHEMA_UPGRADE;
303  }
304 
305  return MYTH_SCHEMA_EXIT;
306  }
307 
308 
309 
310  //
311  // 2. Build up a compound message to show the user, wait for a response
312  //
313  enum MythSchemaUpgrade returnValue = MYTH_SCHEMA_UPGRADE;
314  QString message;
315 
316  if (upgradable)
317  {
318  if (m_autoUpgrade && connections)
319  {
320  message = tr("Error: MythTV cannot upgrade the schema of this"
321  " datatase because other clients are using it.\n\n"
322  "Please shut them down before upgrading.");
323  returnValue = MYTH_SCHEMA_ERROR;
324  }
325  else
326  {
327  message = tr("Warning: MythTV wants to upgrade your database,")
328  + "\n" + tr("for the %1 schema, from %2 to %3.");
329  if (m_expertMode)
330  // Not translated. This string can't appear in released builds.
331  message += "\n\nYou can try using the old schema,"
332  " but that may cause problems.";
333  }
334  }
335  else if (!validDBMS)
336  {
337  message = warnOldDBMS;
338  returnValue = MYTH_SCHEMA_ERROR;
339  }
340  else if (versionsBehind > 0)
341  {
342  message = tr("This version of MythTV requires an updated database. ")
343  + tr("(schema is %1 versions behind)").arg(versionsBehind)
344  + "\n\n" + tr("Please run mythtv-setup or mythbackend "
345  "to update your database.");
346  returnValue = MYTH_SCHEMA_ERROR;
347  }
348  else // This client is too old
349  {
350  if (m_expertMode)
351  // Not translated. This string can't appear in released builds.
352  message = "Warning: MythTV database has newer"
353  " %1 schema (%2) than expected (%3).";
354  else
355  {
356  message = tr("Error: MythTV database has newer"
357  " %1 schema (%2) than expected (%3).");
358  returnValue = MYTH_SCHEMA_ERROR;
359  }
360  }
361 
363  message += "\n" + tr("MythTV was unable to backup your database.");
364 
365  if (message.contains("%1"))
366  message = message.arg(name).arg(DBver).arg(m_newSchemaVer);
367 
368 
370  message += "\n\n" + tr("Database Host: %1\nDatabase Name: %2")
371  .arg(dbParam.dbHostName).arg(dbParam.dbName);
372 
373  if (gui)
374  {
375  if (returnValue == MYTH_SCHEMA_ERROR)
376  {
377  // Display error, return warning to caller
379  return MYTH_SCHEMA_ERROR;
380  }
381 
382  returnValue = GuiPrompt(message, upgradable, m_expertMode);
383  if (returnValue == MYTH_SCHEMA_EXIT)
384  return MYTH_SCHEMA_EXIT;
385 
386  if (m_expertMode)
387  return returnValue;
388 
389  // The annoying extra confirmation:
391  {
392  int dirPos = m_backupResult.lastIndexOf('/');
393  QString dirName;
394  QString fileName;
395  if (dirPos > 0)
396  {
397  fileName = m_backupResult.mid(dirPos + 1);
398  dirName = m_backupResult.left(dirPos);
399  }
400  message = tr("If your system becomes unstable, a database"
401  " backup file called\n%1\nis located in %2")
402  .arg(fileName).arg(dirName);
403  }
404  else
405  message = tr("This cannot be un-done, so having a"
406  " database backup would be a good idea.");
407  if (connections)
408  message += "\n\n" + warnOtherCl;
409 
410  return GuiPrompt(message, upgradable, m_expertMode);
411  }
412 
413  // We are not in a GUI environment, so try to prompt the user in the shell
414  QString resp;
415 
416  cout << endl << message.toLocal8Bit().constData() << endl << endl;
417 
418  if (returnValue == MYTH_SCHEMA_ERROR)
419  return MYTH_SCHEMA_ERROR;
420 
422  cout << "WARNING: MythTV was unable to backup your database."
423  << endl << endl;
424  else if ((backupStatus == kDB_Backup_Completed) &&
425  (m_backupResult != ""))
426  cout << "If your system becomes unstable, "
427  "a database backup is located in "
428  << m_backupResult.toLocal8Bit().constData() << endl << endl;
429 
430  if (m_expertMode)
431  {
432  resp = getResponse("Would you like to use the existing schema?", "yes");
433  if (resp.isEmpty() || resp.startsWith("y", Qt::CaseInsensitive))
435  }
436 
437  resp = getResponse("\nShall I upgrade this database?", "yes");
438  if (!resp.isEmpty() && !resp.startsWith("y", Qt::CaseInsensitive))
439  return MYTH_SCHEMA_EXIT;
440 
441  if (connections)
442  cout << endl << warnOtherCl.toLocal8Bit().constData() << endl;
443 
446  {
447  resp = getResponse("\nA database backup might be a good idea"
448  "\nAre you sure you want to upgrade?", "no");
449  if (resp.isEmpty() || resp.startsWith("n", Qt::CaseInsensitive))
450  return MYTH_SCHEMA_EXIT;
451  }
452 
453  return MYTH_SCHEMA_UPGRADE;
454 }
void SetMessage(const QString &message)
DatabaseParams GetDatabaseParams(void) const
Definition: mythdb.cpp:199
bool emptyDB
Is the database currently empty?
Definition: schemawizard.h:56
Provides UI and helper functions for DB Schema updates.
Definition: schemawizard.h:25
QString dbName
database name
Definition: mythdbparams.h:26
QString m_schemaSetting
To lookup the schema version.
Definition: schemawizard.h:70
int CompareDBMSVersion(int major, int minor=0, int point=0)
Compares the version of the active DBMS with the provided version.
Definition: dbutil.cpp:62
bool m_expertMode
Also allow newer DB schema.
Definition: schemawizard.h:69
bool IsScreenSetup(void)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void AddButton(const QString &title)
Definition: dialogbox.cpp:45
QString m_schemaName
Shown to user in logs.
Definition: schemawizard.h:71
QString m_newSchemaVer
What we need to upgrade to.
Definition: schemawizard.h:72
static MythDB * getMythDB()
Definition: mythdb.cpp:25
static int CountClients(void)
Estimate the number of MythTV programs using the database.
Definition: dbutil.cpp:818
static SchemaUpgradeWizard * Get(const QString &DBSchemaSetting, const QString &appName, const QString &upgradeSchemaVal)
Instead of creating a new wizard, use the existing one for its DB backup file & results and expert se...
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
enum MythSchemaUpgrade PromptForUpgrade(const char *name, const bool upgradeAllowed, const bool upgradeIfNoUI, const int minDMBSmajor=0, const int minDBMSminor=0, const int minDBMSpoint=0)
Query user, to prevent silent, automatic database upgrades.
DialogCode
Definition: mythdialogs.h:21
QString GetSetting(const QString &key, const QString &defaultval="")
MythSchemaUpgrade GuiPrompt(const QString &message, bool upgradable, bool expert)
int Compare(void)
How many schema versions old is the DB?
virtual void deleteLater(void)
Definition: mythdialogs.cpp:88
QString getResponse(const QString &query, const QString &def)
In an interactive shell, prompt the user to input a string.
const char * name
Definition: ParseText.cpp:339
DialogCode exec(void)
MythUIHelper * GetMythUI()
MythMainWindow * GetMythMainWindow(void)
SchemaUpgradeWizard(const QString &DBSchemaSetting, const QString &appName, const QString &upgradeSchemaVal)
MythUIBusyDialog * ShowBusyPopup(const QString &message)
static bool showOkPopup(MythMainWindow *parent, const QString &title, const QString &message, QString button_msg=QString())
int GetNumSetting(const QString &key, int defaultval=0)
Structure containing the basic Database parameters.
Definition: mythdbparams.h:9
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QString dbHostName
database server
Definition: mythdbparams.h:21
bool m_autoUpgrade
If no UI, always upgrade.
Definition: schemawizard.h:66
MythDBBackupStatus
Definition: dbutil.h:9
void BusyPopup(const QString &message)
Delete any current "busy" popup, create new one.
MythSchemaUpgrade
Return values from PromptForUpgrade()
Definition: schemawizard.h:14
QString GetDBMSVersion(void)
Returns the QString version name of the DBMS or QString() in the event of an error.
Definition: dbutil.cpp:43
int versionsBehind
How many schema versions old is the DB?
Definition: schemawizard.h:57
QString m_backupResult
File path, or FAILED
Definition: schemawizard.h:67
MythDBBackupStatus BackupDB(void)
Call DBUtil::BackupDB(), and store results.
MythDBBackupStatus backupStatus
BackupDB() status.
Definition: schemawizard.h:59
static SchemaUpgradeWizard * c_wizard
static bool IsNewDatabase(void)
Returns true for a new (empty) database.
Definition: dbutil.cpp:83
MythDBBackupStatus BackupDB(QString &filename, bool disableRotation=false)
Requests a backup of the database.
Definition: dbutil.cpp:204
QString DBver
Schema version in the database.
Definition: schemawizard.h:55
MythUIBusyDialog * m_busyPopup
Displayed during long pauses.
Definition: schemawizard.h:68