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 <QEventLoop>
8 
9 #include "mythdialogbox.h"
10 #include "mythcorecontext.h"
11 #include "schemawizard.h"
12 #include "mythdate.h"
13 
14 #include "mythtimer.h"
15 #include "mythlogging.h"
16 #include "mythmainwindow.h"
17 #include "mythprogressdialog.h"
18 #include "mythuihelper.h"
19 #include "mythmiscutil.h"
20 #include "mythdb.h"
21 
22 
23 static SchemaUpgradeWizard * c_wizard = nullptr;
24 
25 
27  QString appName,
28  QString upgradeSchemaVal)
29  : m_schemaSetting(std::move(DBSchemaSetting)),
30  m_schemaName(std::move(appName)),
31  m_newSchemaVer(std::move(upgradeSchemaVal))
32 {
33  c_wizard = this;
34 
35  // Users and developers can choose to live dangerously,
36  // either to silently and automatically upgrade,
37  // or an expert option to allow use of existing:
38  switch (gCoreContext->GetNumSetting("DBSchemaAutoUpgrade"))
39  {
40  case 1: m_autoUpgrade = true; break;
41 #if ENABLE_SCHEMA_DEVELOPER_MODE
42  case -1: m_expertMode = true; break;
43 #endif
44  default: break;
45  }
46 }
47 
49 {
50  c_wizard = nullptr;
51 }
52 
54 SchemaUpgradeWizard::Get(const QString &DBSchemaSetting,
55  const QString &appName,
56  const QString &upgradeSchemaVal)
57 {
58  if (c_wizard == nullptr)
59  {
60  c_wizard = new SchemaUpgradeWizard(DBSchemaSetting, appName,
61  upgradeSchemaVal);
62  }
63  else
64  {
65  c_wizard->m_DBver = QString();
67  c_wizard->m_schemaSetting = DBSchemaSetting;
68  c_wizard->m_schemaName = appName;
69  c_wizard->m_newSchemaVer = upgradeSchemaVal;
70  }
71 
72  return c_wizard;
73 }
74 
78 void SchemaUpgradeWizard::BusyPopup(const QString &message)
79 {
80  if (m_busyPopup)
81  m_busyPopup->SetMessage(message);
82 
83  m_busyPopup = ShowBusyPopup(message);
84 }
85 
87 {
88  if (m_emptyDB)
89  {
90  LOG(VB_GENERAL, LOG_INFO,
91  "The database seems to be empty - not attempting a backup");
92  return kDB_Backup_Empty_DB;
93  }
94 
96 
97  return m_backupStatus;
98 }
99 
101 {
103 
104  // No current schema? Investigate further:
105  if (m_DBver.isEmpty() || m_DBver == "0")
106  {
107  LOG(VB_GENERAL, LOG_INFO, "No current database version?");
108 
109  if (DBUtil::IsNewDatabase())
110  {
111  LOG(VB_GENERAL, LOG_INFO, "Database appears to be empty/new!");
112  m_emptyDB = true;
113  }
114  }
115  else
116  {
117  LOG(VB_GENERAL, LOG_INFO,
118  QString("Current %1 Schema Version (%2): %3")
119  .arg(m_schemaName).arg(m_schemaSetting).arg(m_DBver));
120  }
121 
122 #if TESTING
123  //m_DBver = "9" + m_DBver + "-testing";
124  m_DBver += "-testing";
125  return 0;
126 #endif
127 
128  if (m_newSchemaVer == m_DBver)
129  {
130  m_versionsBehind = 0;
131  }
132  else
133  {
134  // Branch DB versions may not be integer version numbers.
135  bool new_ok = false;
136  bool old_ok = false;
137  int new_version = m_newSchemaVer.toInt(&new_ok);
138  int old_version = m_DBver.toInt(&old_ok);
139  if (new_ok && old_ok)
140  m_versionsBehind = new_version - old_version;
141  else
142  m_versionsBehind = 5000;
143  }
144  return m_versionsBehind;
145 }
146 
148  bool upgradable, bool expert)
149 {
151  if (!win)
152  return MYTH_SCHEMA_ERROR;
153 
154  MythScreenStack *stack = win->GetMainStack();
155  if (!stack)
156  return MYTH_SCHEMA_ERROR;
157 
158  // Ignore MENU & ESCAPE dialog actions
159  int btnIndex = -1;
160  while (btnIndex < 0)
161  {
162  auto *dlg = new MythDialogBox(message, stack, "upgrade");
163  if (!dlg->Create())
164  {
165  delete dlg;
166  return MYTH_SCHEMA_ERROR;
167  }
168 
169  dlg->AddButton(tr("Exit"));
170 
171  if (upgradable)
172  dlg->AddButton(tr("Upgrade"));
173 
174  if (expert)
175  // Not translated. This string can't appear in released builds.
176  dlg->AddButton("Use current schema");
177 
178  stack->AddScreen(dlg);
179 
180  // Wait in local event loop so events are processed
181  QEventLoop block;
182  connect(dlg, &MythDialogBox::Closed,
183  &block, [&](const QString& /*resultId*/, int result) { block.exit(result); });
184 
185  // Block until dialog closes
186  btnIndex = block.exec();
187  }
188 
189  switch (btnIndex)
190  {
191  case 0 : return MYTH_SCHEMA_EXIT;
192  case 1 : return upgradable ? MYTH_SCHEMA_UPGRADE
194  case 2 : return MYTH_SCHEMA_USE_EXISTING;
195  default : break;
196  }
197 
198  return MYTH_SCHEMA_ERROR;
199 }
200 
225  const bool upgradeAllowed,
226  const bool upgradeIfNoUI,
227  const int minDBMSmajor,
228  const int minDBMSminor,
229  const int minDBMSpoint)
230 {
231  bool connections = false; // Are (other) FE/BEs connected?
232  bool gui = false; // Was gContext Init'ed gui=true?
233  bool upgradable = false; // Can/should we upgrade?
234  bool validDBMS = false; // Do we measure up to minDBMS* ?
235  QString warnOldDBMS;
236  QString warnOtherCl;
237 
238 
239 
240  if (m_versionsBehind == -1)
241  Compare();
242 
243 #if minDBMS_is_only_for_schema_upgrades
244  if (m_versionsBehind == 0) // Why was this method even called?
246 #endif
247 
248  // Only back up the database if we haven't already successfully made a
249  // backup and the database is old/about to be upgraded (not if it's too
250  // new) or if a user is doing something they probably shouldn't ("expert
251  // mode")
254  ((upgradeAllowed && (m_versionsBehind > 0)) ||
255  m_expertMode))
256  BackupDB();
257 
258  connections = CountClients() > 1;
259  gui = GetMythUI()->IsScreenSetup() && GetMythMainWindow();
260  validDBMS = (minDBMSmajor == 0) // If the caller provided no version,
261  ? true // the upgrade code can't be fussy!
262  : CompareDBMSVersion(minDBMSmajor,
263  minDBMSminor, minDBMSpoint) >= 0;
264  upgradable = validDBMS && (m_versionsBehind > 0)
265  && (upgradeAllowed || m_expertMode);
266 
267 
268  // Build up strings used both in GUI and command shell contexts:
269  if (connections)
270  warnOtherCl = tr("There are also other clients using this"
271  " database. They should be shut down first.");
272  if (!validDBMS)
273  {
274  warnOldDBMS = tr("Error: This version of Myth%1"
275  " requires MySQL %2.%3.%4 or later."
276  " You seem to be running MySQL version %5.")
277  .arg(name).arg(minDBMSmajor).arg(minDBMSminor)
278  .arg(minDBMSpoint).arg(GetDBMSVersion());
279  }
280 
281  //
282  // 1. Deal with the trivial cases (No user prompting required)
283  //
284  if (validDBMS)
285  {
286  // Empty database? Always upgrade, to create tables
287  if (m_emptyDB)
288  return MYTH_SCHEMA_UPGRADE;
289 
290  if (m_autoUpgrade && !connections && upgradable)
291  return MYTH_SCHEMA_UPGRADE;
292  }
293 
294  if (!gui && (!isatty(fileno(stdin)) || !isatty(fileno(stdout))))
295  {
296  LOG(VB_GENERAL, LOG_INFO,
297  "Console is non-interactive, can't prompt user...");
298 
299  if (m_expertMode)
300  {
301  LOG(VB_GENERAL, LOG_CRIT, "Using existing schema.");
303  }
304 
305  if (!validDBMS)
306  {
307  LOG(VB_GENERAL, LOG_CRIT, warnOldDBMS);
308  return MYTH_SCHEMA_EXIT;
309  }
310 
311  if (m_versionsBehind < 0)
312  {
313  LOG(VB_GENERAL, LOG_CRIT,
314  QString("Error: MythTV database has newer %1 schema (%2) "
315  "than expected (%3).")
316  .arg(name).arg(m_DBver).arg(m_newSchemaVer));
317  return MYTH_SCHEMA_ERROR;
318  }
319 
320  if (upgradeIfNoUI && validDBMS)
321  {
322  LOG(VB_GENERAL, LOG_CRIT, "Upgrading.");
323  return MYTH_SCHEMA_UPGRADE;
324  }
325 
326  return MYTH_SCHEMA_EXIT;
327  }
328 
329 
330 
331  //
332  // 2. Build up a compound message to show the user, wait for a response
333  //
334  enum MythSchemaUpgrade returnValue = MYTH_SCHEMA_UPGRADE;
335  QString message;
336 
337  if (upgradable)
338  {
339  if (m_autoUpgrade && connections)
340  {
341  message = tr("Error: MythTV cannot upgrade the schema of this"
342  " datatase because other clients are using it.\n\n"
343  "Please shut them down before upgrading.");
344  returnValue = MYTH_SCHEMA_ERROR;
345  }
346  else
347  {
348  message = tr("Warning: MythTV wants to upgrade your database,")
349  + "\n" + tr("for the %1 schema, from %2 to %3.");
350  if (m_expertMode)
351  {
352  // Not translated. This string can't appear in released builds.
353  message += "\n\nYou can try using the old schema,"
354  " but that may cause problems.";
355  }
356  }
357  }
358  else if (!validDBMS)
359  {
360  message = warnOldDBMS;
361  returnValue = MYTH_SCHEMA_ERROR;
362  }
363  else if (m_versionsBehind > 0)
364  {
365  message = tr("This version of MythTV requires an updated database. ")
366  + tr("(schema is %1 versions behind)").arg(m_versionsBehind)
367  + "\n\n" + tr("Please run mythtv-setup or mythbackend "
368  "to update your database.");
369  returnValue = MYTH_SCHEMA_ERROR;
370  }
371  else // This client is too old
372  {
373  if (m_expertMode)
374  {
375  // Not translated. This string can't appear in released builds.
376  message = "Warning: MythTV database has newer"
377  " %1 schema (%2) than expected (%3).";
378  }
379  else
380  {
381  message = tr("Error: MythTV database has newer"
382  " %1 schema (%2) than expected (%3).");
383  returnValue = MYTH_SCHEMA_ERROR;
384  }
385  }
386 
388  message += "\n" + tr("MythTV was unable to backup your database.");
389 
390  if (message.contains("%1"))
391  message = message.arg(name).arg(m_DBver).arg(m_newSchemaVer);
392 
393 
395  message += "\n\n" + tr("Database Host: %1\nDatabase Name: %2")
396  .arg(dbParam.m_dbHostName).arg(dbParam.m_dbName);
397 
398  if (gui)
399  {
400  if (returnValue == MYTH_SCHEMA_ERROR)
401  {
402  // Display error, wait for acknowledgement
403  // and return warning to caller
404  WaitFor(ShowOkPopup(message));
405  return MYTH_SCHEMA_ERROR;
406  }
407 
408  returnValue = GuiPrompt(message, upgradable, m_expertMode);
409 
410  if (returnValue == MYTH_SCHEMA_EXIT)
411  return MYTH_SCHEMA_EXIT;
412 
413  if (m_expertMode)
414  return returnValue;
415 
416  // The annoying extra confirmation:
418  {
419  int dirPos = m_backupResult.lastIndexOf('/');
420  QString dirName;
421  QString fileName;
422  if (dirPos > 0)
423  {
424  fileName = m_backupResult.mid(dirPos + 1);
425  dirName = m_backupResult.left(dirPos);
426  }
427  message = tr("If your system becomes unstable, a database"
428  " backup file called\n%1\nis located in %2")
429  .arg(fileName).arg(dirName);
430  }
431  else
432  message = tr("This cannot be un-done, so having a"
433  " database backup would be a good idea.");
434  if (connections)
435  message += "\n\n" + warnOtherCl;
436 
437  return GuiPrompt(message, upgradable, m_expertMode);
438  }
439 
440  // We are not in a GUI environment, so try to prompt the user in the shell
441  QString resp;
442 
443  cout << endl << message.toLocal8Bit().constData() << endl << endl;
444 
445  if (returnValue == MYTH_SCHEMA_ERROR)
446  return MYTH_SCHEMA_ERROR;
447 
449  {
450  cout << "WARNING: MythTV was unable to backup your database."
451  << endl << endl;
452  }
453  else if ((m_backupStatus == kDB_Backup_Completed) &&
454  (m_backupResult != ""))
455  {
456  cout << "If your system becomes unstable, "
457  "a database backup is located in "
458  << m_backupResult.toLocal8Bit().constData() << endl << endl;
459  }
460 
461  if (m_expertMode)
462  {
463  resp = getResponse("Would you like to use the existing schema?", "yes");
464  if (resp.isEmpty() || resp.startsWith("y", Qt::CaseInsensitive))
466  }
467 
468  resp = getResponse("\nShall I upgrade this database?", "yes");
469  if (!resp.isEmpty() && !resp.startsWith("y", Qt::CaseInsensitive))
470  return MYTH_SCHEMA_EXIT;
471 
472  if (connections)
473  cout << endl << warnOtherCl.toLocal8Bit().constData() << endl;
474 
477  {
478  resp = getResponse("\nA database backup might be a good idea"
479  "\nAre you sure you want to upgrade?", "no");
480  if (resp.isEmpty() || resp.startsWith("n", Qt::CaseInsensitive))
481  return MYTH_SCHEMA_EXIT;
482  }
483 
484  return MYTH_SCHEMA_UPGRADE;
485 }
void SetMessage(const QString &message)
DatabaseParams GetDatabaseParams(void) const
Definition: mythdb.cpp:199
QString m_dbHostName
database server
Definition: mythdbparams.h:21
Provides UI and helper functions for DB Schema updates.
Definition: schemawizard.h:25
bool WaitFor(MythConfirmationDialog *dialog)
Blocks until confirmation dialog exits.
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
MythDBBackupStatus m_backupStatus
BackupDB() status.
Definition: schemawizard.h:59
int m_versionsBehind
How many schema versions old is the DB?
Definition: schemawizard.h:57
QString m_schemaSetting
To lookup the schema version.
Definition: schemawizard.h:70
Basic menu dialog, message and a list of options.
int CompareDBMSVersion(int major, int minor=0, int point=0)
Compares the version of the active DBMS with the provided version.
Definition: dbutil.cpp:53
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.
MythScreenStack * GetMainStack()
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
bool m_emptyDB
Is the database currently empty?
Definition: schemawizard.h:56
static int CountClients(void)
Estimate the number of MythTV programs using the database.
Definition: dbutil.cpp:808
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
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...
~SchemaUpgradeWizard() override
QString GetSetting(const QString &key, const QString &defaultval="")
static MythSchemaUpgrade GuiPrompt(const QString &message, bool upgradable, bool expert)
QString m_dbName
database name
Definition: mythdbparams.h:26
int Compare(void)
How many schema versions old is the DB?
enum MythSchemaUpgrade PromptForUpgrade(const char *name, bool upgradeAllowed, bool upgradeIfNoUI, int minDBMSmajor=0, int minDBMSminor=0, int minDBMSpoint=0)
Query user, to prevent silent, automatic database upgrades.
QString getResponse(const QString &query, const QString &def)
In an interactive shell, prompt the user to input a string.
MythUIHelper * GetMythUI()
MythMainWindow * GetMythMainWindow(void)
MythUIBusyDialog * ShowBusyPopup(const QString &message)
int GetNumSetting(const QString &key, int defaultval=0)
Structure containing the basic Database parameters.
Definition: mythdbparams.h:9
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.
SchemaUpgradeWizard(QString DBSchemaSetting, QString appName, QString upgradeSchemaVal)
MythSchemaUpgrade
Return values from PromptForUpgrade()
Definition: schemawizard.h:14
QString m_DBver
Schema version in the database.
Definition: schemawizard.h:55
QString GetDBMSVersion(void)
Returns the QString version name of the DBMS or QString() in the event of an error.
Definition: dbutil.cpp:34
QString m_backupResult
File path, or FAILED
Definition: schemawizard.h:67
MythDBBackupStatus BackupDB(void)
Call DBUtil::BackupDB(), and store results.
static SchemaUpgradeWizard * c_wizard
static bool IsNewDatabase(void)
Returns true for a new (empty) database.
Definition: dbutil.cpp:74
void Closed(QString, int)
static MythDBBackupStatus BackupDB(QString &filename, bool disableRotation=false)
Requests a backup of the database.
Definition: dbutil.cpp:185
MythUIBusyDialog * m_busyPopup
Displayed during long pauses.
Definition: schemawizard.h:68
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23