Enable customizing the save statistics time interval
[qBittorrent.git] / src / base / utils / misc.cpp
blob8d6e8776510090e0452cec12f4ecd0d1bd08a4d3
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "misc.h"
31 #include <optional>
33 #include <boost/version.hpp>
34 #include <libtorrent/version.hpp>
35 #include <openssl/crypto.h>
36 #include <openssl/opensslv.h>
37 #include <zlib.h>
39 #include <QtAssert>
40 #include <QCoreApplication>
41 #include <QDebug>
42 #include <QLocale>
43 #include <QMimeDatabase>
44 #include <QRegularExpression>
45 #include <QSet>
46 #include <QString>
47 #include <QStringView>
48 #include <QSysInfo>
50 #include "base/path.h"
51 #include "base/unicodestrings.h"
52 #include "base/utils/string.h"
54 namespace
56 const struct { const char *source; const char *comment; } units[] =
58 QT_TRANSLATE_NOOP3("misc", "B", "bytes"),
59 QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"),
60 QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
61 QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"),
62 QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)"),
63 QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"),
64 QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)")
67 // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
68 // use Binary prefix standards from IEC 60027-2
69 // see http://en.wikipedia.org/wiki/Kilobyte
70 // value must be given in bytes
71 // to send numbers instead of strings with suffixes
72 struct SplitToFriendlyUnitResult
74 qreal value;
75 Utils::Misc::SizeUnit unit;
78 std::optional<SplitToFriendlyUnitResult> splitToFriendlyUnit(const qint64 bytes, const int unitThreshold = 1024)
80 if (bytes < 0)
81 return std::nullopt;
83 int i = 0;
84 auto value = static_cast<qreal>(bytes);
86 while ((value >= unitThreshold) && (i < static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
88 value /= 1024;
89 ++i;
91 return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
95 QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
97 const auto &unitString = units[static_cast<int>(unit)];
98 QString ret = QCoreApplication::translate("misc", unitString.source, unitString.comment);
99 if (isSpeed)
100 ret += QCoreApplication::translate("misc", "/s", "per second");
101 return ret;
104 QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed, const int precision)
106 const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
107 if (!result)
108 return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
110 const int digitPrecision = (precision >= 0) ? precision : friendlyUnitPrecision(result->unit);
111 return Utils::String::fromDouble(result->value, digitPrecision)
112 + QChar::Nbsp + unitString(result->unit, isSpeed);
115 QString Utils::Misc::friendlyUnitCompact(const qint64 bytes)
117 // avoid 1000-1023 values, use next larger unit instead
118 const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes, 1000);
119 if (!result)
120 return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
122 int precision = 0; // >= 100
123 if (result->value < 10)
124 precision = 2; // 0 - 9.99
125 if (result->value < 100)
126 precision = 1; // 10 - 99.9
128 return Utils::String::fromDouble(result->value, precision)
129 // use only one character for unit representation
130 + QChar::Nbsp + unitString(result->unit, false)[0];
133 int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
135 // friendlyUnit's number of digits after the decimal point
136 switch (unit)
138 case SizeUnit::Byte:
139 return 0;
140 case SizeUnit::KibiByte:
141 case SizeUnit::MebiByte:
142 return 1;
143 case SizeUnit::GibiByte:
144 return 2;
145 default:
146 return 3;
150 qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
152 for (int i = 0; i < static_cast<int>(unit); ++i)
153 size *= 1024;
154 return size;
157 bool Utils::Misc::isPreviewable(const Path &filePath)
159 const QString mime = QMimeDatabase().mimeTypeForFile(filePath.data(), QMimeDatabase::MatchExtension).name();
161 if (mime.startsWith(u"audio", Qt::CaseInsensitive)
162 || mime.startsWith(u"video", Qt::CaseInsensitive))
164 return true;
167 const QSet<QString> multimediaExtensions =
169 u".3GP"_s,
170 u".AAC"_s,
171 u".AC3"_s,
172 u".AIF"_s,
173 u".AIFC"_s,
174 u".AIFF"_s,
175 u".ASF"_s,
176 u".AU"_s,
177 u".AVI"_s,
178 u".FLAC"_s,
179 u".FLV"_s,
180 u".M3U"_s,
181 u".M4A"_s,
182 u".M4P"_s,
183 u".M4V"_s,
184 u".MID"_s,
185 u".MKV"_s,
186 u".MOV"_s,
187 u".MP2"_s,
188 u".MP3"_s,
189 u".MP4"_s,
190 u".MPC"_s,
191 u".MPE"_s,
192 u".MPEG"_s,
193 u".MPG"_s,
194 u".MPP"_s,
195 u".OGG"_s,
196 u".OGM"_s,
197 u".OGV"_s,
198 u".QT"_s,
199 u".RA"_s,
200 u".RAM"_s,
201 u".RM"_s,
202 u".RMV"_s,
203 u".RMVB"_s,
204 u".SWA"_s,
205 u".SWF"_s,
206 u".TS"_s,
207 u".VOB"_s,
208 u".WAV"_s,
209 u".WMA"_s,
210 u".WMV"_s
212 return multimediaExtensions.contains(filePath.extension().toUpper());
215 QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap, const TimeResolution resolution)
217 if (seconds < 0)
218 return C_INFINITY;
219 if ((maxCap >= 0) && (seconds >= maxCap))
220 return C_INFINITY;
222 if (seconds == 0)
223 return u"0"_s;
225 if (seconds < 60)
227 if (resolution == TimeResolution::Minutes)
228 return QCoreApplication::translate("misc", "< 1m", "< 1 minute");
230 return QCoreApplication::translate("misc", "%1s", "e.g: 10 seconds").arg(QString::number(seconds));
233 qlonglong minutes = (seconds / 60);
234 if (minutes < 60)
235 return QCoreApplication::translate("misc", "%1m", "e.g: 10 minutes").arg(QString::number(minutes));
237 qlonglong hours = (minutes / 60);
238 if (hours < 24)
240 minutes -= (hours * 60);
241 return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3 hours 5 minutes").arg(QString::number(hours), QString::number(minutes));
244 qlonglong days = (hours / 24);
245 if (days < 365)
247 hours -= (days * 24);
248 return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2 days 10 hours").arg(QString::number(days), QString::number(hours));
251 qlonglong years = (days / 365);
252 days -= (years * 365);
253 return QCoreApplication::translate("misc", "%1y %2d", "e.g: 2 years 10 days").arg(QString::number(years), QString::number(days));
256 QString Utils::Misc::languageToLocalizedString(const QStringView localeStr)
258 if (localeStr.startsWith(u"eo", Qt::CaseInsensitive))
260 // QLocale doesn't work with that locale. Esperanto isn't a "real" language.
261 return C_LOCALE_ESPERANTO;
264 if (localeStr.startsWith(u"ltg", Qt::CaseInsensitive))
266 // QLocale doesn't work with that locale.
267 return C_LOCALE_LATGALIAN;
270 const QLocale locale {localeStr};
271 switch (locale.language())
273 case QLocale::Arabic: return C_LOCALE_ARABIC;
274 case QLocale::Armenian: return C_LOCALE_ARMENIAN;
275 case QLocale::Azerbaijani: return C_LOCALE_AZERBAIJANI;
276 case QLocale::Basque: return C_LOCALE_BASQUE;
277 case QLocale::Bulgarian: return C_LOCALE_BULGARIAN;
278 case QLocale::Byelorussian: return C_LOCALE_BYELORUSSIAN;
279 case QLocale::Catalan: return C_LOCALE_CATALAN;
280 case QLocale::Chinese:
281 switch (locale.territory())
283 case QLocale::China: return C_LOCALE_CHINESE_SIMPLIFIED;
284 case QLocale::HongKong: return C_LOCALE_CHINESE_TRADITIONAL_HK;
285 default: return C_LOCALE_CHINESE_TRADITIONAL_TW;
287 case QLocale::Croatian: return C_LOCALE_CROATIAN;
288 case QLocale::Czech: return C_LOCALE_CZECH;
289 case QLocale::Danish: return C_LOCALE_DANISH;
290 case QLocale::Dutch: return C_LOCALE_DUTCH;
291 case QLocale::English:
292 switch (locale.territory())
294 case QLocale::Australia: return C_LOCALE_ENGLISH_AUSTRALIA;
295 case QLocale::UnitedKingdom: return C_LOCALE_ENGLISH_UNITEDKINGDOM;
296 default: return C_LOCALE_ENGLISH;
298 case QLocale::Estonian: return C_LOCALE_ESTONIAN;
299 case QLocale::Finnish: return C_LOCALE_FINNISH;
300 case QLocale::French: return C_LOCALE_FRENCH;
301 case QLocale::Galician: return C_LOCALE_GALICIAN;
302 case QLocale::Georgian: return C_LOCALE_GEORGIAN;
303 case QLocale::German: return C_LOCALE_GERMAN;
304 case QLocale::Greek: return C_LOCALE_GREEK;
305 case QLocale::Hebrew: return C_LOCALE_HEBREW;
306 case QLocale::Hindi: return C_LOCALE_HINDI;
307 case QLocale::Hungarian: return C_LOCALE_HUNGARIAN;
308 case QLocale::Icelandic: return C_LOCALE_ICELANDIC;
309 case QLocale::Indonesian: return C_LOCALE_INDONESIAN;
310 case QLocale::Italian: return C_LOCALE_ITALIAN;
311 case QLocale::Japanese: return C_LOCALE_JAPANESE;
312 case QLocale::Korean: return C_LOCALE_KOREAN;
313 case QLocale::Latvian: return C_LOCALE_LATVIAN;
314 case QLocale::Lithuanian: return C_LOCALE_LITHUANIAN;
315 case QLocale::Malay: return C_LOCALE_MALAY;
316 case QLocale::Mongolian: return C_LOCALE_MONGOLIAN;
317 case QLocale::NorwegianBokmal: return C_LOCALE_NORWEGIAN;
318 case QLocale::Occitan: return C_LOCALE_OCCITAN;
319 case QLocale::Persian: return C_LOCALE_PERSIAN;
320 case QLocale::Polish: return C_LOCALE_POLISH;
321 case QLocale::Portuguese:
322 if (locale.territory() == QLocale::Brazil)
323 return C_LOCALE_PORTUGUESE_BRAZIL;
324 return C_LOCALE_PORTUGUESE;
325 case QLocale::Romanian: return C_LOCALE_ROMANIAN;
326 case QLocale::Russian: return C_LOCALE_RUSSIAN;
327 case QLocale::Serbian: return C_LOCALE_SERBIAN;
328 case QLocale::Slovak: return C_LOCALE_SLOVAK;
329 case QLocale::Slovenian: return C_LOCALE_SLOVENIAN;
330 case QLocale::Spanish: return C_LOCALE_SPANISH;
331 case QLocale::Swedish: return C_LOCALE_SWEDISH;
332 case QLocale::Thai: return C_LOCALE_THAI;
333 case QLocale::Turkish: return C_LOCALE_TURKISH;
334 case QLocale::Ukrainian: return C_LOCALE_UKRAINIAN;
335 case QLocale::Uzbek: return C_LOCALE_UZBEK;
336 case QLocale::Vietnamese: return C_LOCALE_VIETNAMESE;
337 default:
338 const QString lang = QLocale::languageToString(locale.language());
339 qWarning() << "Unrecognized language name: " << lang;
340 return lang;
344 QString Utils::Misc::parseHtmlLinks(const QString &rawText)
346 QString result = rawText;
347 static const QRegularExpression reURL(
348 u"(\\s|^)" // start with whitespace or beginning of line
349 u"("
350 u"(" // case 1 -- URL with scheme
351 u"(http(s?))\\://" // start with scheme
352 u"([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
353 u"([a-zA-Z0-9\\?%=&/_\\.:#;-]+)" // everything to 1st non-URI char, must be at least one char after the previous dot (cannot use ".*" because it can be too greedy)
354 u")"
355 u"|"
356 u"(" // case 2a -- no scheme, contains common TLD example.com
357 u"([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
358 u"(?=" // must be followed by TLD
359 u"AERO|aero|" // N.B. assertions are non-capturing
360 u"ARPA|arpa|"
361 u"ASIA|asia|"
362 u"BIZ|biz|"
363 u"CAT|cat|"
364 u"COM|com|"
365 u"COOP|coop|"
366 u"EDU|edu|"
367 u"GOV|gov|"
368 u"INFO|info|"
369 u"INT|int|"
370 u"JOBS|jobs|"
371 u"MIL|mil|"
372 u"MOBI|mobi|"
373 u"MUSEUM|museum|"
374 u"NAME|name|"
375 u"NET|net|"
376 u"ORG|org|"
377 u"PRO|pro|"
378 u"RO|ro|"
379 u"RU|ru|"
380 u"TEL|tel|"
381 u"TRAVEL|travel"
382 u")"
383 u"([a-zA-Z0-9\\?%=&/_\\.:#;-]+)" // everything to 1st non-URI char, must be at least one char after the previous dot (cannot use ".*" because it can be too greedy)
384 u")"
385 u"|"
386 u"(" // case 2b no scheme, no TLD, must have at least 2 alphanum strings plus uncommon TLD string --> del.icio.us
387 u"([a-zA-Z0-9_-]+\\.) {2,}" // 2 or more domainpart. --> del.icio.
388 u"[a-zA-Z]{2,}" // one ab (2 char or longer) --> us
389 u"([a-zA-Z0-9\\?%=&/_\\.:#;-]*)" // everything to 1st non-URI char, maybe nothing in case of del.icio.us/path
390 u")"
391 u")"_s
394 // Capture links
395 result.replace(reURL, u"\\1<a href=\"\\2\">\\2</a>"_s);
397 // Capture links without scheme
398 const QRegularExpression reNoScheme(u"<a\\s+href=\"(?!https?)([a-zA-Z0-9\\?%=&/_\\.-:#]+)\\s*\">"_s);
399 result.replace(reNoScheme, u"<a href=\"http://\\1\">"_s);
401 // to preserve plain text formatting
402 result = u"<p style=\"white-space: pre-wrap;\">" + result + u"</p>";
403 return result;
406 QString Utils::Misc::osName()
408 // static initialization for usage in signal handler
409 static const QString name =
410 u"%1 %2 %3"_s
411 .arg(QSysInfo::prettyProductName()
412 , QSysInfo::kernelVersion()
413 , QSysInfo::currentCpuArchitecture());
414 return name;
417 QString Utils::Misc::boostVersionString()
419 // static initialization for usage in signal handler
420 static const QString ver = u"%1.%2.%3"_s
421 .arg(QString::number(BOOST_VERSION / 100000)
422 , QString::number((BOOST_VERSION / 100) % 1000)
423 , QString::number(BOOST_VERSION % 100));
424 return ver;
427 QString Utils::Misc::libtorrentVersionString()
429 // static initialization for usage in signal handler
430 static const auto version {QString::fromLatin1(lt::version())};
431 return version;
434 QString Utils::Misc::opensslVersionString()
436 // static initialization for usage in signal handler
437 static const auto version {QString::fromLatin1(::OpenSSL_version(OPENSSL_VERSION))
438 .section(u' ', 1, 1)};
439 return version;
442 QString Utils::Misc::zlibVersionString()
444 // static initialization for usage in signal handler
445 static const auto version {QString::fromLatin1(zlibVersion())};
446 return version;