MythTV master
schemawizard.cpp
Go to the documentation of this file.
1#include <iostream> // for cout
2using std::cout;
3using std::endl;
4
5#include <unistd.h> // for isatty() on Windows
6
7#include <QEventLoop>
8
11#include "libmythbase/mythdb.h"
19
20#include "schemawizard.h"
21
22
23static 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 defined(ENABLE_SCHEMA_DEVELOPER_MODE) && 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
54SchemaUpgradeWizard::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
78void 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");
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
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")
120 }
121
122#if defined(TESTING) && 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 {
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; // Do we have a GUI?
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 defined(minDBMS_is_only_for_schema_upgrades) && 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)) ||
256 BackupDB();
257
258 connections = CountClients() > 1;
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, m_DBver, 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, m_DBver, m_newSchemaVer);
392
393
394 DatabaseParams dbParam = MythDB::getMythDB()->GetDatabaseParams();
395 message += "\n\n" + tr("Database Host: %1\nDatabase Name: %2")
396 .arg(dbParam.m_dbHostName, 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, dirName);
430 }
431 else
432 {
433 message = tr("This cannot be un-done, so having a"
434 " database backup would be a good idea.");
435 }
436 if (connections)
437 message += "\n\n" + warnOtherCl;
438
439 return GuiPrompt(message, upgradable, m_expertMode);
440 }
441
442 // We are not in a GUI environment, so try to prompt the user in the shell
443 QString resp;
444
445 cout << endl << message.toLocal8Bit().constData() << endl << endl;
446
447 if (returnValue == MYTH_SCHEMA_ERROR)
448 return MYTH_SCHEMA_ERROR;
449
451 {
452 cout << "WARNING: MythTV was unable to backup your database."
453 << endl << endl;
454 }
455 else if ((m_backupStatus == kDB_Backup_Completed) &&
456 (m_backupResult != ""))
457 {
458 cout << "If your system becomes unstable, "
459 "a database backup is located in "
460 << m_backupResult.toLocal8Bit().constData() << endl << endl;
461 }
462
463 if (m_expertMode)
464 {
465 resp = getResponse("Would you like to use the existing schema?", "yes");
466 if (resp.isEmpty() || resp.startsWith("y", Qt::CaseInsensitive))
468 }
469
470 resp = getResponse("\nShall I upgrade this database?", "yes");
471 if (!resp.isEmpty() && !resp.startsWith("y", Qt::CaseInsensitive))
472 return MYTH_SCHEMA_EXIT;
473
474 if (connections)
475 cout << endl << warnOtherCl.toLocal8Bit().constData() << endl;
476
479 {
480 resp = getResponse("\nA database backup might be a good idea"
481 "\nAre you sure you want to upgrade?", "no");
482 if (resp.isEmpty() || resp.startsWith("n", Qt::CaseInsensitive))
483 return MYTH_SCHEMA_EXIT;
484 }
485
486 return MYTH_SCHEMA_UPGRADE;
487}
static MythDBBackupStatus BackupDB(QString &filename, bool disableRotation=false)
Requests a backup of the database.
Definition: dbutil.cpp:186
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
static int CountClients(void)
Estimate the number of MythTV programs using the database.
Definition: dbutil.cpp:804
QString GetDBMSVersion(void)
Returns the QString version name of the DBMS or QString() in the event of an error.
Definition: dbutil.cpp:34
static bool IsNewDatabase(void)
Returns true for a new (empty) database.
Definition: dbutil.cpp:74
Structure containing the basic Database parameters.
Definition: mythdbparams.h:11
QString m_dbName
database name
Definition: mythdbparams.h:26
QString m_dbHostName
database server
Definition: mythdbparams.h:21
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
static MythDB * getMythDB()
Definition: mythdb.cpp:30
Basic menu dialog, message and a list of options.
void Closed(QString, int)
MythScreenStack * GetMainStack()
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void SetMessage(const QString &message)
bool IsScreenSetup() const
Provides UI and helper functions for DB Schema updates.
Definition: schemawizard.h:26
QString m_schemaName
Shown to user in logs.
Definition: schemawizard.h:71
bool m_autoUpgrade
If no UI, always upgrade.
Definition: schemawizard.h:66
MythDBBackupStatus m_backupStatus
BackupDB() status.
Definition: schemawizard.h:59
MythDBBackupStatus BackupDB(void)
Call DBUtil::BackupDB(), and store results.
QString m_newSchemaVer
What we need to upgrade to.
Definition: schemawizard.h:72
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
QString m_backupResult
File path, or FAILED
Definition: schemawizard.h:67
static MythSchemaUpgrade GuiPrompt(const QString &message, bool upgradable, bool expert)
bool m_emptyDB
Is the database currently empty?
Definition: schemawizard.h:56
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.
MythUIBusyDialog * m_busyPopup
Displayed during long pauses.
Definition: schemawizard.h:68
QString m_DBver
Schema version in the database.
Definition: schemawizard.h:55
bool m_expertMode
Also allow newer DB schema.
Definition: schemawizard.h:69
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...
void BusyPopup(const QString &message)
Delete any current "busy" popup, create new one.
SchemaUpgradeWizard(QString DBSchemaSetting, QString appName, QString upgradeSchemaVal)
~SchemaUpgradeWizard() override
int Compare(void)
How many schema versions old is the DB?
MythDBBackupStatus
Definition: dbutil.h:10
@ kDB_Backup_Unknown
Definition: dbutil.h:11
@ kDB_Backup_Failed
Definition: dbutil.h:12
@ kDB_Backup_Empty_DB
Definition: dbutil.h:14
@ kDB_Backup_Completed
Definition: dbutil.h:13
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
bool WaitFor(MythConfirmationDialog *dialog)
Blocks until confirmation dialog exits.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
QString getResponse(const QString &query, const QString &def)
In an interactive shell, prompt the user to input a string.
MythUIBusyDialog * ShowBusyPopup(const QString &message)
MythUIHelper * GetMythUI()
STL namespace.
static SchemaUpgradeWizard * c_wizard
MythSchemaUpgrade
Return values from PromptForUpgrade()
Definition: schemawizard.h:15
@ MYTH_SCHEMA_ERROR
Definition: schemawizard.h:17
@ MYTH_SCHEMA_UPGRADE
Definition: schemawizard.h:18
@ MYTH_SCHEMA_EXIT
Definition: schemawizard.h:16
@ MYTH_SCHEMA_USE_EXISTING
Definition: schemawizard.h:19