1 /* BEGIN_COMMON_COPYRIGHT_HEADER
2  * (c)LGPL2+
3  *
4  * Flacon - audio File Encoder
5  * https://github.com/flacon/flacon
6  *
7  * Copyright: 2012-2013
8  *   Alexander Sokoloff <sokoloff.a@gmail.com>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14 
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19 
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23  *
24  * END_COMMON_COPYRIGHT_HEADER */
25 
26 #include "types.h"
27 #include "formats_in/informat.h"
28 #include "settings.h"
29 #include "inputaudiofile.h"
30 #include "formats_out/outformat.h"
31 #include "converter/sox.h"
32 
33 #include <assert.h>
34 #include <QtGlobal>
35 #include <QDir>
36 #include <QDebug>
37 #include <QProcessEnvironment>
38 #include <QDir>
39 #include <QStandardPaths>
40 #include <QCoreApplication>
41 #include <QMetaEnum>
42 
43 #ifdef Q_OS_WIN
44 #define PATH_ENV_SEPARATOR ';'
45 #define BINARY_EXT ".exe"
46 
47 #elif defined(Q_OS_OS2)
48 #define PATH_ENV_SEPARATOR ';'
49 #define BINARY_EXT ".exe"
50 
51 #else
52 #define PATH_ENV_SEPARATOR ':'
53 #define BINARY_EXT ""
54 
55 #endif
56 
57 #define PROFILES_PREFIX "Profiles"
58 
59 QString   Settings::mFileName;
60 Settings *Settings::mInstance = nullptr;
61 
62 /************************************************
63  * Added on 6.0.0 release, remove after 8.0.0 release
64  ************************************************/
migrateKey(Settings * settings,const QString & oldKey,const QString & newKey)65 static void migrateKey(Settings *settings, const QString &oldKey, const QString &newKey)
66 {
67     if (settings->contains(oldKey) && !settings->contains(newKey))
68         settings->setValue(newKey, settings->value(oldKey));
69 }
70 
71 /************************************************
72  * Added on 6.0.0 release, remove after 8.0.0 release
73  ************************************************/
migrateProfile(Settings * settings,const QString & formatId)74 static void migrateProfile(Settings *settings, const QString &formatId)
75 {
76     const OutFormat *format = OutFormat::formatForId(formatId);
77     assert(format != nullptr);
78     if (!format)
79         return;
80 
81     QString group = QString("%1/%2/").arg(PROFILES_PREFIX).arg(format->id());
82     settings->setValue(group + "Format", format->id());
83     settings->setValue(group + "Name", format->name());
84 
85     if (format->id() == "AAC") {
86         migrateKey(settings, "Aac/UseQuality", group + "UseQuality");
87         migrateKey(settings, "Aac/Quality", group + "Quality");
88         migrateKey(settings, "Aac/Bitrate", group + "Bitrate");
89     }
90 
91     if (format->id() == "FLAC") {
92         migrateKey(settings, "Flac/Compression", group + "Compression");
93         migrateKey(settings, "Flac/ReplayGain", group + "ReplayGain");
94     }
95 
96     if (format->id() == "MP3") {
97         migrateKey(settings, "Mp3/Preset", group + "Preset");
98         migrateKey(settings, "Mp3/Bitrate", group + "Bitrate");
99         migrateKey(settings, "Mp3/Quality", group + "Quality");
100         migrateKey(settings, "Mp3/ReplayGain", group + "ReplayGain");
101 
102         // Replace vbrStandardFast to vbrStandard
103         if (settings->value(group + "Preset", "") == "vbrStandardFast") {
104             settings->setValue(group + "Preset", "vbrStandard");
105         }
106 
107         // Replace vbrExtremeFast to vbrExtreme
108         if (settings->value(group + "Preset", "") == "vbrExtremeFast") {
109             settings->setValue(group + "Preset", "vbrExtreme");
110         }
111     }
112 
113     if (format->id() == "OGG") {
114         migrateKey(settings, "Ogg/UseQuality", group + "UseQuality");
115         migrateKey(settings, "Ogg/Quality", group + "Quality");
116         migrateKey(settings, "Ogg/MinBitrate", group + "MinBitrate");
117         migrateKey(settings, "Ogg/NormBitrate", group + "NormBitrate");
118         migrateKey(settings, "Ogg/MaxBitrate", group + "MaxBitrate");
119         migrateKey(settings, "Ogg/ReplayGain", group + "ReplayGain");
120     }
121 
122     if (format->id() == "OPUS") {
123         migrateKey(settings, "Opus/BitrateType", group + "BitrateType");
124         migrateKey(settings, "Opus/Bitrate", group + "Bitrate");
125     }
126 
127     if (format->id() == "WAV") {
128         // No options
129     }
130 
131     if (format->id() == "WV") {
132         migrateKey(settings, "WV/Compression", group + "Compression");
133         migrateKey(settings, "WV/ReplayGain", group + "ReplayGain");
134     }
135 
136     migrateKey(settings, "OutFiles/Directory", group + Profile::OUT_DIRECTORY_KEY);
137     migrateKey(settings, "OutFiles/Pattern", group + Profile::OUT_PATTERN_KEY);
138 
139     migrateKey(settings, "Resample/BitsPerSample", group + Profile::BITS_PER_SAMPLE_KEY);
140     migrateKey(settings, "Resample/SampleRate", group + Profile::SAMPLE_RATE_KEY);
141 
142     migrateKey(settings, "PerTrackCue/Create", group + Profile::CREATE_CUE_KEY);
143     migrateKey(settings, "PerTrackCue/FileName", group + Profile::CUE_FILE_NAME_KEY);
144     migrateKey(settings, "PerTrackCue/Pregap", group + Profile::PREGAP_TYPE_KEY);
145 }
146 
147 /************************************************
148  * Added on 6.0.0 release, remove after 8.0.0 release
149  ************************************************/
migrateProfiles(Settings * settings)150 static void migrateProfiles(Settings *settings)
151 {
152     settings->allKeys();
153     if (!settings->childGroups().contains(PROFILES_PREFIX)) {
154 
155         migrateProfile(settings, "WAV");
156         migrateProfile(settings, "AAC");
157         migrateProfile(settings, "FLAC");
158         migrateProfile(settings, "MP3");
159         migrateProfile(settings, "OGG");
160         migrateProfile(settings, "OPUS");
161         migrateProfile(settings, "WAV");
162         migrateProfile(settings, "WV");
163     }
164 
165     if (settings->contains("OutFiles/Format") && !settings->contains("OutFiles/Profile"))
166         settings->setValue("OutFiles/Profile", settings->value("OutFiles/Format"));
167 }
168 
169 /************************************************
170 
171  ************************************************/
i()172 Settings *Settings::i()
173 {
174     if (!mInstance) {
175         if (mFileName.isEmpty())
176             mInstance = new Settings("flacon", "flacon");
177         else
178             mInstance = new Settings(mFileName);
179     }
180 
181     return mInstance;
182 }
183 
184 /************************************************
185 
186  ************************************************/
setFileName(const QString & fileName)187 void Settings::setFileName(const QString &fileName)
188 {
189     mFileName = fileName;
190     delete mInstance;
191     mInstance = nullptr;
192 }
193 
194 /************************************************
195 
196  ************************************************/
Settings(const QString & organization,const QString & application)197 Settings::Settings(const QString &organization, const QString &application) :
198     QSettings(organization, application)
199 {
200     setIniCodec("UTF-8");
201     init();
202 }
203 
204 /************************************************
205 
206  ************************************************/
Settings(const QString & fileName)207 Settings::Settings(const QString &fileName) :
208     QSettings(fileName, QSettings::IniFormat)
209 {
210     setIniCodec("UTF-8");
211     init();
212 }
213 
214 /************************************************
215 
216  ************************************************/
init()217 void Settings::init()
218 {
219     migrateProfiles(this);
220     if (value(Inet_CDDBHost) == "https://gnudb.gnudb.org/")
221         remove("Inet/CDDBHost");
222 
223     setDefaultValue(Tags_DefaultCodepage, "AUTODETECT");
224 
225     // Globals **********************************
226     setDefaultValue(Encoder_ThreadCount, 8);
227     setDefaultValue(Encoder_TmpDir, "");
228 
229     // Out Files ********************************
230     setDefaultValue(OutFiles_Profile, "FLAC");
231 
232     // Internet *********************************
233     setDefaultValue(Inet_CDDBHost, "https://gnudb.gnudb.org/");
234 
235     // Misc *************************************
236     setDefaultValue(Misc_LastDir, QDir::homePath());
237 
238     // Cover image **************************
239     setDefaultValue(Cover_Mode, coverModeToString(CoverMode::Scale));
240     setDefaultValue(Cover_Size, 500);
241 
242     // Embedded Cover image ******************
243     setDefaultValue(EmbeddedCover_Mode, coverModeToString(CoverMode::Disable));
244     setDefaultValue(EmbeddedCover_Size, 500);
245 
246     // ConfigureDialog **********************
247     setDefaultValue(ConfigureDialog_Width, 645);
248     setDefaultValue(ConfigureDialog_Height, 425);
249 
250     mPrograms << Conv::Sox::programName();
251 
252     foreach (OutFormat *format, OutFormat::allFormats()) {
253         mPrograms << format->encoderProgramName();
254         mPrograms << format->gainProgramName();
255     }
256 
257     foreach (const InputFormat *format, InputFormat::allFormats()) {
258         mPrograms << format->decoderProgramName();
259     }
260 
261     mPrograms.remove("");
262 
263     foreach (QString program, mPrograms) {
264         if (!checkProgram(program))
265             setValue("Programs/" + program, findProgram(program));
266     }
267 
268     if (!childGroups().contains(PROFILES_PREFIX)) {
269         foreach (OutFormat *format, OutFormat::allFormats()) {
270             QString group = QString("%1/%2/").arg(PROFILES_PREFIX, format->id());
271             setDefaultValue(group + "Format", format->id());
272             setDefaultValue(group + "Name", format->name());
273         }
274     }
275 }
276 
277 /************************************************
278 
279  ************************************************/
keyToString(Settings::Key key) const280 QString Settings::keyToString(Settings::Key key) const
281 {
282     switch (key) {
283         case Tags_DefaultCodepage:
284             return "Tags/DefaultCodepage";
285 
286         // MainWindow **************************
287         case MainWindow_Width:
288             return "MainWindow/Width";
289         case MainWindow_Height:
290             return "MainWindow/Height";
291 
292         // Globals *****************************
293         case Encoder_ThreadCount:
294             return "Encoder/ThreadCount";
295         case Encoder_TmpDir:
296             return "Encoder/TmpDir";
297 
298         // Out Files ***************************
299         case OutFiles_Profile:
300             return "OutFiles/Profile";
301         case OutFiles_PatternHistory:
302             return "OutFiles/PatternHistory";
303         case OutFiles_DirectoryHistory:
304             return "OutFiles/DirectoryHistory";
305 
306         // Internet ****************************
307         case Inet_CDDBHost:
308             return "Inet/CDDBHost";
309 
310         // Misc *********************************
311         case Misc_LastDir:
312             return "Misc/LastDirectory";
313 
314         // ConfigureDialog **********************
315         case ConfigureDialog_Width:
316             return "ConfigureDialog/Width";
317         case ConfigureDialog_Height:
318             return "ConfigureDialog/Height";
319 
320         // Cover image **************************
321         case Cover_Mode:
322             return "Cover/Mode";
323         case Cover_Size:
324             return "Cover/Size";
325 
326         // Embedded Cover image ******************
327         case EmbeddedCover_Mode:
328             return "EmbeddedCover/Mode";
329         case EmbeddedCover_Size:
330             return "EmbeddedCover/Size";
331     }
332 
333     assert(false);
334     return "";
335 }
336 
337 /************************************************
338 
339  ************************************************/
~Settings()340 Settings::~Settings()
341 {
342 }
343 
344 /************************************************
345 
346  ************************************************/
value(Key key,const QVariant & defaultValue) const347 QVariant Settings::value(Key key, const QVariant &defaultValue) const
348 {
349     return value(keyToString(key), defaultValue);
350 }
351 
352 /************************************************
353 
354  ************************************************/
value(const QString & key,const QVariant & defaultValue) const355 QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
356 {
357     return QSettings::value(key, defaultValue);
358 }
359 
360 /************************************************
361  *
362  ************************************************/
groups(const QString & parentGroup) const363 QStringList Settings::groups(const QString &parentGroup) const
364 {
365     QStringList res;
366     for (const QString &key : allKeys()) {
367         if (key.startsWith(parentGroup)) {
368             res << key.section("/", 1, 1);
369         }
370     }
371     res.removeDuplicates();
372     return res;
373 }
374 
375 /************************************************
376 
377  ************************************************/
checkProgram(const QString & program) const378 bool Settings::checkProgram(const QString &program) const
379 {
380     QString val = programName(program);
381 
382     if (val.isEmpty())
383         return false;
384 
385     QFileInfo fi(val);
386     return fi.exists() && fi.isExecutable();
387 }
388 
389 /************************************************
390 
391  ************************************************/
programName(const QString & program) const392 QString Settings::programName(const QString &program) const
393 {
394 #ifdef MAC_BUNDLE
395     return QDir(qApp->applicationDirPath()).absoluteFilePath(program);
396 #else
397     return value("Programs/" + program).toString();
398 #endif
399 }
400 
401 /************************************************
402 
403  ************************************************/
findProgram(const QString & program) const404 QString Settings::findProgram(const QString &program) const
405 {
406     QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH").split(PATH_ENV_SEPARATOR);
407     foreach (QString path, paths) {
408         QFileInfo fi(path + QDir::separator() + program + BINARY_EXT);
409         if (fi.exists() && fi.isExecutable())
410             return fi.absoluteFilePath();
411     }
412     return "";
413 }
414 
415 /************************************************
416  *
417  ************************************************/
outFormat() const418 OutFormat *Settings::outFormat() const
419 {
420     OutFormat *format = OutFormat::formatForId(currentProfile().formatId());
421     if (format)
422         return format;
423 
424     return OutFormat::allFormats().first();
425 }
426 
427 /************************************************
428  *
429  ************************************************/
tmpDir() const430 QString Settings::tmpDir() const
431 {
432     return value(Encoder_TmpDir).toString();
433 }
434 
435 /************************************************
436  *
437  ************************************************/
setTmpDir(const QString & value)438 void Settings::setTmpDir(const QString &value)
439 {
440     setValue(Encoder_TmpDir, value);
441 }
442 
443 /************************************************
444 
445  ************************************************/
defaultCodepage() const446 QString Settings::defaultCodepage() const
447 {
448     return value(Tags_DefaultCodepage).toString();
449 }
450 
451 /************************************************
452 
453  ************************************************/
setDefaultCodepage(const QString & value)454 void Settings::setDefaultCodepage(const QString &value)
455 {
456     setValue(Tags_DefaultCodepage, value);
457 }
458 
459 /************************************************
460  *
461  ************************************************/
coverMode() const462 CoverMode Settings::coverMode() const
463 {
464     return strToCoverMode(value(Cover_Mode).toString());
465 }
466 
467 /************************************************
468  *
469  ************************************************/
setCoverMode(CoverMode value)470 void Settings::setCoverMode(CoverMode value)
471 {
472     setValue(Cover_Mode, coverModeToString(value));
473 }
474 
475 /************************************************
476  *
477  ************************************************/
coverImageSize() const478 int Settings::coverImageSize() const
479 {
480     return value(Cover_Size).toInt();
481 }
482 
483 /************************************************
484 
485 ************************************************/
setCoverImageSize(int value)486 void Settings::setCoverImageSize(int value)
487 {
488     setValue(Cover_Size, value);
489 }
490 
491 /************************************************
492 
493 ************************************************/
embeddedCoverMode() const494 CoverMode Settings::embeddedCoverMode() const
495 {
496     return strToCoverMode(value(EmbeddedCover_Mode).toString());
497 }
498 
499 /************************************************
500 
501  ************************************************/
setEmbeddedCoverMode(CoverMode value)502 void Settings::setEmbeddedCoverMode(CoverMode value)
503 {
504     setValue(EmbeddedCover_Mode, coverModeToString(value));
505 }
506 
507 /************************************************
508 
509  ************************************************/
embeddedCoverImageSize() const510 int Settings::embeddedCoverImageSize() const
511 {
512     return value(EmbeddedCover_Size).toInt();
513 }
514 
515 /************************************************
516 
517  ************************************************/
setEmbeddedCoverImageSize(int value)518 void Settings::setEmbeddedCoverImageSize(int value)
519 {
520     setValue(EmbeddedCover_Size, value);
521 }
522 
523 /************************************************
524 
525  ************************************************/
setValue(Settings::Key key,const QVariant & value)526 void Settings::setValue(Settings::Key key, const QVariant &value)
527 {
528     setValue(keyToString(key), value);
529     emit changed();
530 }
531 
532 /************************************************
533 
534  ************************************************/
setValue(const QString & key,const QVariant & value)535 void Settings::setValue(const QString &key, const QVariant &value)
536 {
537     QSettings::setValue(key, value);
538     emit changed();
539 }
540 
541 /************************************************
542 
543  ************************************************/
setDefaultValue(Key key,const QVariant & defaultValue)544 void Settings::setDefaultValue(Key key, const QVariant &defaultValue)
545 {
546     setValue(key, value(key, defaultValue));
547 }
548 
549 /************************************************
550 
551  ************************************************/
setDefaultValue(const QString & key,const QVariant & defaultValue)552 void Settings::setDefaultValue(const QString &key, const QVariant &defaultValue)
553 {
554     setValue(key, value(key, defaultValue));
555 }
556 
557 /************************************************
558 
559  ************************************************/
profiles() const560 const Profiles &Settings::profiles() const
561 {
562     if (mProfiles.isEmpty()) {
563         const_cast<Settings *>(this)->loadProfiles();
564     }
565 
566     return mProfiles;
567 }
568 
569 /************************************************
570 
571  ************************************************/
profiles()572 Profiles &Settings::profiles()
573 {
574     if (mProfiles.isEmpty()) {
575         const_cast<Settings *>(this)->loadProfiles();
576     }
577 
578     return mProfiles;
579 }
580 
581 /************************************************
582  *
583  ************************************************/
loadProfiles()584 void Settings::loadProfiles()
585 {
586     QSet<QString> loaded;
587     allKeys();
588     beginGroup(PROFILES_PREFIX);
589     for (QString id : childGroups()) {
590 
591         Profile profile(id);
592         profile.load(*this, id);
593 
594         if (profile.isValid()) {
595             mProfiles << profile;
596             loaded << profile.formatId();
597         }
598     }
599     endGroup();
600 }
601 
602 /************************************************
603  *
604  ************************************************/
setProfiles(const Profiles & profiles)605 void Settings::setProfiles(const Profiles &profiles)
606 {
607     allKeys();
608     beginGroup(PROFILES_PREFIX);
609 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
610     QSet<QString> old = QSet<QString>::fromList(childGroups());
611 #else
612     // After 5.14.0, QT has stated range constructors are available and preferred.
613     // See: https://doc.qt.io/qt-5/qset.html#toList
614     QList<QString> groups = childGroups();
615     QSet<QString>  old    = QSet<QString>(groups.begin(), groups.end());
616 #endif
617 
618     for (const Profile &profile : profiles) {
619         old.remove(profile.id());
620         profile.save(*this, profile.id());
621     }
622 
623     for (const QString &id : old) {
624         remove(id);
625     }
626     endGroup();
627     mProfiles.clear();
628 }
629 
630 /************************************************
631  *
632  ************************************************/
currentProfile() const633 const Profile &Settings::currentProfile() const
634 {
635     int n = profiles().indexOf(value(OutFiles_Profile).toString());
636     if (n > -1) {
637         return profiles()[qMax(0, n)];
638     }
639 
640     return NullProfile();
641 }
642 
643 /************************************************
644  *
645  ************************************************/
currentProfile()646 Profile &Settings::currentProfile()
647 {
648 
649     int n = profiles().indexOf(value(OutFiles_Profile).toString());
650     if (n > -1) {
651         return profiles()[qMax(0, n)];
652     }
653 
654     return NullProfile();
655 }
656 
657 /************************************************
658  *
659  ************************************************/
selectProfile(const QString & profileId)660 bool Settings::selectProfile(const QString &profileId)
661 {
662     if (profiles().indexOf(profileId) < 0)
663         return false;
664 
665     setValue(OutFiles_Profile, profileId);
666     return true;
667 }
668 
669 /************************************************
670  *
671  ************************************************/
encoderThreadsCount() const672 uint Settings::encoderThreadsCount() const
673 {
674     return value(Encoder_ThreadCount).toUInt();
675 }
676 
677 /************************************************
678  *
679  ************************************************/
setEncoderThreadsCount(uint value)680 void Settings::setEncoderThreadsCount(uint value)
681 {
682     setValue(Encoder_ThreadCount, value);
683 }
684 
685 /************************************************
686  *
687  ************************************************/
cddbHost() const688 QString Settings::cddbHost() const
689 {
690     return value(Inet_CDDBHost).toString();
691 }
692 
693 /************************************************
694  *
695  ************************************************/
setCddbHost(const QString & value)696 void Settings::setCddbHost(const QString &value)
697 {
698     setValue(Inet_CDDBHost, value);
699 }
700