1 /*
2     Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
3 
4     This file is part of CopyQ.
5 
6     CopyQ is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     CopyQ is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with CopyQ.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "settings.h"
21 
22 #include "common/common.h"
23 #include "common/config.h"
24 #include "common/log.h"
25 
26 #include <QCoreApplication>
27 #include <QDateTime>
28 #include <QFileInfo>
29 #include <QStringList>
30 
31 namespace {
32 
needsUpdate(const Settings & newSettings,const QSettings & oldSettings)33 bool needsUpdate(const Settings &newSettings, const QSettings &oldSettings)
34 {
35     if ( Settings::isEmpty(oldSettings) )
36         return false;
37 
38     const QFileInfo newFile(newSettings.fileName());
39     const QFileInfo oldFile(oldSettings.fileName());
40 
41     return newFile.size() != oldFile.size()
42             || newFile.lastModified() < oldFile.lastModified();
43 }
44 
copySettings(const QSettings & from,QSettings * to)45 void copySettings(const QSettings &from, QSettings *to)
46 {
47     Q_ASSERT(from.group().isEmpty());
48     Q_ASSERT(to->group().isEmpty());
49 
50     to->clear();
51 
52     for ( const auto &key : from.allKeys() )
53         to->setValue(key, from.value(key));
54 
55     to->sync();
56 }
57 
lockFileName(const QString & path)58 QString lockFileName(const QString &path)
59 {
60     if ( path.isEmpty() )
61         return getConfigurationFilePath(".bad");
62     return path + ".bad";
63 }
64 
isLastSaveUnfinished(const QString & path)65 bool isLastSaveUnfinished(const QString &path)
66 {
67     return QFile::exists(lockFileName(path));
68 }
69 
beginSave(const QString & path)70 void beginSave(const QString &path)
71 {
72     QFile lockFile(lockFileName(path));
73     lockFile.open(QIODevice::WriteOnly);
74 }
75 
endSave(const QString & path)76 void endSave(const QString &path)
77 {
78     QFile::remove(lockFileName(path));
79 }
80 
81 } // namespace
82 
83 bool Settings::canModifySettings = false;
84 
isEmpty(const QSettings & settings)85 bool Settings::isEmpty(const QSettings &settings)
86 {
87     return settings.childGroups().isEmpty();
88 }
89 
Settings()90 Settings::Settings()
91     : m_settings(
92           QSettings::defaultFormat(),
93           QSettings::UserScope,
94           QCoreApplication::organizationName(),
95           QCoreApplication::applicationName() + "-bak" )
96     , m_changed(false)
97 {
98 }
99 
Settings(const QString & path)100 Settings::Settings(const QString &path)
101     : m_settings(path + ".bak", QSettings::IniFormat)
102     , m_path(path)
103 {
104 }
105 
~Settings()106 Settings::~Settings()
107 {
108     // Only main application is allowed to change settings.
109     if (canModifySettings && m_changed) {
110         m_settings.sync();
111 
112         beginSave(m_path);
113 
114         while ( !m_settings.group().isEmpty() )
115             m_settings.endGroup();
116 
117         save();
118 
119         endSave(m_path);
120     }
121 }
122 
restore()123 void Settings::restore()
124 {
125     if ( isLastSaveUnfinished(m_path) ) {
126         log("Restoring application settings", LogWarning);
127 
128         if ( isEmpty() )
129             log("Cannot restore application settings", LogError);
130         else
131             save();
132 
133         endSave(m_path);
134     } else if ( m_path.isEmpty() ) {
135         restore( QSettings() );
136     } else {
137         restore( QSettings(m_path, QSettings::IniFormat) );
138     }
139 }
140 
restore(const QSettings & settings)141 void Settings::restore(const QSettings &settings)
142 {
143     if ( needsUpdate(*this, settings) )
144         copySettings(settings, &m_settings);
145 }
146 
save()147 void Settings::save()
148 {
149     if ( m_path.isEmpty() ) {
150         QSettings settings;
151         copySettings(m_settings, &settings);
152     } else {
153         QSettings settings(m_path, QSettings::IniFormat);
154         copySettings(m_settings, &settings);
155     }
156 }
157