1 #include "preferences/upgrade.h"
2
3 #include <QMessageBox>
4 #include <QPixmap>
5 #include <QPushButton>
6 #include <QScopedPointer>
7 #include <QTranslator>
8
9 #include "config.h"
10 #include "controllers/defs_controllers.h"
11 #include "database/mixxxdb.h"
12 #include "library/library_preferences.h"
13 #include "library/trackcollection.h"
14 #include "preferences/beatdetectionsettings.h"
15 #include "preferences/usersettings.h"
16 #include "util/cmdlineargs.h"
17 #include "util/db/dbconnectionpooled.h"
18 #include "util/db/dbconnectionpooler.h"
19 #include "util/math.h"
20 #include "util/versionstore.h"
21
Upgrade()22 Upgrade::Upgrade()
23 : m_bFirstRun(false),
24 m_bRescanLibrary(false) {
25 }
26
~Upgrade()27 Upgrade::~Upgrade() {
28 }
29
30 // We return the UserSettings here because we have to make changes to the
31 // configuration and the location of the file may change between releases.
versionUpgrade(const QString & settingsPath)32 UserSettingsPointer Upgrade::versionUpgrade(const QString& settingsPath) {
33
34 /* Pre-1.7.0:
35 *
36 * Since we didn't store version numbers in the config file prior to 1.7.0,
37 * we check to see if the user is upgrading if his config files are in the old location,
38 * since we moved them in 1.7.0. This code takes care of moving them.
39 */
40
41 QDir oldLocation = QDir(QDir::homePath());
42 #ifdef __WINDOWS__
43 QFileInfo* pre170Config = new QFileInfo(oldLocation.filePath("mixxx.cfg"));
44 #else
45 QFileInfo* pre170Config = new QFileInfo(oldLocation.filePath(".mixxx.cfg"));
46 #endif
47
48 if (pre170Config->exists()) {
49
50 // Move the files to their new location
51 QDir newLocation = QDir(settingsPath);
52
53 if (!newLocation.exists()) {
54 qDebug() << "Creating new settings directory" << newLocation.absolutePath();
55 newLocation.mkpath(".");
56 }
57
58 QString errorText = "Error moving your %1 file %2 to the new location %3: \n";
59
60 #ifdef __WINDOWS__
61 QString oldFilePath = oldLocation.filePath("mixxxtrack.xml");
62 #else
63 QString oldFilePath = oldLocation.filePath(".mixxxtrack.xml");
64 #endif
65
66 QString newFilePath = newLocation.filePath("mixxxtrack.xml");
67 QFile* oldFile = new QFile(oldFilePath);
68 if (oldFile->exists()) {
69 if (oldFile->copy(newFilePath)) {
70 oldFile->remove();
71 }
72 else {
73 if (oldFile->error() == 14) {
74 qDebug() << errorText.arg(
75 "library", oldFilePath, newFilePath)
76 << "The destination file already exists.";
77 } else {
78 qDebug() << errorText.arg(
79 "library", oldFilePath, newFilePath)
80 << "Error #" << oldFile->error();
81 }
82 }
83 }
84 delete oldFile;
85
86 #ifdef __WINDOWS__
87 oldFilePath = oldLocation.filePath("mixxxbpmschemes.xml");
88 #else
89 oldFilePath = oldLocation.filePath(".mixxxbpmscheme.xml");
90 #endif
91 newFilePath = newLocation.filePath("mixxxbpmscheme.xml");
92 oldFile = new QFile(oldFilePath);
93 if (oldFile->exists()) {
94 if (oldFile->copy(newFilePath)) {
95 oldFile->remove();
96 } else {
97 if (oldFile->error() == 14) {
98 qDebug() << errorText.arg(
99 "settings", oldFilePath, newFilePath)
100 << "The destination file already exists.";
101 } else {
102 qDebug() << errorText.arg(
103 "settings", oldFilePath, newFilePath)
104 << "Error #" << oldFile->error();
105 }
106 }
107 }
108 delete oldFile;
109 #ifdef __WINDOWS__
110 oldFilePath = oldLocation.filePath("MixxxMIDIBindings.xml");
111 #else
112 oldFilePath = oldLocation.filePath(".MixxxMIDIBindings.xml");
113 #endif
114 newFilePath = newLocation.filePath("MixxxMIDIBindings.xml");
115 oldFile = new QFile(oldFilePath);
116 if (oldFile->exists()) {
117 qWarning() << "The MIDI mapping file format has changed in this version of Mixxx. You will need to reconfigure your MIDI controller. See the Wiki for full details on the new format.";
118 if (oldFile->copy(newFilePath)) {
119 oldFile->remove();
120 } else {
121 if (oldFile->error() == 14) {
122 qDebug()
123 << errorText.arg(
124 "MIDI mapping", oldFilePath, newFilePath)
125 << "The destination file already exists.";
126 } else {
127 qDebug()
128 << errorText.arg(
129 "MIDI mapping", oldFilePath, newFilePath)
130 << "Error #" << oldFile->error();
131 }
132 }
133 }
134 // Tidy up
135 delete oldFile;
136 #ifdef __WINDOWS__
137 QFile::remove(oldLocation.filePath("MixxxMIDIDevice.xml")); // Obsolete file, so just delete it
138 #else
139 QFile::remove(oldLocation.filePath(".MixxxMIDIDevice.xml")); // Obsolete file, so just delete it
140 #endif
141
142 #ifdef __WINDOWS__
143 oldFilePath = oldLocation.filePath("mixxx.cfg");
144 #else
145 oldFilePath = oldLocation.filePath(".mixxx.cfg");
146 #endif
147 newFilePath = newLocation.filePath(MIXXX_SETTINGS_FILE);
148 oldFile = new QFile(oldFilePath);
149 if (oldFile->copy(newFilePath)) {
150 oldFile->remove();
151 } else {
152 if (oldFile->error() == 14) {
153 qDebug() << errorText.arg(
154 "configuration", oldFilePath, newFilePath)
155 << "The destination file already exists.";
156 } else {
157 qDebug() << errorText.arg(
158 "configuration", oldFilePath, newFilePath)
159 << "Error #" << oldFile->error();
160 }
161 }
162 delete oldFile;
163
164 }
165 // Tidy up
166 delete pre170Config;
167 // End pre-1.7.0 code
168
169
170 /***************************************************************************
171 * Post-1.7.0 upgrade code
172 *
173 * Add entries to the IF ladder below if anything needs to change from the
174 * previous to the current version. This allows for incremental upgrades
175 * in case a user upgrades from a few versions prior.
176 ****************************************************************************/
177
178 // Read the config file from home directory
179 UserSettingsPointer config(new ConfigObject<ConfigValue>(
180 QDir(settingsPath).filePath(MIXXX_SETTINGS_FILE)));
181
182 QString configVersion = config->getValueString(ConfigKey("[Config]","Version"));
183
184 if (configVersion.isEmpty()) {
185
186 #ifdef __APPLE__
187 qDebug() << "Config version is empty, trying to read pre-1.9.0 config";
188 // Try to read the config from the pre-1.9.0 final directory on OS X (we moved it in 1.9.0 final)
189 QScopedPointer<QFile> oldConfigFile(new QFile(QDir::homePath().append("/").append(".mixxx/mixxx.cfg")));
190 if (oldConfigFile->exists() && ! CmdlineArgs::Instance().getSettingsPathSet()) {
191 qDebug() << "Found pre-1.9.0 config for OS X";
192 // Note: We changed MIXXX_SETTINGS_PATH in 1.9.0 final on OS X so
193 // it must be hardcoded to ".mixxx" here for legacy.
194 config = UserSettingsPointer(new ConfigObject<ConfigValue>(
195 QDir::homePath().append("/.mixxx/mixxx.cfg")));
196 // Just to be sure all files like logs and soundconfig go with mixxx.cfg
197 // TODO(XXX) Trailing slash not needed anymore as we switches from String::append
198 // to QDir::filePath elsewhere in the code. This is candidate for removal.
199 CmdlineArgs::Instance().setSettingsPath(QDir::homePath().append("/.mixxx/"));
200 configVersion = config->getValueString(ConfigKey("[Config]","Version"));
201 }
202 else {
203 #elif __WINDOWS__
204 qDebug() << "Config version is empty, trying to read pre-1.12.0 config";
205 // Try to read the config from the pre-1.12.0 final directory on Windows (we moved it in 1.12.0 final)
206 QScopedPointer<QFile> oldConfigFile(new QFile(QDir::homePath().append("/Local Settings/Application Data/Mixxx/mixxx.cfg")));
207 if (oldConfigFile->exists() && ! CmdlineArgs::Instance().getSettingsPathSet()) {
208 qDebug() << "Found pre-1.12.0 config for Windows";
209 // Note: We changed MIXXX_SETTINGS_PATH in 1.12.0 final on Windows
210 // so it must be hardcoded to "Local Settings/Application
211 // Data/Mixxx/" here for legacy.
212 config = UserSettingsPointer(new ConfigObject<ConfigValue>(
213 QDir::homePath().append("/Local Settings/Application Data/Mixxx/mixxx.cfg")));
214 // Just to be sure all files like logs and soundconfig go with mixxx.cfg
215 // TODO(XXX) Trailing slash not needed anymore as we switches from String::append
216 // to QDir::filePath elsewhere in the code. This is candidate for removal.
217 CmdlineArgs::Instance().setSettingsPath(QDir::homePath().append("/Local Settings/Application Data/Mixxx/"));
218 configVersion = config->getValueString(ConfigKey("[Config]","Version"));
219 }
220 else {
221 #endif
222 // This must have been the first run... right? :)
223 qDebug() << "No version number in configuration file. Setting to"
224 << VersionStore::version();
225 config->set(ConfigKey("[Config]", "Version"), ConfigValue(VersionStore::version()));
226 m_bFirstRun = true;
227 return config;
228 #ifdef __APPLE__
229 }
230 #elif __WINDOWS__
231 }
232 #endif
233 }
234
235 // If it's already current, stop here
236 if (configVersion == VersionStore::version()) {
237 qDebug() << "Configuration file is at the current version" << VersionStore::version();
238 return config;
239 }
240
241 // Allows for incremental upgrades in case someone upgrades from a few versions prior
242 // (I wish we could do a switch on a QString.)
243 /*
244 // Examples, since we didn't store the version number prior to v1.7.0
245 if (configVersion.startsWith("1.6.0")) {
246 qDebug() << "Upgrading from v1.6.0 to 1.6.1...";
247 // Upgrade tasks go here
248 configVersion = "1.6.1";
249 config->set(ConfigKey("[Config]","Version"), ConfigValue("1.6.1"));
250 }
251 if (configVersion.startsWith("1.6.1")) {
252 qDebug() << "Upgrading from v1.6.1 to 1.7.0...";
253 // Upgrade tasks go here
254 configVersion = "1.7.0";
255 config->set(ConfigKey("[Config]","Version"), ConfigValue("1.7.0"));
256 }
257 */
258
259 // We use the following blocks to detect if this is the first time
260 // you've run the latest version of Mixxx. This lets us show
261 // the promo tracks stats agreement stuff for all users that are
262 // upgrading Mixxx.
263
264 if (configVersion.startsWith("1.7")) {
265 qDebug() << "Upgrading from v1.7.x...";
266 // Upgrade tasks go here
267 // Nothing to change, really
268 configVersion = "1.8.0";
269 config->set(ConfigKey("[Config]","Version"), ConfigValue("1.8.0"));
270 }
271
272 if (configVersion.startsWith("1.8.0~beta1") ||
273 configVersion.startsWith("1.8.0~beta2")) {
274 qDebug() << "Upgrading from v1.8.0~beta...";
275 // Upgrade tasks go here
276 configVersion = "1.8.0";
277 config->set(ConfigKey("[Config]","Version"), ConfigValue("1.8.0"));
278 }
279 if (configVersion.startsWith("1.8") || configVersion.startsWith("1.9.0beta1")) {
280 qDebug() << "Upgrading from" << configVersion << "...";
281 // Upgrade tasks go here
282 #ifdef __APPLE__
283 QString OSXLocation180 = QDir::homePath().append("/").append(".mixxx");
284 QString OSXLocation190 = settingsPath;
285 QDir newOSXDir(OSXLocation190);
286 newOSXDir.mkpath(OSXLocation190);
287
288 QList<QPair<QString, QString> > dirsToMove;
289 dirsToMove.push_back(QPair<QString, QString>(OSXLocation180, OSXLocation190));
290 dirsToMove.push_back(QPair<QString, QString>(OSXLocation180 + "/midi", OSXLocation190 + "midi"));
291 dirsToMove.push_back(QPair<QString, QString>(OSXLocation180 + "/presets", OSXLocation190 + "presets"));
292
293 QListIterator<QPair<QString, QString> > dirIt(dirsToMove);
294 QPair<QString, QString> curPair;
295 while (dirIt.hasNext())
296 {
297 curPair = dirIt.next();
298 qDebug() << "Moving" << curPair.first << "to" << curPair.second;
299 QDir oldSubDir(curPair.first);
300 QDir newSubDir(curPair.second);
301 newSubDir.mkpath(curPair.second); // Create the new destination directory
302
303 QStringList contents = oldSubDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
304 QStringListIterator it(contents);
305 QString cur;
306 // Iterate over all the files in the source directory and copy them to the dest dir.
307 while (it.hasNext())
308 {
309 cur = it.next();
310 QString src = curPair.first + "/" + cur;
311 QString dest = curPair.second + "/" + cur;
312 qDebug() << "Copying" << src << "to" << dest;
313 if (!QFile::copy(src, dest))
314 {
315 qDebug() << "Failed to move file during upgrade.";
316 }
317 }
318
319 // Rename the old directory.
320 newOSXDir.rename(OSXLocation180, OSXLocation180 + "-1.8");
321 }
322 // Reload the configuration file from the new location.
323 // (We want to make sure we save to the new location...)
324 config = UserSettingsPointer(new ConfigObject<ConfigValue>(
325 QDir(settingsPath).filePath(MIXXX_SETTINGS_FILE)));
326 #endif
327 configVersion = "1.9.0";
328 config->set(ConfigKey("[Config]","Version"), ConfigValue("1.9.0"));
329 }
330 if (configVersion.startsWith("1.9") || configVersion.startsWith("1.10")) {
331 qDebug() << "Upgrading from v1.9.x/1.10.x...";
332
333 bool successful = true;
334
335 qDebug() << "Copying midi/ to controllers/";
336 QString midiPath = legacyUserPresetsPath(config);
337 QString controllerPath = userPresetsPath(config);
338 QDir oldDir(midiPath);
339 QDir newDir(controllerPath);
340 newDir.mkpath(controllerPath); // create the new directory
341
342 QStringList contents = oldDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
343 QStringListIterator it(contents);
344 QString cur;
345 // Iterate over all the files in the source directory and copy them to the dest dir.
346 while (it.hasNext()) {
347 cur = it.next();
348 if (newDir.exists(cur)) {
349 qDebug() << cur << "already exists in"
350 << controllerPath << "Skipping.";
351 continue;
352 }
353 QString src = oldDir.absoluteFilePath(cur);
354 QString dest = newDir.absoluteFilePath(cur);
355 qDebug() << "Copying" << src << "to" << dest;
356 if (!QFile::copy(src, dest)) {
357 qDebug() << "Failed to copy file during upgrade.";
358 successful = false;
359 }
360 }
361
362 bool reanalyze_choice = askReanalyzeBeats();
363 BeatDetectionSettings bpmSettings(config);
364 bpmSettings.setReanalyzeWhenSettingsChange(reanalyze_choice);
365
366 if (successful) {
367 qDebug() << "Upgrade Successful";
368 configVersion = "1.11.0";
369 config->set(ConfigKey("[Config]","Version"),
370 ConfigValue(configVersion));
371 } else {
372 qDebug() << "Upgrade Failed";
373 }
374 }
375
376 if (configVersion.startsWith("1.11")) {
377 qDebug() << "Upgrading from v1.11.x...";
378 bool successful = false;
379 {
380 MixxxDb mixxxDb(config);
381 const mixxx::DbConnectionPooler dbConnectionPooler(
382 mixxxDb.connectionPool());
383 if (dbConnectionPooler.isPooling()) {
384 QSqlDatabase dbConnection = mixxx::DbConnectionPooled(mixxxDb.connectionPool());
385 DEBUG_ASSERT(dbConnection.isOpen());
386 if (MixxxDb::initDatabaseSchema(dbConnection)) {
387 TrackCollection tc(config);
388 tc.connectDatabase(dbConnection);
389
390 // upgrade to the multi library folder settings
391 QString currentFolder = config->getValueString(PREF_LEGACY_LIBRARY_DIR);
392 // to migrate the DB just add the current directory to the new
393 // directories table
394 // NOTE(rryan): We don't have to ask for sandbox permission to this
395 // directory because the normal startup integrity check in Library will
396 // notice if we don't have permission and ask for access. Also, the
397 // Sandbox isn't setup yet at this point in startup because it relies on
398 // the config settings path and this function is what loads the config
399 // so it's not ready yet.
400 successful = tc.addDirectory(currentFolder);
401
402 tc.disconnectDatabase();
403 }
404 }
405 }
406
407 // ask for library rescan to activate cover art. We can later ask for
408 // this variable when the library scanner is constructed.
409 m_bRescanLibrary = askReScanLibrary();
410
411 // Versions of mixxx until 1.11 had a hack that multiplied gain by 1/2,
412 // which was compensation for another hack that set replaygain to a
413 // default of 6. We've now removed all of the hacks, so subtracting
414 // 6 from everyone's replay gain should keep things consistent for
415 // all users.
416 int oldReplayGain = config->getValue(
417 ConfigKey("[ReplayGain]", "InitialReplayGainBoost"), 6);
418 int newReplayGain = math_max(-6, oldReplayGain - 6);
419 config->set(ConfigKey("[ReplayGain]", "InitialReplayGainBoost"),
420 ConfigValue(newReplayGain));
421
422 // if everything until here worked fine we can mark the configuration as
423 // updated
424 if (successful) {
425 configVersion = VersionStore::version();
426 config->set(ConfigKey("[Config]", "Version"), ConfigValue(VersionStore::version()));
427 }
428 else {
429 qDebug() << "Upgrade failed!\n";
430 }
431 }
432
433 if (configVersion.startsWith("1.12") ||
434 configVersion.startsWith("2.0") ||
435 configVersion.startsWith("2.1.0")) {
436 // No special upgrade required, just update the value.
437 configVersion = VersionStore::version();
438 config->set(ConfigKey("[Config]", "Version"), ConfigValue(VersionStore::version()));
439 }
440
441 if (configVersion == VersionStore::version()) {
442 qDebug() << "Configuration file is now at the current version" << VersionStore::version();
443 } else {
444 qWarning() << "Configuration file is at version" << configVersion
445 << "instead of the current" << VersionStore::version();
446 }
447
448 return config;
449 }
450
askReScanLibrary()451 bool Upgrade::askReScanLibrary() {
452 QMessageBox msgBox;
453 msgBox.setIconPixmap(QPixmap(":/images/icons/mixxx.svg"));
454 msgBox.setWindowTitle(QMessageBox::tr("Upgrading Mixxx"));
455 msgBox.setText(QMessageBox::tr("Mixxx now supports displaying cover art.\n"
456 "Do you want to scan your library for cover files now?"));
457 QPushButton* rescanButton = msgBox.addButton(
458 QMessageBox::tr("Scan"), QMessageBox::AcceptRole);
459 msgBox.addButton(QMessageBox::tr("Later"), QMessageBox::RejectRole);
460 msgBox.setDefaultButton(rescanButton);
461 msgBox.exec();
462
463 return msgBox.clickedButton() == rescanButton;
464 }
465
askReanalyzeBeats()466 bool Upgrade::askReanalyzeBeats() {
467 QString windowTitle =
468 QMessageBox::tr("Upgrading Mixxx from v1.9.x/1.10.x.");
469 QString mainHeading =
470 QMessageBox::tr("Mixxx has a new and improved beat detector.");
471 QString paragraph1 = QMessageBox::tr(
472 "When you load tracks, Mixxx can re-analyze them "
473 "and generate new, more accurate beatgrids. This will make "
474 "automatic beatsync and looping more reliable.");
475 QString paragraph2 = QMessageBox::tr(
476 "This does not affect saved cues, hotcues, playlists, or crates.");
477 QString paragraph3 = QMessageBox::tr(
478 "If you do not want Mixxx to re-analyze your tracks, choose "
479 "\"Keep Current Beatgrids\". You can change this setting at any time "
480 "from the \"Beat Detection\" section of the Preferences.");
481 QString keepCurrent = QMessageBox::tr("Keep Current Beatgrids");
482 QString generateNew = QMessageBox::tr("Generate New Beatgrids");
483
484 QMessageBox msgBox;
485 msgBox.setIconPixmap(QPixmap(":/images/icons/mixxx.svg"));
486 msgBox.setWindowTitle(windowTitle);
487 msgBox.setText(QString("<html><h2>%1</h2><p>%2</p><p>%3</p><p>%4</p></html>")
488 .arg(mainHeading, paragraph1, paragraph2, paragraph3));
489 msgBox.addButton(keepCurrent, QMessageBox::NoRole);
490 QPushButton* OverwriteButton = msgBox.addButton(
491 generateNew, QMessageBox::YesRole);
492 msgBox.setDefaultButton(OverwriteButton);
493 msgBox.exec();
494
495 if (msgBox.clickedButton() == (QAbstractButton*)OverwriteButton) {
496 return true;
497 }
498 return false;
499 }
500