MythTV master
schemawizard.cpp
Go to the documentation of this file.
1#include <iostream> // for std::cout
2
3#include <unistd.h> // for isatty() on Windows
4
5#include <QEventLoop>
6
17
18#include "schemawizard.h"
19
20
21static SchemaUpgradeWizard * c_wizard = nullptr;
22
23
25 QString appName,
26 QString upgradeSchemaVal)
27 : m_schemaSetting(std::move(DBSchemaSetting)),
28 m_schemaName(std::move(appName)),
29 m_newSchemaVer(std::move(upgradeSchemaVal))
30{
31 c_wizard = this;
32
33 // Users and developers can choose to live dangerously,
34 // either to silently and automatically upgrade,
35 // or an expert option to allow use of existing:
36 switch (gCoreContext->GetNumSetting("DBSchemaAutoUpgrade"))
37 {
38 case 1: m_autoUpgrade = true; break;
39#if defined(ENABLE_SCHEMA_DEVELOPER_MODE) && ENABLE_SCHEMA_DEVELOPER_MODE
40 case -1: m_expertMode = true; break;
41#endif
42 default: break;
43 }
44}
45
47{
48 c_wizard = nullptr;
49}
50
52SchemaUpgradeWizard::Get(const QString &DBSchemaSetting,
53 const QString &appName,
54 const QString &upgradeSchemaVal)
55{
56 if (c_wizard == nullptr)
57 {
58 c_wizard = new SchemaUpgradeWizard(DBSchemaSetting, appName,
59 upgradeSchemaVal);
60 }
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
76void 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");
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
108 {
109 LOG(VB_GENERAL, LOG_INFO, "Database appears to be empty/new!");
110 m_emptyDB = true;
111 }
112 }
113 else
114 {
115 LOG(VB_GENERAL, LOG_INFO,
116 QString("Current %1 Schema Version (%2): %3")
118 }
119
120#if defined(TESTING) && TESTING
121 //m_DBver = "9" + m_DBver + "-testing";
122 m_DBver += "-testing";
123 return 0;
124#endif
125
126 if (m_newSchemaVer == m_DBver)
127 {
129 }
130 else
131 {
132 // Branch DB versions may not be integer version numbers.
133 bool new_ok = false;
134 bool old_ok = false;
135 int new_version = m_newSchemaVer.toInt(&new_ok);
136 int old_version = m_DBver.toInt(&old_ok);
137 if (new_ok && old_ok)
138 m_versionsBehind = new_version - old_version;
139 else
140 m_versionsBehind = 5000;
141 }
142 return m_versionsBehind;
143}
144
146 bool upgradable, bool expert)
147{
149 if (!win)
150 return MYTH_SCHEMA_ERROR;
151
152 MythScreenStack *stack = win->GetMainStack();
153 if (!stack)
154 return MYTH_SCHEMA_ERROR;
155
156 // Ignore MENU & ESCAPE dialog actions
157 int btnIndex = -1;
158 while (btnIndex < 0)
159 {
160 auto *dlg = new MythDialogBox(message, stack, "upgrade");
161 if (!dlg->Create())
162 {
163 delete dlg;
164 return MYTH_SCHEMA_ERROR;
165 }
166
167 dlg->AddButton(tr("Exit"));
168
169 if (upgradable)
170 dlg->AddButton(tr("Upgrade"));
171
172 if (expert)
173 // Not translated. This string can't appear in released builds.
174 dlg->AddButton("Use current schema");
175
176 stack->AddScreen(dlg);
177
178 // Wait in local event loop so events are processed
179 QEventLoop block;
180 connect(dlg, &MythDialogBox::Closed,
181 &block, [&](const QString& /*resultId*/, int result) { block.exit(result); });
182
183 // Block until dialog closes
184 btnIndex = block.exec();
185 }
186
187 switch (btnIndex)
188 {
189 case 0 : return MYTH_SCHEMA_EXIT;
190 case 1 : return upgradable ? MYTH_SCHEMA_UPGRADE
192 case 2 : return MYTH_SCHEMA_USE_EXISTING;
193 default : break;
194 }
195
196 return MYTH_SCHEMA_ERROR;
197}
198
223 const bool upgradeAllowed,
224 const bool upgradeIfNoUI,
225 const int minDBMSmajor,
226 const int minDBMSminor,
227 const int minDBMSpoint)
228{
229 bool connections = false; // Are (other) FE/BEs connected?
230 bool gui = false; // Do we have a GUI?
231 bool upgradable = false; // Can/should we upgrade?
232 bool validDBMS = false; // Do we measure up to minDBMS* ?
233 QString warnOldDBMS;
234 QString warnOtherCl;
235
236
237
238 if (m_versionsBehind == -1)
239 Compare();
240
241#if defined(minDBMS_is_only_for_schema_upgrades) && minDBMS_is_only_for_schema_upgrades
242 if (m_versionsBehind == 0) // Why was this method even called?
244#endif
245
246 // Only back up the database if we haven't already successfully made a
247 // backup and the database is old/about to be upgraded (not if it's too
248 // new) or if a user is doing something they probably shouldn't ("expert
249 // mode")
252 ((upgradeAllowed && (m_versionsBehind > 0)) ||
254 BackupDB();
255
256 connections = CountClients() > 1;
258 validDBMS = (minDBMSmajor == 0) // If the caller provided no version,
259 ? true // the upgrade code can't be fussy!
260 : CompareDBMSVersion(minDBMSmajor,
261 minDBMSminor, minDBMSpoint) >= 0;
262 upgradable = validDBMS && (m_versionsBehind > 0)
263 && (upgradeAllowed || m_expertMode);
264
265
266 // Build up strings used both in GUI and command shell contexts:
267 if (connections)
268 warnOtherCl = tr("There are also other clients using this"
269 " database. They should be shut down first.");
270 if (!validDBMS)
271 {
272 warnOldDBMS = tr("Error: This version of Myth%1"
273 " requires MySQL %2.%3.%4 or later."
274 " You seem to be running MySQL version %5.")
275 .arg(name).arg(minDBMSmajor).arg(minDBMSminor)
276 .arg(minDBMSpoint).arg(GetDBMSVersion());
277 }
278
279 //
280 // 1. Deal with the trivial cases (No user prompting required)
281 //
282 if (validDBMS)
283 {
284 // Empty database? Always upgrade, to create tables
285 if (m_emptyDB)
286 return MYTH_SCHEMA_UPGRADE;
287
288 if (m_autoUpgrade && !connections && upgradable)
289 return MYTH_SCHEMA_UPGRADE;
290 }
291
292 if (!gui && (!isatty(fileno(stdin)) || !isatty(fileno(stdout))))
293 {
294 LOG(VB_GENERAL, LOG_INFO,
295 "Console is non-interactive, can't prompt user...");
296
297 if (m_expertMode)
298 {
299 LOG(VB_GENERAL, LOG_CRIT, "Using existing schema.");
301 }
302
303 if (!validDBMS)
304 {
305 LOG(VB_GENERAL, LOG_CRIT, warnOldDBMS);
306 return MYTH_SCHEMA_EXIT;
307 }
308
309 if (m_versionsBehind < 0)
310 {
311 LOG(VB_GENERAL, LOG_CRIT,
312 QString("Error: MythTV database has newer %1 schema (%2) "
313 "than expected (%3).")
314 .arg(name, m_DBver, m_newSchemaVer));
315 return MYTH_SCHEMA_ERROR;
316 }
317
318 if (upgradeIfNoUI && validDBMS)
319 {
320 LOG(VB_GENERAL, LOG_CRIT, "Upgrading.");
321 return MYTH_SCHEMA_UPGRADE;
322 }
323
324 return MYTH_SCHEMA_EXIT;
325 }
326
327
328
329 //
330 // 2. Build up a compound message to show the user, wait for a response
331 //
332 enum MythSchemaUpgrade returnValue = MYTH_SCHEMA_UPGRADE;
333 QString message;
334
335 if (upgradable)
336 {
337 if (m_autoUpgrade && connections)
338 {
339 message = tr("Error: MythTV cannot upgrade the schema of this"
340 " datatase because other clients are using it.\n\n"
341 "Please shut them down before upgrading.");
342 returnValue = MYTH_SCHEMA_ERROR;
343 }
344 else
345 {
346 message = tr("Warning: MythTV wants to upgrade your database,")
347 + "\n" + tr("for the %1 schema, from %2 to %3.");
348 if (m_expertMode)
349 {
350 // Not translated. This string can't appear in released builds.
351 message += "\n\nYou can try using the old schema,"
352 " but that may cause problems.";
353 }
354 }
355 }
356 else if (!validDBMS)
357 {
358 message = warnOldDBMS;
359 returnValue = MYTH_SCHEMA_ERROR;
360 }
361 else if (m_versionsBehind > 0)
362 {
363 message = tr("This version of MythTV requires an updated database. ")
364 + tr("(schema is %1 versions behind)").arg(m_versionsBehind)
365 + "\n\n" + tr("Please run mythtv-setup or mythbackend "
366 "to update your database.");
367 returnValue = MYTH_SCHEMA_ERROR;
368 }
369 else // This client is too old
370 {
371 if (m_expertMode)
372 {
373 // Not translated. This string can't appear in released builds.
374 message = "Warning: MythTV database has newer"
375 " %1 schema (%2) than expected (%3).";
376 }
377 else
378 {
379 message = tr("Error: MythTV database has newer"
380 " %1 schema (%2) than expected (%3).");
381 returnValue = MYTH_SCHEMA_ERROR;
382 }
383 }
384
386 message += "\n" + tr("MythTV was unable to backup your database.");
387
388 if (message.contains("%1"))
389 message = message.arg(name, m_DBver, m_newSchemaVer);
390
391
392 DatabaseParams dbParam = MythDB::getMythDB()->GetDatabaseParams();
393 message += "\n\n" + tr("Database Host: %1\nDatabase Name: %2")
394 .arg(dbParam.m_dbHostName, dbParam.m_dbName);
395
396 if (gui)
397 {
398 if (returnValue == MYTH_SCHEMA_ERROR)
399 {
400 // Display error, wait for acknowledgement
401 // and return warning to caller
402 WaitFor(ShowOkPopup(message));
403 return MYTH_SCHEMA_ERROR;
404 }
405
406 returnValue = GuiPrompt(message, upgradable, m_expertMode);
407
408 if (returnValue == MYTH_SCHEMA_EXIT)
409 return MYTH_SCHEMA_EXIT;
410
411 if (m_expertMode)
412 return returnValue;
413
414 // The annoying extra confirmation:
416 {
417 int dirPos = m_backupResult.lastIndexOf('/');
418 QString dirName;
419 QString fileName;
420 if (dirPos > 0)
421 {
422 fileName = m_backupResult.mid(dirPos + 1);
423 dirName = m_backupResult.left(dirPos);
424 }
425 message = tr("If your system becomes unstable, a database"
426 " backup file called\n%1\nis located in %2")
427 .arg(fileName, dirName);
428 }
429 else
430 {
431 message = tr("This cannot be un-done, so having a"
432 " database backup would be a good idea.");
433 }
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 std::cout << '\n' << message.toLocal8Bit().constData() << "\n\n";
444
445 if (returnValue == MYTH_SCHEMA_ERROR)
446 return MYTH_SCHEMA_ERROR;
447
449 {
450 std::cout << "WARNING: MythTV was unable to backup your database.\n\n";
451 }
452 else if ((m_backupStatus == kDB_Backup_Completed) &&
453 (m_backupResult != ""))
454 {
455 std::cout << "If your system becomes unstable, "
456 "a database backup is located in "
457 << m_backupResult.toLocal8Bit().constData() << "\n\n";
458 }
459
460 if (m_expertMode)
461 {
462 resp = getResponse("Would you like to use the existing schema?", "yes");
463 if (resp.isEmpty() || resp.startsWith("y", Qt::CaseInsensitive))
465 }
466
467 resp = getResponse("\nShall I upgrade this database?", "yes");
468 if (!resp.isEmpty() && !resp.startsWith("y", Qt::CaseInsensitive))
469 return MYTH_SCHEMA_EXIT;
470
471 if (connections)
472 std::cout << '\n' << warnOtherCl.toLocal8Bit().constData() << '\n';
473
476 {
477 resp = getResponse("\nA database backup might be a good idea"
478 "\nAre you sure you want to upgrade?", "no");
479 if (resp.isEmpty() || resp.startsWith("n", Qt::CaseInsensitive))
480 return MYTH_SCHEMA_EXIT;
481 }
482
483 return MYTH_SCHEMA_UPGRADE;
484}
static MythDBBackupStatus BackupDB(QString &filename, bool disableRotation=false)
Requests a backup of the database.
Definition: dbutil.cpp:190
int CompareDBMSVersion(int major, int minor=0, int point=0)
Compares the version of the active DBMS with the provided version.
Definition: dbutil.cpp:57
static int CountClients(void)
Estimate the number of MythTV programs using the database.
Definition: dbutil.cpp:808
QString GetDBMSVersion(void)
Returns the QString version name of the DBMS or QString() in the event of an error.
Definition: dbutil.cpp:38
static bool IsNewDatabase(void)
Returns true for a new (empty) database.
Definition: dbutil.cpp:78
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:29
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