WebUI: migrate to fetch API
[qBittorrent.git] / src / base / utils / misc.cpp
blob27d3054521b6688f0a007c58fbec92d8e69cb449
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/net/downloadmanager.h"
51 #include "base/path.h"
52 #include "base/unicodestrings.h"
53 #include "base/utils/string.h"
55 namespace
57 const struct { const char *source; const char *comment; } units[] =
59 QT_TRANSLATE_NOOP3("misc", "B", "bytes"),
60 QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"),
61 QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
62 QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"),
63 QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)"),
64 QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"),
65 QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)")
68 // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
69 // use Binary prefix standards from IEC 60027-2
70 // see http://en.wikipedia.org/wiki/Kilobyte
71 // value must be given in bytes
72 // to send numbers instead of strings with suffixes
73 struct SplitToFriendlyUnitResult
75 qreal value;
76 Utils::Misc::SizeUnit unit;
79 std::optional<SplitToFriendlyUnitResult> splitToFriendlyUnit(const qint64 bytes, const int unitThreshold = 1024)
81 if (bytes < 0)
82 return std::nullopt;
84 int i = 0;
85 auto value = static_cast<qreal>(bytes);
87 while ((value >= unitThreshold) && (i < static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
89 value /= 1024;
90 ++i;
92 return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
96 QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
98 const auto &unitString = units[static_cast<int>(unit)];
99 QString ret = QCoreApplication::translate("misc", unitString.source, unitString.comment);
100 if (isSpeed)
101 ret += QCoreApplication::translate("misc", "/s", "per second");
102 return ret;
105 QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed, const int precision)
107 const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
108 if (!result)
109 return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
111 const int digitPrecision = (precision >= 0) ? precision : friendlyUnitPrecision(result->unit);
112 return Utils::String::fromDouble(result->value, digitPrecision)
113 + QChar::Nbsp + unitString(result->unit, isSpeed);
116 QString Utils::Misc::friendlyUnitCompact(const qint64 bytes)
118 // avoid 1000-1023 values, use next larger unit instead
119 const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes, 1000);
120 if (!result)
121 return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
123 int precision = 0; // >= 100
124 if (result->value < 10)
125 precision = 2; // 0 - 9.99
126 if (result->value < 100)
127 precision = 1; // 10 - 99.9
129 return Utils::String::fromDouble(result->value, precision)
130 // use only one character for unit representation
131 + QChar::Nbsp + unitString(result->unit, false)[0];
134 int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
136 // friendlyUnit's number of digits after the decimal point
137 switch (unit)
139 case SizeUnit::Byte:
140 return 0;
141 case SizeUnit::KibiByte:
142 case SizeUnit::MebiByte:
143 return 1;
144 case SizeUnit::GibiByte:
145 return 2;
146 default:
147 return 3;
151 qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
153 for (int i = 0; i < static_cast<int>(unit); ++i)
154 size *= 1024;
155 return size;
158 bool Utils::Misc::isPreviewable(const Path &filePath)
160 const QString mime = QMimeDatabase().mimeTypeForFile(filePath.data(), QMimeDatabase::MatchExtension).name();
162 if (mime.startsWith(u"audio", Qt::CaseInsensitive)
163 || mime.startsWith(u"video", Qt::CaseInsensitive))
165 return true;
168 const QSet<QString> multimediaExtensions =
170 u".3GP"_s,
171 u".AAC"_s,
172 u".AC3"_s,
173 u".AIF"_s,
174 u".AIFC"_s,
175 u".AIFF"_s,
176 u".ASF"_s,
177 u".AU"_s,
178 u".AVI"_s,
179 u".FLAC"_s,
180 u".FLV"_s,
181 u".M3U"_s,
182 u".M4A"_s,
183 u".M4P"_s,
184 u".M4V"_s,
185 u".MID"_s,
186 u".MKV"_s,
187 u".MOV"_s,
188 u".MP2"_s,
189 u".MP3"_s,
190 u".MP4"_s,
191 u".MPC"_s,
192 u".MPE"_s,
193 u".MPEG"_s,
194 u".MPG"_s,
195 u".MPP"_s,
196 u".OGG"_s,
197 u".OGM"_s,
198 u".OGV"_s,
199 u".QT"_s,
200 u".RA"_s,
201 u".RAM"_s,
202 u".RM"_s,
203 u".RMV"_s,
204 u".RMVB"_s,
205 u".SWA"_s,
206 u".SWF"_s,
207 u".TS"_s,
208 u".VOB"_s,
209 u".WAV"_s,
210 u".WMA"_s,
211 u".WMV"_s
213 return multimediaExtensions.contains(filePath.extension().toUpper());
216 bool Utils::Misc::isTorrentLink(const QString &str)
218 return str.startsWith(u"magnet:", Qt::CaseInsensitive)
219 || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)
220 || (!str.startsWith(u"file:", Qt::CaseInsensitive)
221 && Net::DownloadManager::hasSupportedScheme(str));
224 QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap, const TimeResolution resolution)
226 if (seconds < 0)
227 return C_INFINITY;
228 if ((maxCap >= 0) && (seconds >= maxCap))
229 return C_INFINITY;
231 if (seconds == 0)
232 return u"0"_s;
234 if (seconds < 60)
236 if (resolution == TimeResolution::Minutes)
237 return QCoreApplication::translate("misc", "< 1m", "< 1 minute");
239 return QCoreApplication::translate("misc", "%1s", "e.g: 10 seconds").arg(QString::number(seconds));
242 qlonglong minutes = (seconds / 60);
243 if (minutes < 60)
244 return QCoreApplication::translate("misc", "%1m", "e.g: 10 minutes").arg(QString::number(minutes));
246 qlonglong hours = (minutes / 60);
247 if (hours < 24)
249 minutes -= (hours * 60);
250 return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3 hours 5 minutes").arg(QString::number(hours), QString::number(minutes));
253 qlonglong days = (hours / 24);
254 if (days < 365)
256 hours -= (days * 24);
257 return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2 days 10 hours").arg(QString::number(days), QString::number(hours));
260 qlonglong years = (days / 365);
261 days -= (years * 365);
262 return QCoreApplication::translate("misc", "%1y %2d", "e.g: 2 years 10 days").arg(QString::number(years), QString::number(days));
265 QString Utils::Misc::languageToLocalizedString(const QStringView localeStr)
267 if (localeStr.startsWith(u"eo", Qt::CaseInsensitive))
269 // QLocale doesn't work with that locale. Esperanto isn't a "real" language.
270 return C_LOCALE_ESPERANTO;
273 if (localeStr.startsWith(u"ltg", Qt::CaseInsensitive))
275 // QLocale doesn't work with that locale.
276 return C_LOCALE_LATGALIAN;
279 const QLocale locale {localeStr};
280 switch (locale.language())
282 case QLocale::Arabic: return C_LOCALE_ARABIC;
283 case QLocale::Armenian: return C_LOCALE_ARMENIAN;
284 case QLocale::Azerbaijani: return C_LOCALE_AZERBAIJANI;
285 case QLocale::Basque: return C_LOCALE_BASQUE;
286 case QLocale::Bulgarian: return C_LOCALE_BULGARIAN;
287 case QLocale::Byelorussian: return C_LOCALE_BYELORUSSIAN;
288 case QLocale::Catalan: return C_LOCALE_CATALAN;
289 case QLocale::Chinese:
290 switch (locale.territory())
292 case QLocale::China: return C_LOCALE_CHINESE_SIMPLIFIED;
293 case QLocale::HongKong: return C_LOCALE_CHINESE_TRADITIONAL_HK;
294 default: return C_LOCALE_CHINESE_TRADITIONAL_TW;
296 case QLocale::Croatian: return C_LOCALE_CROATIAN;
297 case QLocale::Czech: return C_LOCALE_CZECH;
298 case QLocale::Danish: return C_LOCALE_DANISH;
299 case QLocale::Dutch: return C_LOCALE_DUTCH;
300 case QLocale::English:
301 switch (locale.territory())
303 case QLocale::Australia: return C_LOCALE_ENGLISH_AUSTRALIA;
304 case QLocale::UnitedKingdom: return C_LOCALE_ENGLISH_UNITEDKINGDOM;
305 default: return C_LOCALE_ENGLISH;
307 case QLocale::Estonian: return C_LOCALE_ESTONIAN;
308 case QLocale::Finnish: return C_LOCALE_FINNISH;
309 case QLocale::French: return C_LOCALE_FRENCH;
310 case QLocale::Galician: return C_LOCALE_GALICIAN;
311 case QLocale::Georgian: return C_LOCALE_GEORGIAN;
312 case QLocale::German: return C_LOCALE_GERMAN;
313 case QLocale::Greek: return C_LOCALE_GREEK;
314 case QLocale::Hebrew: return C_LOCALE_HEBREW;
315 case QLocale::Hindi: return C_LOCALE_HINDI;
316 case QLocale::Hungarian: return C_LOCALE_HUNGARIAN;
317 case QLocale::Icelandic: return C_LOCALE_ICELANDIC;
318 case QLocale::Indonesian: return C_LOCALE_INDONESIAN;
319 case QLocale::Italian: return C_LOCALE_ITALIAN;
320 case QLocale::Japanese: return C_LOCALE_JAPANESE;
321 case QLocale::Korean: return C_LOCALE_KOREAN;
322 case QLocale::Latvian: return C_LOCALE_LATVIAN;
323 case QLocale::Lithuanian: return C_LOCALE_LITHUANIAN;
324 case QLocale::Malay: return C_LOCALE_MALAY;
325 case QLocale::Mongolian: return C_LOCALE_MONGOLIAN;
326 case QLocale::NorwegianBokmal: return C_LOCALE_NORWEGIAN;
327 case QLocale::Occitan: return C_LOCALE_OCCITAN;
328 case QLocale::Persian: return C_LOCALE_PERSIAN;
329 case QLocale::Polish: return C_LOCALE_POLISH;
330 case QLocale::Portuguese:
331 if (locale.territory() == QLocale::Brazil)
332 return C_LOCALE_PORTUGUESE_BRAZIL;
333 return C_LOCALE_PORTUGUESE;
334 case QLocale::Romanian: return C_LOCALE_ROMANIAN;
335 case QLocale::Russian: return C_LOCALE_RUSSIAN;
336 case QLocale::Serbian: return C_LOCALE_SERBIAN;
337 case QLocale::Slovak: return C_LOCALE_SLOVAK;
338 case QLocale::Slovenian: return C_LOCALE_SLOVENIAN;
339 case QLocale::Spanish: return C_LOCALE_SPANISH;
340 case QLocale::Swedish: return C_LOCALE_SWEDISH;
341 case QLocale::Thai: return C_LOCALE_THAI;
342 case QLocale::Turkish: return C_LOCALE_TURKISH;
343 case QLocale::Ukrainian: return C_LOCALE_UKRAINIAN;
344 case QLocale::Uzbek: return C_LOCALE_UZBEK;
345 case QLocale::Vietnamese: return C_LOCALE_VIETNAMESE;
346 default:
347 const QString lang = QLocale::languageToString(locale.language());
348 qWarning() << "Unrecognized language name: " << lang;
349 return lang;
353 QString Utils::Misc::parseHtmlLinks(const QString &rawText)
355 QString result = rawText;
356 static const QRegularExpression reURL(
357 u"(\\s|^)" // start with whitespace or beginning of line
358 u"("
359 u"(" // case 1 -- URL with scheme
360 u"(http(s?))\\://" // start with scheme
361 u"([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
362 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)
363 u")"
364 u"|"
365 u"(" // case 2a -- no scheme, contains common TLD example.com
366 u"([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
367 u"(?=" // must be followed by TLD
368 u"AERO|aero|" // N.B. assertions are non-capturing
369 u"ARPA|arpa|"
370 u"ASIA|asia|"
371 u"BIZ|biz|"
372 u"CAT|cat|"
373 u"COM|com|"
374 u"COOP|coop|"
375 u"EDU|edu|"
376 u"GOV|gov|"
377 u"INFO|info|"
378 u"INT|int|"
379 u"JOBS|jobs|"
380 u"MIL|mil|"
381 u"MOBI|mobi|"
382 u"MUSEUM|museum|"
383 u"NAME|name|"
384 u"NET|net|"
385 u"ORG|org|"
386 u"PRO|pro|"
387 u"RO|ro|"
388 u"RU|ru|"
389 u"TEL|tel|"
390 u"TRAVEL|travel"
391 u")"
392 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)
393 u")"
394 u"|"
395 u"(" // case 2b no scheme, no TLD, must have at least 2 alphanum strings plus uncommon TLD string --> del.icio.us
396 u"([a-zA-Z0-9_-]+\\.) {2,}" // 2 or more domainpart. --> del.icio.
397 u"[a-zA-Z]{2,}" // one ab (2 char or longer) --> us
398 u"([a-zA-Z0-9\\?%=&/_\\.:#;-]*)" // everything to 1st non-URI char, maybe nothing in case of del.icio.us/path
399 u")"
400 u")"_s
403 // Capture links
404 result.replace(reURL, u"\\1<a href=\"\\2\">\\2</a>"_s);
406 // Capture links without scheme
407 const QRegularExpression reNoScheme(u"<a\\s+href=\"(?!https?)([a-zA-Z0-9\\?%=&/_\\.-:#]+)\\s*\">"_s);
408 result.replace(reNoScheme, u"<a href=\"http://\\1\">"_s);
410 // to preserve plain text formatting
411 result = u"<p style=\"white-space: pre-wrap;\">" + result + u"</p>";
412 return result;
415 QString Utils::Misc::osName()
417 // static initialization for usage in signal handler
418 static const QString name =
419 u"%1 %2 %3"_s
420 .arg(QSysInfo::prettyProductName()
421 , QSysInfo::kernelVersion()
422 , QSysInfo::currentCpuArchitecture());
423 return name;
426 QString Utils::Misc::boostVersionString()
428 // static initialization for usage in signal handler
429 static const QString ver = u"%1.%2.%3"_s
430 .arg(QString::number(BOOST_VERSION / 100000)
431 , QString::number((BOOST_VERSION / 100) % 1000)
432 , QString::number(BOOST_VERSION % 100));
433 return ver;
436 QString Utils::Misc::libtorrentVersionString()
438 // static initialization for usage in signal handler
439 static const auto version {QString::fromLatin1(lt::version())};
440 return version;
443 QString Utils::Misc::opensslVersionString()
445 // static initialization for usage in signal handler
446 static const auto version {QString::fromLatin1(::OpenSSL_version(OPENSSL_VERSION))
447 .section(u' ', 1, 1)};
448 return version;
451 QString Utils::Misc::zlibVersionString()
453 // static initialization for usage in signal handler
454 static const auto version {QString::fromLatin1(zlibVersion())};
455 return version;