qBittorrent
settingsstorage.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
4  * Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program 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 this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give permission to
21  * link this program with the OpenSSL project's "OpenSSL" library (or with
22  * modified versions of it that use the same license as the "OpenSSL" library),
23  * and distribute the linked executables. You must obey the GNU General Public
24  * License in all respects for all of the code used other than "OpenSSL". If you
25  * modify file(s), you may extend this exception to your version of the file(s),
26  * but you are not obligated to do so. If you do not wish to do so, delete this
27  * exception statement from your version.
28  */
29 
30 #include "settingsstorage.h"
31 
32 #include <memory>
33 #include <QFile>
34 #include <QHash>
35 
36 #include "global.h"
37 #include "logger.h"
38 #include "profile.h"
39 #include "utils/fs.h"
40 
41 namespace
42 {
43  // Encapsulates serialization of settings in "atomic" way.
44  // write() does not leave half-written files,
45  // read() has a workaround for a case of power loss during a previous serialization
47  {
48  public:
49  explicit TransactionalSettings(const QString &name)
50  : m_name(name)
51  {
52  }
53 
54  QVariantHash read() const;
55  bool write(const QVariantHash &data) const;
56 
57  private:
58  // we return actual file names used by QSettings because
59  // there is no other way to get that name except
60  // actually create a QSettings object.
61  // if serialization operation was not successful we return empty string
62  QString deserialize(const QString &name, QVariantHash &data) const;
63  QString serialize(const QString &name, const QVariantHash &data) const;
64 
65  const QString m_name;
66  };
67 }
68 
70 
72  : m_data {TransactionalSettings(QLatin1String("qBittorrent")).read()}
73 {
74  m_timer.setSingleShot(true);
75  m_timer.setInterval(5 * 1000);
76  connect(&m_timer, &QTimer::timeout, this, &SettingsStorage::save);
77 }
78 
80 {
81  save();
82 }
83 
85 {
86  if (!m_instance)
88 }
89 
91 {
92  delete m_instance;
93  m_instance = nullptr;
94 }
95 
97 {
98  return m_instance;
99 }
100 
102 {
103  const QWriteLocker locker(&m_lock); // guard for `m_dirty` too
104  if (!m_dirty) return true;
105 
106  const TransactionalSettings settings(QLatin1String("qBittorrent"));
107  if (!settings.write(m_data))
108  {
109  m_timer.start();
110  return false;
111  }
112 
113  m_dirty = false;
114  return true;
115 }
116 
117 QVariant SettingsStorage::loadValueImpl(const QString &key, const QVariant &defaultValue) const
118 {
119  const QReadLocker locker(&m_lock);
120  return m_data.value(key, defaultValue);
121 }
122 
123 void SettingsStorage::storeValueImpl(const QString &key, const QVariant &value)
124 {
125  const QWriteLocker locker(&m_lock);
126  QVariant &currentValue = m_data[key];
127  if (currentValue != value)
128  {
129  m_dirty = true;
130  currentValue = value;
131  m_timer.start();
132  }
133 }
134 
135 void SettingsStorage::removeValue(const QString &key)
136 {
137  const QWriteLocker locker(&m_lock);
138 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
139  if (m_data.remove(key))
140 #else
141  if (m_data.remove(key) > 0)
142 #endif
143  {
144  m_dirty = true;
145  m_timer.start();
146  }
147 }
148 
149 bool SettingsStorage::hasKey(const QString &key) const
150 {
151  const QReadLocker locker {&m_lock};
152  return m_data.contains(key);
153 }
154 
155 QVariantHash TransactionalSettings::read() const
156 {
157  QVariantHash res;
158 
159  const QString newPath = deserialize(m_name + QLatin1String("_new"), res);
160  if (!newPath.isEmpty())
161  { // "_new" file is NOT empty
162  // This means that the PC closed either due to power outage
163  // or because the disk was full. In any case the settings weren't transferred
164  // in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf
165  // contains the most recent settings.
166  Logger::instance()->addMessage(QObject::tr("Detected unclean program exit. Using fallback file to restore settings: %1")
167  .arg(Utils::Fs::toNativePath(newPath))
168  , Log::WARNING);
169 
170  QString finalPath = newPath;
171  int index = finalPath.lastIndexOf("_new", -1, Qt::CaseInsensitive);
172  finalPath.remove(index, 4);
173 
174  Utils::Fs::forceRemove(finalPath);
175  QFile::rename(newPath, finalPath);
176  }
177  else
178  {
179  deserialize(m_name, res);
180  }
181 
182  return res;
183 }
184 
185 bool TransactionalSettings::write(const QVariantHash &data) const
186 {
187  // QSettings deletes the file before writing it out. This can result in problems
188  // if the disk is full or a power outage occurs. Those events might occur
189  // between deleting the file and recreating it. This is a safety measure.
190  // Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds
191  // replace qBittorrent.ini/qBittorrent.conf with it.
192  const QString newPath = serialize(m_name + QLatin1String("_new"), data);
193  if (newPath.isEmpty())
194  {
195  Utils::Fs::forceRemove(newPath);
196  return false;
197  }
198 
199  QString finalPath = newPath;
200  int index = finalPath.lastIndexOf("_new", -1, Qt::CaseInsensitive);
201  finalPath.remove(index, 4);
202 
203  Utils::Fs::forceRemove(finalPath);
204  return QFile::rename(newPath, finalPath);
205 }
206 
207 QString TransactionalSettings::deserialize(const QString &name, QVariantHash &data) const
208 {
210 
211  if (settings->allKeys().isEmpty())
212  return {};
213 
214  // Copy everything into memory. This means even keys inserted in the file manually
215  // or that we don't touch directly in this code (eg disabled by ifdef). This ensures
216  // that they will be copied over when save our settings to disk.
217  for (const QString &key : asConst(settings->allKeys()))
218  {
219  const QVariant value = settings->value(key);
220  if (value.isValid())
221  data[key] = value;
222  }
223 
224  return settings->fileName();
225 }
226 
227 QString TransactionalSettings::serialize(const QString &name, const QVariantHash &data) const
228 {
230  for (auto i = data.begin(); i != data.end(); ++i)
231  settings->setValue(i.key(), i.value());
232 
233  settings->sync(); // Important to get error status
234 
235  switch (settings->status())
236  {
237  case QSettings::NoError:
238  return settings->fileName();
239  case QSettings::AccessError:
240  Logger::instance()->addMessage(QObject::tr("An access error occurred while trying to write the configuration file."), Log::CRITICAL);
241  break;
242  case QSettings::FormatError:
243  Logger::instance()->addMessage(QObject::tr("A format error occurred while trying to write the configuration file."), Log::CRITICAL);
244  break;
245  default:
246  Logger::instance()->addMessage(QObject::tr("An unknown error occurred while trying to write the configuration file."), Log::CRITICAL);
247  break;
248  }
249  return {};
250 }
void addMessage(const QString &message, const Log::MsgType &type=Log::NORMAL)
Definition: logger.cpp:73
static Logger * instance()
Definition: logger.cpp:56
SettingsPtr applicationSettings(const QString &name) const
Definition: profile.cpp:109
static const Profile * instance()
Definition: profile.cpp:67
QReadWriteLock m_lock
static SettingsStorage * instance()
void storeValueImpl(const QString &key, const QVariant &value)
void removeValue(const QString &key)
static SettingsStorage * m_instance
static void freeInstance()
static void initInstance()
bool hasKey(const QString &key) const
QVariantHash m_data
QVariant loadValueImpl(const QString &key, const QVariant &defaultValue={}) const
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
@ WARNING
Definition: logger.h:47
@ CRITICAL
Definition: logger.h:48
bool forceRemove(const QString &filePath)
Definition: fs.cpp:173
QString toNativePath(const QString &path)
Definition: fs.cpp:64
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
std::unique_ptr< QSettings > SettingsPtr
Definition: profile.h:44
QVariantMap serialize(const BitTorrent::Torrent &torrent)