qBittorrent
misc.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
4  *
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.
9  *
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.
14  *
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.
18  *
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.
27  */
28 
29 #include "misc.h"
30 
31 #include <optional>
32 
33 #ifdef Q_OS_WIN
34 #include <windows.h>
35 #include <powrprof.h>
36 #include <Shlobj.h>
37 #else
38 #include <sys/types.h>
39 #include <unistd.h>
40 #endif
41 
42 #ifdef Q_OS_MACOS
43 #include <Carbon/Carbon.h>
44 #include <CoreServices/CoreServices.h>
45 #endif
46 
47 #include <boost/version.hpp>
48 #include <libtorrent/version.hpp>
49 #include <openssl/crypto.h>
50 #include <openssl/opensslv.h>
51 #include <zlib.h>
52 
53 #include <QCoreApplication>
54 #include <QMimeDatabase>
55 #include <QRegularExpression>
56 #include <QSet>
57 #include <QSysInfo>
58 #include <QVector>
59 
60 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
61 #include <QDBusInterface>
62 #endif
63 
64 #include "base/types.h"
65 #include "base/unicodestrings.h"
66 #include "base/utils/fs.h"
67 #include "base/utils/string.h"
68 
69 namespace
70 {
71  const struct { const char *source; const char *comment; } units[] =
72  {
73  QT_TRANSLATE_NOOP3("misc", "B", "bytes"),
74  QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"),
75  QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
76  QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"),
77  QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)"),
78  QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"),
79  QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)")
80  };
81 
82  // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
83  // use Binary prefix standards from IEC 60027-2
84  // see http://en.wikipedia.org/wiki/Kilobyte
85  // value must be given in bytes
86  // to send numbers instead of strings with suffixes
88  {
89  qreal value;
91  };
92 
93  std::optional<SplitToFriendlyUnitResult> splitToFriendlyUnit(const qint64 bytes)
94  {
95  if (bytes < 0)
96  return std::nullopt;
97 
98  int i = 0;
99  auto value = static_cast<qreal>(bytes);
100 
101  while ((value >= 1024) && (i < static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
102  {
103  value /= 1024;
104  ++i;
105  }
106  return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
107  }
108 }
109 
111 {
112 #if defined(Q_OS_WIN)
113  HANDLE hToken; // handle to process token
114  TOKEN_PRIVILEGES tkp; // pointer to token structure
115  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
116  return;
117  // Get the LUID for shutdown privilege.
118  LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
119  &tkp.Privileges[0].Luid);
120 
121  tkp.PrivilegeCount = 1; // one privilege to set
122  tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
123 
124  // Get shutdown privilege for this process.
125 
126  AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
127  (PTOKEN_PRIVILEGES) NULL, 0);
128 
129  // Cannot test the return value of AdjustTokenPrivileges.
130 
131  if (GetLastError() != ERROR_SUCCESS)
132  return;
133 
135  {
136  ::SetSuspendState(FALSE, FALSE, FALSE);
137  }
139  {
140  ::SetSuspendState(TRUE, FALSE, FALSE);
141  }
142  else
143  {
144  const QString msg = QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete.");
145  auto msgWchar = std::make_unique<wchar_t[]>(msg.length() + 1);
146  msg.toWCharArray(msgWchar.get());
147  ::InitiateSystemShutdownW(nullptr, msgWchar.get(), 10, TRUE, FALSE);
148  }
149 
150  // Disable shutdown privilege.
151  tkp.Privileges[0].Attributes = 0;
152  AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0);
153 
154 #elif defined(Q_OS_MACOS)
155  AEEventID EventToSend;
157  EventToSend = kAESleep;
158  else
159  EventToSend = kAEShutDown;
160  AEAddressDesc targetDesc;
161  const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess};
162  AppleEvent eventReply = {typeNull, NULL};
163  AppleEvent appleEventToSend = {typeNull, NULL};
164 
165  OSStatus error = AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess,
166  sizeof(kPSNOfSystemProcess), &targetDesc);
167 
168  if (error != noErr)
169  return;
170 
171  error = AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc,
172  kAutoGenerateReturnID, kAnyTransactionID, &appleEventToSend);
173 
174  AEDisposeDesc(&targetDesc);
175  if (error != noErr)
176  return;
177 
178  error = AESend(&appleEventToSend, &eventReply, kAENoReply,
179  kAENormalPriority, kAEDefaultTimeout, NULL, NULL);
180 
181  AEDisposeDesc(&appleEventToSend);
182  if (error != noErr)
183  return;
184 
185  AEDisposeDesc(&eventReply);
186 
187 #elif (defined(Q_OS_UNIX) && defined(QT_DBUS_LIB))
188  // Use dbus to power off / suspend the system
190  {
191  // Some recent systems use systemd's logind
192  QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
193  "org.freedesktop.login1.Manager", QDBusConnection::systemBus());
194  if (login1Iface.isValid())
195  {
197  login1Iface.call("Suspend", false);
198  else
199  login1Iface.call("Hibernate", false);
200  return;
201  }
202  // Else, other recent systems use UPower
203  QDBusInterface upowerIface("org.freedesktop.UPower", "/org/freedesktop/UPower",
204  "org.freedesktop.UPower", QDBusConnection::systemBus());
205  if (upowerIface.isValid())
206  {
208  upowerIface.call("Suspend");
209  else
210  upowerIface.call("Hibernate");
211  return;
212  }
213  // HAL (older systems)
214  QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer",
215  "org.freedesktop.Hal.Device.SystemPowerManagement",
216  QDBusConnection::systemBus());
218  halIface.call("Suspend", 5);
219  else
220  halIface.call("Hibernate");
221  }
222  else
223  {
224  // Some recent systems use systemd's logind
225  QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
226  "org.freedesktop.login1.Manager", QDBusConnection::systemBus());
227  if (login1Iface.isValid())
228  {
229  login1Iface.call("PowerOff", false);
230  return;
231  }
232  // Else, other recent systems use ConsoleKit
233  QDBusInterface consolekitIface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager",
234  "org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus());
235  if (consolekitIface.isValid())
236  {
237  consolekitIface.call("Stop");
238  return;
239  }
240  // HAL (older systems)
241  QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer",
242  "org.freedesktop.Hal.Device.SystemPowerManagement",
243  QDBusConnection::systemBus());
244  halIface.call("Shutdown");
245  }
246 
247 #else
248  Q_UNUSED(action);
249 #endif
250 }
251 
252 QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
253 {
254  const auto &unitString = units[static_cast<int>(unit)];
255  QString ret = QCoreApplication::translate("misc", unitString.source, unitString.comment);
256  if (isSpeed)
257  ret += QCoreApplication::translate("misc", "/s", "per second");
258  return ret;
259 }
260 
261 QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed)
262 {
263  const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
264  if (!result)
265  return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
266  return Utils::String::fromDouble(result->value, friendlyUnitPrecision(result->unit))
267  + QString::fromUtf8(C_NON_BREAKING_SPACE)
268  + unitString(result->unit, isSpeed);
269 }
270 
272 {
273  // friendlyUnit's number of digits after the decimal point
274  switch (unit)
275  {
276  case SizeUnit::Byte:
277  return 0;
278  case SizeUnit::KibiByte:
279  case SizeUnit::MebiByte:
280  return 1;
281  case SizeUnit::GibiByte:
282  return 2;
283  default:
284  return 3;
285  }
286 }
287 
288 qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
289 {
290  for (int i = 0; i < static_cast<int>(unit); ++i)
291  size *= 1024;
292  return size;
293 }
294 
295 bool Utils::Misc::isPreviewable(const QString &filename)
296 {
297  const QString mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension).name();
298 
299  if (mime.startsWith(QLatin1String("audio"), Qt::CaseInsensitive)
300  || mime.startsWith(QLatin1String("video"), Qt::CaseInsensitive))
301  {
302  return true;
303  }
304 
305  const QSet<QString> multimediaExtensions =
306  {
307  "3GP",
308  "AAC",
309  "AC3",
310  "AIF",
311  "AIFC",
312  "AIFF",
313  "ASF",
314  "AU",
315  "AVI",
316  "FLAC",
317  "FLV",
318  "M3U",
319  "M4A",
320  "M4P",
321  "M4V",
322  "MID",
323  "MKV",
324  "MOV",
325  "MP2",
326  "MP3",
327  "MP4",
328  "MPC",
329  "MPE",
330  "MPEG",
331  "MPG",
332  "MPP",
333  "OGG",
334  "OGM",
335  "OGV",
336  "QT",
337  "RA",
338  "RAM",
339  "RM",
340  "RMV",
341  "RMVB",
342  "SWA",
343  "SWF",
344  "TS",
345  "VOB",
346  "WAV",
347  "WMA",
348  "WMV"
349  };
350  return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper());
351 }
352 
353 QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap)
354 {
355  if (seconds < 0)
356  return QString::fromUtf8(C_INFINITY);
357  if ((maxCap >= 0) && (seconds >= maxCap))
358  return QString::fromUtf8(C_INFINITY);
359 
360  if (seconds == 0)
361  return "0";
362 
363  if (seconds < 60)
364  return QCoreApplication::translate("misc", "< 1m", "< 1 minute");
365 
366  qlonglong minutes = (seconds / 60);
367  if (minutes < 60)
368  return QCoreApplication::translate("misc", "%1m", "e.g: 10minutes").arg(QString::number(minutes));
369 
370  qlonglong hours = (minutes / 60);
371  if (hours < 24)
372  {
373  minutes -= (hours * 60);
374  return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours), QString::number(minutes));
375  }
376 
377  qlonglong days = (hours / 24);
378  if (days < 365)
379  {
380  hours -= (days * 24);
381  return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days), QString::number(hours));
382  }
383 
384  qlonglong years = (days / 365);
385  days -= (years * 365);
386  return QCoreApplication::translate("misc", "%1y %2d", "e.g: 2years 10days").arg(QString::number(years), QString::number(days));
387 }
388 
390 {
391  QString uid = "0";
392 #ifdef Q_OS_WIN
393  const int UNLEN = 256;
394  WCHAR buffer[UNLEN + 1] = {0};
395  DWORD buffer_len = sizeof(buffer) / sizeof(*buffer);
396  if (GetUserNameW(buffer, &buffer_len))
397  uid = QString::fromWCharArray(buffer);
398 #else
399  uid = QString::number(getuid());
400 #endif
401  return uid;
402 }
403 
404 QString Utils::Misc::parseHtmlLinks(const QString &rawText)
405 {
406  QString result = rawText;
407  static const QRegularExpression reURL(
408  "(\\s|^)" // start with whitespace or beginning of line
409  "("
410  "(" // case 1 -- URL with scheme
411  "(http(s?))\\://" // start with scheme
412  "([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
413  "([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)
414  ")"
415  "|"
416  "(" // case 2a -- no scheme, contains common TLD example.com
417  "([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
418  "(?=" // must be followed by TLD
419  "AERO|aero|" // N.B. assertions are non-capturing
420  "ARPA|arpa|"
421  "ASIA|asia|"
422  "BIZ|biz|"
423  "CAT|cat|"
424  "COM|com|"
425  "COOP|coop|"
426  "EDU|edu|"
427  "GOV|gov|"
428  "INFO|info|"
429  "INT|int|"
430  "JOBS|jobs|"
431  "MIL|mil|"
432  "MOBI|mobi|"
433  "MUSEUM|museum|"
434  "NAME|name|"
435  "NET|net|"
436  "ORG|org|"
437  "PRO|pro|"
438  "RO|ro|"
439  "RU|ru|"
440  "TEL|tel|"
441  "TRAVEL|travel"
442  ")"
443  "([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)
444  ")"
445  "|"
446  "(" // case 2b no scheme, no TLD, must have at least 2 alphanum strings plus uncommon TLD string --> del.icio.us
447  "([a-zA-Z0-9_-]+\\.) {2,}" // 2 or more domainpart. --> del.icio.
448  "[a-zA-Z]{2,}" // one ab (2 char or longer) --> us
449  "([a-zA-Z0-9\\?%=&/_\\.:#;-]*)" // everything to 1st non-URI char, maybe nothing in case of del.icio.us/path
450  ")"
451  ")"
452  );
453 
454  // Capture links
455  result.replace(reURL, "\\1<a href=\"\\2\">\\2</a>");
456 
457  // Capture links without scheme
458  static const QRegularExpression reNoScheme("<a\\s+href=\"(?!https?)([a-zA-Z0-9\\?%=&/_\\.-:#]+)\\s*\">");
459  result.replace(reNoScheme, "<a href=\"http://\\1\">");
460 
461  // to preserve plain text formatting
462  result = "<p style=\"white-space: pre-wrap;\">" + result + "</p>";
463  return result;
464 }
465 
467 {
468  // static initialization for usage in signal handler
469  static const QString name =
470  QString("%1 %2 %3")
471  .arg(QSysInfo::prettyProductName()
472  , QSysInfo::kernelVersion()
473  , QSysInfo::currentCpuArchitecture());
474  return name;
475 }
476 
478 {
479  // static initialization for usage in signal handler
480  static const QString ver = QString("%1.%2.%3")
481  .arg(QString::number(BOOST_VERSION / 100000)
482  , QString::number((BOOST_VERSION / 100) % 1000)
483  , QString::number(BOOST_VERSION % 100));
484  return ver;
485 }
486 
488 {
489  // static initialization for usage in signal handler
490  static const auto version {QString::fromLatin1(lt::version())};
491  return version;
492 }
493 
495 {
496 #if (OPENSSL_VERSION_NUMBER >= 0x1010000f)
497  static const auto version {QString::fromLatin1(OpenSSL_version(OPENSSL_VERSION))};
498 #else
499  static const auto version {QString::fromLatin1(SSLeay_version(SSLEAY_VERSION))};
500 #endif
501  return QStringView(version).split(u' ', Qt::SkipEmptyParts).at(1).toString();
502 }
503 
505 {
506  // static initialization for usage in signal handler
507  static const auto version {QString::fromLatin1(zlibVersion())};
508  return version;
509 }
510 
511 #ifdef Q_OS_WIN
512 QString Utils::Misc::windowsSystemPath()
513 {
514  static const QString path = []() -> QString
515  {
516  WCHAR systemPath[MAX_PATH] = {0};
517  GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
518  return QString::fromWCharArray(systemPath);
519  }();
520  return path;
521 }
522 #endif
QString fileExtension(const QString &filename)
Definition: fs.cpp:82
QString osName()
Definition: misc.cpp:466
QString libtorrentVersionString()
Definition: misc.cpp:487
QString opensslVersionString()
Definition: misc.cpp:494
QString parseHtmlLinks(const QString &rawText)
Definition: misc.cpp:404
SizeUnit
Definition: misc.h:49
void shutdownComputer(const ShutdownDialogAction &action)
Definition: misc.cpp:110
QString unitString(SizeUnit unit, bool isSpeed=false)
Definition: misc.cpp:252
qint64 sizeInBytes(qreal size, SizeUnit unit)
Definition: misc.cpp:288
QString boostVersionString()
Definition: misc.cpp:477
bool isPreviewable(const QString &filename)
Definition: misc.cpp:295
int friendlyUnitPrecision(SizeUnit unit)
Definition: misc.cpp:271
QString zlibVersionString()
Definition: misc.cpp:504
QString userFriendlyDuration(qlonglong seconds, qlonglong maxCap=-1)
Definition: misc.cpp:353
QString friendlyUnit(qint64 bytes, bool isSpeed=false)
Definition: misc.cpp:261
QString getUserIDString()
Definition: misc.cpp:389
QString fromDouble(double n, int precision)
Definition: string.cpp:44
std::optional< SplitToFriendlyUnitResult > splitToFriendlyUnit(const qint64 bytes)
Definition: misc.cpp:93
const struct anonymous_namespace{misc.cpp}::@6 units[]
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
action
Definition: tstool.py:143
ShutdownDialogAction
Definition: types.h:36
const char C_NON_BREAKING_SPACE[]
const char C_INFINITY[]