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