/* * Bittorrent Client using Qt4 and libtorrent. * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give permission to * link this program with the OpenSSL project's "OpenSSL" library (or with * modified versions of it that use the same license as the "OpenSSL" library), * and distribute the linked executables. You must obey the GNU General Public * License in all respects for all of the code used other than "OpenSSL". If you * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. * * Contact : chris@qbittorrent.org */ #include "misc.h" #include #include #include #include #include #include #include #include #include #include #ifdef DISABLE_GUI #include #else #include #include #endif #ifdef Q_OS_WIN #include #include const int UNLEN = 256; #else #include #include #endif #ifdef Q_OS_MAC #include #include #endif #ifndef DISABLE_GUI #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB) #include #include #endif #endif // DISABLE_GUI using namespace libtorrent; static struct { const char *source; const char *comment; } units[] = { QT_TRANSLATE_NOOP3("misc", "B", "bytes"), QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"), QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"), QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"), QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)") }; #ifndef DISABLE_GUI void misc::shutdownComputer(shutDownAction action) { #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB) // Use dbus to power off / suspend the system if (action != SHUTDOWN_COMPUTER) { // Some recent systems use systemd's logind QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QDBusConnection::systemBus()); if (login1Iface.isValid()) { if (action == SUSPEND_COMPUTER) login1Iface.call("Suspend", false); else login1Iface.call("Hibernate", false); return; } // Else, other recent systems use UPower QDBusInterface upowerIface("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", QDBusConnection::systemBus()); if (upowerIface.isValid()) { if (action == SUSPEND_COMPUTER) upowerIface.call("Suspend"); else upowerIface.call("Hibernate"); return; } // HAL (older systems) QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer", "org.freedesktop.Hal.Device.SystemPowerManagement", QDBusConnection::systemBus()); if (action == SUSPEND_COMPUTER) halIface.call("Suspend", 5); else halIface.call("Hibernate"); } else { // Some recent systems use systemd's logind QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QDBusConnection::systemBus()); if (login1Iface.isValid()) { login1Iface.call("PowerOff", false); return; } // Else, other recent systems use ConsoleKit QDBusInterface consolekitIface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus()); if (consolekitIface.isValid()) { consolekitIface.call("Stop"); return; } // HAL (older systems) QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer", "org.freedesktop.Hal.Device.SystemPowerManagement", QDBusConnection::systemBus()); halIface.call("Shutdown"); } #endif #ifdef Q_OS_MAC AEEventID EventToSend; if (action != SHUTDOWN_COMPUTER) EventToSend = kAESleep; else EventToSend = kAEShutDown; AEAddressDesc targetDesc; static const ProcessSerialNumber kPSNOfSystemProcess = { 0, kSystemProcess }; AppleEvent eventReply = {typeNull, NULL}; AppleEvent appleEventToSend = {typeNull, NULL}; OSStatus error = noErr; error = AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess, sizeof(kPSNOfSystemProcess), &targetDesc); if (error != noErr) { return; } error = AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc, kAutoGenerateReturnID, kAnyTransactionID, &appleEventToSend); AEDisposeDesc(&targetDesc); if (error != noErr) { return; } error = AESend(&appleEventToSend, &eventReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, NULL, NULL); AEDisposeDesc(&appleEventToSend); if (error != noErr) { return; } AEDisposeDesc(&eventReply); #endif #ifdef Q_OS_WIN HANDLE hToken; // handle to process token TOKEN_PRIVILEGES tkp; // pointer to token structure if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) return; // Get the LUID for shutdown privilege. LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); tkp.PrivilegeCount = 1; // one privilege to set tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // Get shutdown privilege for this process. AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0); // Cannot test the return value of AdjustTokenPrivileges. if (GetLastError() != ERROR_SUCCESS) return; if (action == SUSPEND_COMPUTER) SetSuspendState(false, false, false); else if (action == HIBERNATE_COMPUTER) SetSuspendState(true, false, false); else InitiateSystemShutdownA(0, QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete.").toLocal8Bit().data(), 10, true, false); // Disable shutdown privilege. tkp.Privileges[0].Attributes = 0; AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0); #endif } #endif // DISABLE_GUI #ifndef DISABLE_GUI // Get screen center QPoint misc::screenCenter(QWidget *win) { int scrn = 0; const QWidget *w = win->window(); if (w) scrn = QApplication::desktop()->screenNumber(w); else if (QApplication::desktop()->isVirtualDesktop()) scrn = QApplication::desktop()->screenNumber(QCursor::pos()); else scrn = QApplication::desktop()->screenNumber(win); QRect desk(QApplication::desktop()->availableGeometry(scrn)); return QPoint((desk.width() - win->frameGeometry().width()) / 2, (desk.height() - win->frameGeometry().height()) / 2); } #endif /** * Detects the version of python by calling * "python --version" and parsing the output. */ int misc::pythonVersion() { static int version = -1; if (version < 0) { QProcess python_proc; python_proc.start("python", QStringList() << "--version", QIODevice::ReadOnly); if (!python_proc.waitForFinished()) return -1; if (python_proc.exitCode() < 0) return -1; QByteArray output = python_proc.readAllStandardOutput(); if (output.isEmpty()) output = python_proc.readAllStandardError(); const QByteArray version_str = output.split(' ').last(); qDebug() << "Python version is:" << version_str.trimmed(); if (version_str.startsWith("3.")) version = 3; else version = 2; } return version; } // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB) // use Binary prefix standards from IEC 60027-2 // see http://en.wikipedia.org/wiki/Kilobyte // value must be given in bytes // FIXME: Remove the 'webui' variable, when the webui API is reworked // to send numbers instead of strings with suffixes QString misc::friendlyUnit(qreal val, bool is_speed, bool webui) { if (val < 0) return QCoreApplication::translate("misc", "Unknown", "Unknown (size)"); int i = 0; while(val >= 1024. && i++<6) val /= 1024.; QString ret; if (i == 0) ret = QString::number((long)val) + " " + QCoreApplication::translate("misc", units[0].source, units[0].comment); else ret = accurateDoubleToString(val, 1, !webui) + " " + QCoreApplication::translate("misc", units[i].source, units[i].comment); if (is_speed) ret += QCoreApplication::translate("misc", "/s", "per second"); return ret; } bool misc::isPreviewable(const QString& extension) { static QSet multimedia_extensions; if (multimedia_extensions.empty()) { multimedia_extensions.insert("3GP"); multimedia_extensions.insert("AAC"); multimedia_extensions.insert("AC3"); multimedia_extensions.insert("AIF"); multimedia_extensions.insert("AIFC"); multimedia_extensions.insert("AIFF"); multimedia_extensions.insert("ASF"); multimedia_extensions.insert("AU"); multimedia_extensions.insert("AVI"); multimedia_extensions.insert("FLAC"); multimedia_extensions.insert("FLV"); multimedia_extensions.insert("M3U"); multimedia_extensions.insert("M4A"); multimedia_extensions.insert("M4P"); multimedia_extensions.insert("M4V"); multimedia_extensions.insert("MID"); multimedia_extensions.insert("MKV"); multimedia_extensions.insert("MOV"); multimedia_extensions.insert("MP2"); multimedia_extensions.insert("MP3"); multimedia_extensions.insert("MP4"); multimedia_extensions.insert("MPC"); multimedia_extensions.insert("MPE"); multimedia_extensions.insert("MPEG"); multimedia_extensions.insert("MPG"); multimedia_extensions.insert("MPP"); multimedia_extensions.insert("OGG"); multimedia_extensions.insert("OGM"); multimedia_extensions.insert("OGV"); multimedia_extensions.insert("QT"); multimedia_extensions.insert("RA"); multimedia_extensions.insert("RAM"); multimedia_extensions.insert("RM"); multimedia_extensions.insert("RMV"); multimedia_extensions.insert("RMVB"); multimedia_extensions.insert("SWA"); multimedia_extensions.insert("SWF"); multimedia_extensions.insert("VOB"); multimedia_extensions.insert("WAV"); multimedia_extensions.insert("WMA"); multimedia_extensions.insert("WMV"); } if (extension.isEmpty()) return false; return multimedia_extensions.contains(extension.toUpper()); } QString misc::bcLinkToMagnet(QString bc_link) { QByteArray raw_bc = bc_link.toUtf8(); raw_bc = raw_bc.mid(8); // skip bc://bt/ raw_bc = QByteArray::fromBase64(raw_bc); // Decode base64 // Format is now AA/url_encoded_filename/size_bytes/info_hash/ZZ QStringList parts = QString(raw_bc).split("/"); if (parts.size() != 5) return QString::null; QString filename = parts.at(1); QString hash = parts.at(3); QString magnet = "magnet:?xt=urn:btih:" + hash; magnet += "&dn=" + filename; return magnet; } QString misc::magnetUriToName(const QString& magnet_uri) { QString name = ""; QRegExp regHex("dn=([^&]+)"); const int pos = regHex.indexIn(magnet_uri); if (pos > -1) { const QString found = regHex.cap(1); // URL decode name = QUrl::fromPercentEncoding(found.toLocal8Bit()).replace("+", " "); } return name; } QList misc::magnetUriToTrackers(const QString& magnet_uri) { QList trackers; QRegExp rx("tr=([^&]+)"); int pos = 0; while ((pos = rx.indexIn(magnet_uri, pos)) != -1) { const QUrl tracker = QUrl::fromEncoded(rx.cap(1).toUtf8()); qDebug() << Q_FUNC_INFO << "Found tracker: " << tracker.toString(); trackers << tracker; pos += rx.matchedLength(); } return trackers; } QString misc::magnetUriToHash(const QString& magnet_uri) { QString hash = ""; QRegExp regHex("urn:btih:([0-9A-Za-z]+)"); // Hex int pos = regHex.indexIn(magnet_uri); if (pos > -1) { const QString found = regHex.cap(1); qDebug() << Q_FUNC_INFO << "regex found: " << found; if (found.length() == 40) { const sha1_hash sha1(QByteArray::fromHex(found.toLatin1()).constData()); qDebug("magnetUriToHash (Hex): hash: %s", qPrintable(misc::toQString(sha1))); return misc::toQString(sha1); } } // Base 32 QRegExp regBase32("urn:btih:([A-Za-z2-7=]+)"); pos = regBase32.indexIn(magnet_uri); if (pos > -1) { const QString found = regBase32.cap(1); if (found.length() > 20 && (found.length()*5)%40 == 0) { const sha1_hash sha1(base32decode(regBase32.cap(1).toStdString())); hash = misc::toQString(sha1); } } qDebug("magnetUriToHash (base32): hash: %s", qPrintable(hash)); return hash; } // Take a number of seconds and return an user-friendly // time duration like "1d 2h 10m". QString misc::userFriendlyDuration(qlonglong seconds) { if (seconds < 0 || seconds >= MAX_ETA) { return QString::fromUtf8("∞"); } if (seconds == 0) { return "0"; } if (seconds < 60) { return QCoreApplication::translate("misc", "< 1m", "< 1 minute"); } int minutes = seconds / 60; if (minutes < 60) { return QCoreApplication::translate("misc", "%1m","e.g: 10minutes").arg(QString::number(minutes)); } int hours = minutes / 60; minutes = minutes - hours*60; if (hours < 24) { return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours)).arg(QString::number(minutes)); } int days = hours / 24; hours = hours - days * 24; if (days < 100) { return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days)).arg(QString::number(hours)); } return QString::fromUtf8("∞"); } QString misc::getUserIDString() { QString uid = "0"; #ifdef Q_OS_WIN char buffer[UNLEN+1] = {0}; DWORD buffer_len = UNLEN + 1; if (!GetUserNameA(buffer, &buffer_len)) uid = QString(buffer); #else uid = QString::number(getuid()); #endif return uid; } QStringList misc::toStringList(const QList &l) { QStringList ret; foreach (const bool &b, l) { ret << (b ? "1" : "0"); } return ret; } QList misc::intListfromStringList(const QStringList &l) { QList ret; foreach (const QString &s, l) { ret << s.toInt(); } return ret; } QList misc::boolListfromStringList(const QStringList &l) { QList ret; foreach (const QString &s, l) { ret << (s=="1"); } return ret; } bool misc::isUrl(const QString &s) { const QString scheme = QUrl(s).scheme(); QRegExp is_url("http[s]?|ftp", Qt::CaseInsensitive); return is_url.exactMatch(scheme); } QString misc::parseHtmlLinks(const QString &raw_text) { QString result = raw_text; QRegExp reURL("(\\s|^)" //start with whitespace or beginning of line "(" "(" //case 1 -- URL with scheme "(http(s?))\\://" //start with scheme "([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist "([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) ")" "|" "(" //case 2a -- no scheme, contains common TLD example.com "([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist "(?=" // must be followed by TLD "AERO|aero|" //N.B. assertions are non-capturing "ARPA|arpa|" "ASIA|asia|" "BIZ|biz|" "CAT|cat|" "COM|com|" "COOP|coop|" "EDU|edu|" "GOV|gov|" "INFO|info|" "INT|int|" "JOBS|jobs|" "MIL|mil|" "MOBI|mobi|" "MUSEUM|museum|" "NAME|name|" "NET|net|" "ORG|org|" "PRO|pro|" "RO|ro|" "RU|ru|" "TEL|tel|" "TRAVEL|travel" ")" "([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) ")" "|" "(" // case 2b no scheme, no TLD, must have at least 2 aphanum strings plus uncommon TLD string --> del.icio.us "([a-zA-Z0-9_-]+\\.) {2,}" //2 or more domainpart. --> del.icio. "[a-zA-Z]{2,}" //one ab (2 char or longer) --> us "([a-zA-Z0-9\\?%=&/_\\.:#;-]*)" // everything to 1st non-URI char, maybe nothing in case of del.icio.us/path ")" ")" ); // Capture links result.replace(reURL, "\\1\\2"); // Capture links without scheme QRegExp reNoScheme(""); result.replace(reNoScheme, ""); return result; } QString misc::toQString(time_t t) { return QDateTime::fromTime_t(t).toString(Qt::DefaultLocaleLongDate); } #ifndef DISABLE_GUI bool misc::naturalSort(QString left, QString right, bool &result) { // uses lessThan comparison // Return value indicates if functions was successful // result argument will contain actual comparison result if function was successful do { int posL = left.indexOf(QRegExp("[0-9]")); int posR = right.indexOf(QRegExp("[0-9]")); if (posL == -1 || posR == -1) break; // No data else if (posL != posR) break; // Digit positions mismatch else if (left.left(posL) != right.left(posR)) break; // Strings' subsets before digit do not match QString temp; while (posL < left.size()) { if (left.at(posL).isDigit()) temp += left.at(posL); else break; posL++; } int numL = temp.toInt(); temp.clear(); while (posR < right.size()) { if (right.at(posR).isDigit()) temp += right.at(posR); else break; posR++; } int numR = temp.toInt(); if (numL != numR) { result = (numL < numR); return true; } // Strings + digits do match and we haven't hit string end // Do another round left.remove(0, posL); right.remove(0, posR); } while (true); return false; } #endif // FIXME: Remove the 'localized' variable, when the webui API is reworked // to send numbers instead of strings with suffixes QString misc::accurateDoubleToString(const double &n, const int &precision, bool localized) { /* HACK because QString rounds up. Eg QString::number(0.999*100.0, 'f' ,1) == 99.9 ** but QString::number(0.9999*100.0, 'f' ,1) == 100.0 The problem manifests when ** the number has more digits after the decimal than we want AND the digit after ** our 'wanted' is >= 5. In this case our last digit gets rounded up. So for each ** precision we add an extra 0 behind 1 in the below algorithm. */ double prec = std::pow(10.0, precision); if (localized) return QLocale::system().toString(std::floor(n*prec)/prec, 'f', precision); else return QString::number(std::floor(n*prec)/prec, 'f', precision); }