mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-29 13:58:51 +03:00
commit
d07ece53e6
96 changed files with 5007 additions and 3125 deletions
|
@ -19,6 +19,7 @@ bittorrent/torrentinfo.h
|
|||
bittorrent/tracker.h
|
||||
bittorrent/trackerentry.h
|
||||
http/connection.h
|
||||
http/httperror.h
|
||||
http/irequesthandler.h
|
||||
http/requestparser.h
|
||||
http/responsebuilder.h
|
||||
|
@ -51,6 +52,7 @@ utils/random.h
|
|||
utils/string.h
|
||||
utils/version.h
|
||||
asyncfilestorage.h
|
||||
exceptions.h
|
||||
filesystemwatcher.h
|
||||
global.h
|
||||
iconprovider.h
|
||||
|
@ -84,6 +86,7 @@ bittorrent/torrentinfo.cpp
|
|||
bittorrent/tracker.cpp
|
||||
bittorrent/trackerentry.cpp
|
||||
http/connection.cpp
|
||||
http/httperror.cpp
|
||||
http/requestparser.cpp
|
||||
http/responsebuilder.cpp
|
||||
http/responsegenerator.cpp
|
||||
|
@ -113,6 +116,7 @@ utils/net.cpp
|
|||
utils/random.cpp
|
||||
utils/string.cpp
|
||||
asyncfilestorage.cpp
|
||||
exceptions.cpp
|
||||
filesystemwatcher.cpp
|
||||
iconprovider.cpp
|
||||
logger.cpp
|
||||
|
|
|
@ -17,9 +17,11 @@ HEADERS += \
|
|||
$$PWD/bittorrent/torrentinfo.h \
|
||||
$$PWD/bittorrent/tracker.h \
|
||||
$$PWD/bittorrent/trackerentry.h \
|
||||
$$PWD/exceptions.h \
|
||||
$$PWD/filesystemwatcher.h \
|
||||
$$PWD/global.h \
|
||||
$$PWD/http/connection.h \
|
||||
$$PWD/http/httperror.h \
|
||||
$$PWD/http/irequesthandler.h \
|
||||
$$PWD/http/requestparser.h \
|
||||
$$PWD/http/responsebuilder.h \
|
||||
|
@ -82,8 +84,10 @@ SOURCES += \
|
|||
$$PWD/bittorrent/torrentinfo.cpp \
|
||||
$$PWD/bittorrent/tracker.cpp \
|
||||
$$PWD/bittorrent/trackerentry.cpp \
|
||||
$$PWD/exceptions.cpp \
|
||||
$$PWD/filesystemwatcher.cpp \
|
||||
$$PWD/http/connection.cpp \
|
||||
$$PWD/http/httperror.cpp \
|
||||
$$PWD/http/requestparser.cpp \
|
||||
$$PWD/http/responsebuilder.cpp \
|
||||
$$PWD/http/responsegenerator.cpp \
|
||||
|
|
|
@ -60,23 +60,32 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
|
|||
return *this;
|
||||
}
|
||||
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString &error)
|
||||
TorrentInfo TorrentInfo::load(const QByteArray &data, QString *error) noexcept
|
||||
{
|
||||
error.clear();
|
||||
libt::error_code ec;
|
||||
TorrentInfo info(NativePtr(new libt::torrent_info(Utils::Fs::toNativePath(path).toStdString(), ec)));
|
||||
if (ec) {
|
||||
error = QString::fromUtf8(ec.message().c_str());
|
||||
qDebug("Cannot load .torrent file: %s", qUtf8Printable(error));
|
||||
TorrentInfo info(NativePtr(new libt::torrent_info(data.constData(), data.size(), ec)));
|
||||
if (error) {
|
||||
if (ec)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
else
|
||||
error->clear();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path)
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexcept
|
||||
{
|
||||
QString error;
|
||||
return loadFromFile(path, error);
|
||||
libt::error_code ec;
|
||||
TorrentInfo info(NativePtr(new libt::torrent_info(Utils::Fs::toNativePath(path).toStdString(), ec)));
|
||||
if (error) {
|
||||
if (ec)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
else
|
||||
error->clear();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
bool TorrentInfo::isValid() const
|
||||
|
|
|
@ -63,8 +63,8 @@ namespace BitTorrent
|
|||
explicit TorrentInfo(NativeConstPtr nativeInfo = NativeConstPtr());
|
||||
TorrentInfo(const TorrentInfo &other);
|
||||
|
||||
static TorrentInfo loadFromFile(const QString &path, QString &error);
|
||||
static TorrentInfo loadFromFile(const QString &path);
|
||||
static TorrentInfo load(const QByteArray &data, QString *error = nullptr) noexcept;
|
||||
static TorrentInfo loadFromFile(const QString &path, QString *error = nullptr) noexcept;
|
||||
|
||||
TorrentInfo &operator=(const TorrentInfo &other);
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ libtorrent::entry Peer::toEntry(bool noPeerId) const
|
|||
// Tracker
|
||||
|
||||
Tracker::Tracker(QObject *parent)
|
||||
: Http::ResponseBuilder(parent)
|
||||
: QObject(parent)
|
||||
, m_server(new Http::Server(this, this))
|
||||
{
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#define BITTORRENT_TRACKER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
|
||||
#include "base/http/irequesthandler.h"
|
||||
#include "base/http/responsebuilder.h"
|
||||
|
@ -75,7 +76,7 @@ namespace BitTorrent
|
|||
|
||||
/* Basic Bittorrent tracker implementation in Qt */
|
||||
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
|
||||
class Tracker : public Http::ResponseBuilder, public Http::IRequestHandler
|
||||
class Tracker : public QObject, public Http::IRequestHandler, private Http::ResponseBuilder
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Tracker)
|
||||
|
|
40
src/base/exceptions.cpp
Normal file
40
src/base/exceptions.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "exceptions.h"
|
||||
|
||||
RuntimeError::RuntimeError(const QString &message)
|
||||
: std::runtime_error {message.toUtf8().data()}
|
||||
, m_message {message}
|
||||
{
|
||||
}
|
||||
|
||||
QString RuntimeError::message() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006-2012 Ishan Arora and Christophe Dumez
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -24,24 +24,19 @@
|
|||
* 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
|
||||
*/
|
||||
|
||||
#ifndef PREFJSON_H
|
||||
#define PREFJSON_H
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include <QString>
|
||||
|
||||
class prefjson
|
||||
class RuntimeError : public std::runtime_error
|
||||
{
|
||||
private:
|
||||
prefjson();
|
||||
|
||||
public:
|
||||
static QByteArray getPreferences();
|
||||
static void setPreferences(const QString& json);
|
||||
explicit RuntimeError(const QString &message = "");
|
||||
QString message() const;
|
||||
|
||||
private:
|
||||
const QString m_message;
|
||||
};
|
||||
|
||||
#endif // PREFJSON_H
|
81
src/base/http/httperror.cpp
Normal file
81
src/base/http/httperror.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "httperror.h"
|
||||
|
||||
HTTPError::HTTPError(int statusCode, const QString &statusText, const QString &message)
|
||||
: RuntimeError {message}
|
||||
, m_statusCode {statusCode}
|
||||
, m_statusText {statusText}
|
||||
{
|
||||
}
|
||||
|
||||
int HTTPError::statusCode() const
|
||||
{
|
||||
return m_statusCode;
|
||||
}
|
||||
|
||||
QString HTTPError::statusText() const
|
||||
{
|
||||
return m_statusText;
|
||||
}
|
||||
|
||||
BadRequestHTTPError::BadRequestHTTPError(const QString &message)
|
||||
: HTTPError(400, QLatin1String("Bad Request"), message)
|
||||
{
|
||||
}
|
||||
|
||||
ConflictHTTPError::ConflictHTTPError(const QString &message)
|
||||
: HTTPError(409, QLatin1String("Conflict"), message)
|
||||
{
|
||||
}
|
||||
|
||||
ForbiddenHTTPError::ForbiddenHTTPError(const QString &message)
|
||||
: HTTPError(403, QLatin1String("Forbidden"), message)
|
||||
{
|
||||
}
|
||||
|
||||
NotFoundHTTPError::NotFoundHTTPError(const QString &message)
|
||||
: HTTPError(404, QLatin1String("Not Found"), message)
|
||||
{
|
||||
}
|
||||
|
||||
UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &message)
|
||||
: HTTPError(415, QLatin1String("Unsupported Media Type"), message)
|
||||
{
|
||||
}
|
||||
|
||||
UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message)
|
||||
: HTTPError(401, QLatin1String("Unauthorized"), message)
|
||||
{
|
||||
}
|
||||
|
||||
InternalServerErrorHTTPError::InternalServerErrorHTTPError(const QString &message)
|
||||
: HTTPError(500, QLatin1String("Internal Server Error"), message)
|
||||
{
|
||||
}
|
86
src/base/http/httperror.h
Normal file
86
src/base/http/httperror.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "base/exceptions.h"
|
||||
|
||||
class HTTPError : public RuntimeError
|
||||
{
|
||||
public:
|
||||
HTTPError(int statusCode, const QString &statusText, const QString &message = "");
|
||||
|
||||
int statusCode() const;
|
||||
QString statusText() const;
|
||||
|
||||
private:
|
||||
const int m_statusCode;
|
||||
const QString m_statusText;
|
||||
};
|
||||
|
||||
class BadRequestHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit BadRequestHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class ForbiddenHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit ForbiddenHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class NotFoundHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit NotFoundHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class ConflictHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit ConflictHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class UnsupportedMediaTypeHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit UnsupportedMediaTypeHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class UnauthorizedHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit UnauthorizedHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class InternalServerErrorHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit InternalServerErrorHTTPError(const QString &message = "");
|
||||
};
|
|
@ -30,11 +30,6 @@
|
|||
|
||||
using namespace Http;
|
||||
|
||||
ResponseBuilder::ResponseBuilder(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ResponseBuilder::status(uint code, const QString &text)
|
||||
{
|
||||
m_response.status = ResponseStatus(code, text);
|
||||
|
|
|
@ -29,17 +29,13 @@
|
|||
#ifndef HTTP_RESPONSEBUILDER_H
|
||||
#define HTTP_RESPONSEBUILDER_H
|
||||
|
||||
#include <QObject>
|
||||
#include "types.h"
|
||||
|
||||
namespace Http
|
||||
{
|
||||
class ResponseBuilder : public QObject
|
||||
class ResponseBuilder
|
||||
{
|
||||
public:
|
||||
explicit ResponseBuilder(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void status(uint code = 200, const QString &text = QLatin1String("OK"));
|
||||
void header(const QString &name, const QString &value);
|
||||
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
|
||||
namespace Http
|
||||
{
|
||||
const char METHOD_GET[] = "GET";
|
||||
const char METHOD_POST[] = "POST";
|
||||
|
||||
const char HEADER_CACHE_CONTROL[] = "cache-control";
|
||||
const char HEADER_CONTENT_ENCODING[] = "content-encoding";
|
||||
const char HEADER_CONTENT_LENGTH[] = "content-length";
|
||||
|
@ -52,13 +55,14 @@ namespace Http
|
|||
const char HEADER_X_FRAME_OPTIONS[] = "x-frame-options";
|
||||
const char HEADER_X_XSS_PROTECTION[] = "x-xss-protection";
|
||||
|
||||
const char CONTENT_TYPE_CSS[] = "text/css; charset=UTF-8";
|
||||
const char CONTENT_TYPE_GIF[] = "image/gif";
|
||||
const char CONTENT_TYPE_HTML[] = "text/html; charset=UTF-8";
|
||||
const char CONTENT_TYPE_JS[] = "application/javascript; charset=UTF-8";
|
||||
const char CONTENT_TYPE_HTML[] = "text/html";
|
||||
const char CONTENT_TYPE_JS[] = "application/javascript";
|
||||
const char CONTENT_TYPE_JSON[] = "application/json";
|
||||
const char CONTENT_TYPE_BMP[] = "image/bmp";
|
||||
const char CONTENT_TYPE_GIF[] = "image/gif";
|
||||
const char CONTENT_TYPE_JPEG[] = "image/jpeg";
|
||||
const char CONTENT_TYPE_PNG[] = "image/png";
|
||||
const char CONTENT_TYPE_TXT[] = "text/plain; charset=UTF-8";
|
||||
const char CONTENT_TYPE_TXT[] = "text/plain";
|
||||
const char CONTENT_TYPE_SVG[] = "image/svg+xml";
|
||||
|
||||
// portability: "\r\n" doesn't guarantee mapping to the correct value
|
||||
|
|
|
@ -609,6 +609,26 @@ void Preferences::setWebUiHttpsKey(const QByteArray &data)
|
|||
setValue("Preferences/WebUI/HTTPS/Key", data);
|
||||
}
|
||||
|
||||
bool Preferences::isAltWebUiEnabled() const
|
||||
{
|
||||
return value("Preferences/WebUI/AlternativeUIEnabled", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setAltWebUiEnabled(bool enabled)
|
||||
{
|
||||
setValue("Preferences/WebUI/AlternativeUIEnabled", enabled);
|
||||
}
|
||||
|
||||
QString Preferences::getWebUiRootFolder() const
|
||||
{
|
||||
return value("Preferences/WebUI/RootFolder").toString();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiRootFolder(const QString &path)
|
||||
{
|
||||
setValue("Preferences/WebUI/RootFolder", path);
|
||||
}
|
||||
|
||||
bool Preferences::isDynDNSEnabled() const
|
||||
{
|
||||
return value("Preferences/DynDNS/Enabled", false).toBool();
|
||||
|
|
|
@ -204,6 +204,10 @@ public:
|
|||
void setWebUiHttpsCertificate(const QByteArray &data);
|
||||
QByteArray getWebUiHttpsKey() const;
|
||||
void setWebUiHttpsKey(const QByteArray &data);
|
||||
bool isAltWebUiEnabled() const;
|
||||
void setAltWebUiEnabled(bool enabled);
|
||||
QString getWebUiRootFolder() const;
|
||||
void setWebUiRootFolder(const QString &path);
|
||||
|
||||
// Dynamic DNS
|
||||
bool isDynDNSEnabled() const;
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
|
||||
#include "fs.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
|
@ -47,6 +51,7 @@
|
|||
#include <kernel/fs_info.h>
|
||||
#else
|
||||
#include <sys/vfs.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
|
@ -281,3 +286,17 @@ QString Utils::Fs::tempPath()
|
|||
QDir().mkdir(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
bool Utils::Fs::isRegularFile(const QString &path)
|
||||
{
|
||||
struct ::stat st;
|
||||
if (::stat(path.toUtf8().constData(), &st) != 0) {
|
||||
// analyse erno and log the error
|
||||
const auto err = errno;
|
||||
qDebug("Could not get file stats for path '%s'. Error: %s"
|
||||
, qUtf8Printable(path), qUtf8Printable(strerror(err)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return (st.st_mode & S_IFMT) == S_IFREG;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ namespace Utils
|
|||
bool sameFileNames(const QString &first, const QString &second);
|
||||
QString expandPath(const QString &path);
|
||||
QString expandPathAbs(const QString &path);
|
||||
bool isRegularFile(const QString &path);
|
||||
|
||||
bool smartRemoveEmptyFolderTree(const QString &path);
|
||||
bool forceRemove(const QString &filePath);
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
#include <QThreadStorage>
|
||||
#endif
|
||||
|
||||
#include "../tristatebool.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
class NaturalCompare
|
||||
|
@ -184,3 +186,19 @@ QString Utils::String::wildcardToRegex(const QString &pattern)
|
|||
{
|
||||
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
|
||||
}
|
||||
|
||||
bool Utils::String::parseBool(const QString &string, const bool defaultValue)
|
||||
{
|
||||
if (defaultValue)
|
||||
return (string.compare("false", Qt::CaseInsensitive) == 0) ? false : true;
|
||||
return (string.compare("true", Qt::CaseInsensitive) == 0) ? true : false;
|
||||
}
|
||||
|
||||
TriStateBool Utils::String::parseTriStateBool(const QString &string)
|
||||
{
|
||||
if (string.compare("true", Qt::CaseInsensitive) == 0)
|
||||
return TriStateBool::True;
|
||||
if (string.compare("false", Qt::CaseInsensitive) == 0)
|
||||
return TriStateBool::False;
|
||||
return TriStateBool::Undefined;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
class QByteArray;
|
||||
class QLatin1String;
|
||||
class TriStateBool;
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
|
@ -66,6 +67,9 @@ namespace Utils
|
|||
|
||||
return str;
|
||||
}
|
||||
|
||||
bool parseBool(const QString &string, const bool defaultValue);
|
||||
TriStateBool parseTriStateBool(const QString &string);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -286,7 +286,7 @@ bool AddNewTorrentDialog::loadTorrent(const QString &torrentPath)
|
|||
|
||||
m_hasMetadata = true;
|
||||
QString error;
|
||||
m_torrentInfo = BitTorrent::TorrentInfo::loadFromFile(m_filePath, error);
|
||||
m_torrentInfo = BitTorrent::TorrentInfo::loadFromFile(m_filePath, &error);
|
||||
if (!m_torrentInfo.isValid()) {
|
||||
MessageBoxRaised::critical(this, tr("Invalid torrent"), tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.").arg(Utils::Fs::toNativePath(m_filePath)).arg(error));
|
||||
return false;
|
||||
|
|
|
@ -183,6 +183,9 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||
m_ui->groupFileAssociation->setVisible(false);
|
||||
#endif
|
||||
|
||||
m_ui->textWebUIRootFolder->setMode(FileSystemPathEdit::Mode::DirectoryOpen);
|
||||
m_ui->textWebUIRootFolder->setDialogCaption(tr("Choose Alternative UI files location"));
|
||||
|
||||
// Connect signals / slots
|
||||
// Shortcuts for frequently used signals that have more than one overload. They would require
|
||||
// type casts and that is why we declare required member pointer here instead.
|
||||
|
@ -367,6 +370,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||
connect(m_ui->domainNameTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->DNSUsernameTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->DNSPasswordTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->groupAltWebUI, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIRootFolder, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
#endif
|
||||
|
||||
// RSS tab
|
||||
|
@ -677,6 +682,9 @@ void OptionsDialog::saveOptions()
|
|||
pref->setDynDomainName(m_ui->domainNameTxt->text());
|
||||
pref->setDynDNSUsername(m_ui->DNSUsernameTxt->text());
|
||||
pref->setDynDNSPassword(m_ui->DNSPasswordTxt->text());
|
||||
// Alternative UI
|
||||
pref->setAltWebUiEnabled(m_ui->groupAltWebUI->isChecked());
|
||||
pref->setWebUiRootFolder(m_ui->textWebUIRootFolder->selectedPath());
|
||||
}
|
||||
// End Web UI
|
||||
// End preferences
|
||||
|
@ -1069,6 +1077,9 @@ void OptionsDialog::loadOptions()
|
|||
m_ui->domainNameTxt->setText(pref->getDynDomainName());
|
||||
m_ui->DNSUsernameTxt->setText(pref->getDynDNSUsername());
|
||||
m_ui->DNSPasswordTxt->setText(pref->getDynDNSPassword());
|
||||
|
||||
m_ui->groupAltWebUI->setChecked(pref->isAltWebUiEnabled());
|
||||
m_ui->textWebUIRootFolder->setSelectedPath(pref->getWebUiRootFolder());
|
||||
// End Web UI preferences
|
||||
}
|
||||
|
||||
|
|
|
@ -3014,6 +3014,31 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupAltWebUI">
|
||||
<property name="title">
|
||||
<string>Use alternative Web UI</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelWebUIRootFolder">
|
||||
<property name="text">
|
||||
<string>Files location:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="FileSystemPathLineEdit" name="textWebUIRootFolder" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="checkDynDNS">
|
||||
<property name="title">
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
set(QBT_WEBUI_HEADERS
|
||||
abstractwebapplication.h
|
||||
btjson.h
|
||||
api/apicontroller.h
|
||||
api/apierror.h
|
||||
api/appcontroller.h
|
||||
api/isessionmanager.h
|
||||
api/authcontroller.h
|
||||
api/logcontroller.h
|
||||
api/rsscontroller.h
|
||||
api/synccontroller.h
|
||||
api/torrentscontroller.h
|
||||
api/transfercontroller.h
|
||||
api/serialize/serialize_torrent.h
|
||||
extra_translations.h
|
||||
jsonutils.h
|
||||
prefjson.h
|
||||
webapplication.h
|
||||
websessiondata.h
|
||||
webui.h
|
||||
)
|
||||
|
||||
set(QBT_WEBUI_SOURCES
|
||||
abstractwebapplication.cpp
|
||||
btjson.cpp
|
||||
prefjson.cpp
|
||||
api/apicontroller.cpp
|
||||
api/apierror.cpp
|
||||
api/appcontroller.cpp
|
||||
api/authcontroller.cpp
|
||||
api/logcontroller.cpp
|
||||
api/rsscontroller.cpp
|
||||
api/synccontroller.cpp
|
||||
api/torrentscontroller.cpp
|
||||
api/transfercontroller.cpp
|
||||
api/serialize/serialize_torrent.cpp
|
||||
webapplication.cpp
|
||||
webui.cpp
|
||||
)
|
||||
|
|
|
@ -1,525 +0,0 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "abstractwebapplication.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QNetworkCookie>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/random.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "websessiondata.h"
|
||||
|
||||
// UnbanTimer
|
||||
|
||||
class UnbanTimer: public QTimer
|
||||
{
|
||||
public:
|
||||
UnbanTimer(const QHostAddress& peer_ip, QObject *parent)
|
||||
: QTimer(parent), m_peerIp(peer_ip)
|
||||
{
|
||||
setSingleShot(true);
|
||||
setInterval(BAN_TIME);
|
||||
}
|
||||
|
||||
inline const QHostAddress& peerIp() const { return m_peerIp; }
|
||||
|
||||
private:
|
||||
QHostAddress m_peerIp;
|
||||
};
|
||||
|
||||
// WebSession
|
||||
|
||||
struct WebSession
|
||||
{
|
||||
const QString id;
|
||||
uint timestamp;
|
||||
WebSessionData data;
|
||||
|
||||
WebSession(const QString& id)
|
||||
: id(id)
|
||||
{
|
||||
updateTimestamp();
|
||||
}
|
||||
|
||||
void updateTimestamp()
|
||||
{
|
||||
timestamp = QDateTime::currentDateTime().toTime_t();
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
inline QUrl urlFromHostHeader(const QString &hostHeader)
|
||||
{
|
||||
if (!hostHeader.contains(QLatin1String("://")))
|
||||
return QUrl(QLatin1String("http://") + hostHeader);
|
||||
return hostHeader;
|
||||
}
|
||||
}
|
||||
|
||||
// AbstractWebApplication
|
||||
|
||||
AbstractWebApplication::AbstractWebApplication(QObject *parent)
|
||||
: Http::ResponseBuilder(parent)
|
||||
, session_(0)
|
||||
{
|
||||
QTimer *timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &AbstractWebApplication::removeInactiveSessions);
|
||||
timer->start(60 * 1000); // 1 min.
|
||||
|
||||
reloadDomainList();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &AbstractWebApplication::reloadDomainList);
|
||||
}
|
||||
|
||||
AbstractWebApplication::~AbstractWebApplication()
|
||||
{
|
||||
// cleanup sessions data
|
||||
qDeleteAll(sessions_);
|
||||
}
|
||||
|
||||
Http::Response AbstractWebApplication::processRequest(const Http::Request &request, const Http::Environment &env)
|
||||
{
|
||||
session_ = 0;
|
||||
request_ = request;
|
||||
env_ = env;
|
||||
|
||||
// clear response
|
||||
clear();
|
||||
|
||||
// avoid clickjacking attacks
|
||||
header(Http::HEADER_X_FRAME_OPTIONS, "SAMEORIGIN");
|
||||
header(Http::HEADER_X_XSS_PROTECTION, "1; mode=block");
|
||||
header(Http::HEADER_X_CONTENT_TYPE_OPTIONS, "nosniff");
|
||||
header(Http::HEADER_CONTENT_SECURITY_POLICY, "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none';");
|
||||
|
||||
// block cross-site requests
|
||||
if (isCrossSiteRequest(request_) || !validateHostHeader(domainList)) {
|
||||
status(401, "Unauthorized");
|
||||
return response();
|
||||
}
|
||||
|
||||
sessionInitialize();
|
||||
if (!sessionActive() && !isAuthNeeded())
|
||||
sessionStart();
|
||||
|
||||
if (isBanned()) {
|
||||
status(403, "Forbidden");
|
||||
print(QObject::tr("Your IP address has been banned after too many failed authentication attempts."), Http::CONTENT_TYPE_TXT);
|
||||
}
|
||||
else {
|
||||
doProcessRequest();
|
||||
}
|
||||
|
||||
return response();
|
||||
}
|
||||
|
||||
void AbstractWebApplication::UnbanTimerEvent()
|
||||
{
|
||||
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender());
|
||||
qDebug("Ban period has expired for %s", qUtf8Printable(ubantimer->peerIp().toString()));
|
||||
clientFailedAttempts_.remove(ubantimer->peerIp());
|
||||
ubantimer->deleteLater();
|
||||
}
|
||||
|
||||
void AbstractWebApplication::removeInactiveSessions()
|
||||
{
|
||||
const uint now = QDateTime::currentDateTime().toTime_t();
|
||||
|
||||
foreach (const QString &id, sessions_.keys()) {
|
||||
if ((now - sessions_[id]->timestamp) > INACTIVE_TIME)
|
||||
delete sessions_.take(id);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractWebApplication::reloadDomainList()
|
||||
{
|
||||
domainList = Preferences::instance()->getServerDomains().split(';', QString::SkipEmptyParts);
|
||||
std::for_each(domainList.begin(), domainList.end(), [](QString &entry){ entry = entry.trimmed(); });
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::sessionInitialize()
|
||||
{
|
||||
if (session_ == 0)
|
||||
{
|
||||
const QString sessionId = parseCookie(request_).value(C_SID);
|
||||
|
||||
// TODO: Additional session check
|
||||
|
||||
if (!sessionId.isEmpty()) {
|
||||
if (sessions_.contains(sessionId)) {
|
||||
session_ = sessions_[sessionId];
|
||||
session_->updateTimestamp();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
qDebug() << Q_FUNC_INFO << "session does not exist!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::readFile(const QString& path, QByteArray &data, QString &type)
|
||||
{
|
||||
QString ext = "";
|
||||
int index = path.lastIndexOf('.') + 1;
|
||||
if (index > 0)
|
||||
ext = path.mid(index);
|
||||
|
||||
// find translated file in cache
|
||||
if (translatedFiles_.contains(path)) {
|
||||
data = translatedFiles_[path];
|
||||
}
|
||||
else {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qDebug("File %s was not found!", qUtf8Printable(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
data = file.readAll();
|
||||
file.close();
|
||||
|
||||
// Translate the file
|
||||
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js"))) {
|
||||
QString dataStr = QString::fromUtf8(data.constData());
|
||||
translateDocument(dataStr);
|
||||
|
||||
if (path.endsWith("about.html") || path.endsWith("index.html") || path.endsWith("client.js"))
|
||||
dataStr.replace("${VERSION}", QBT_VERSION);
|
||||
|
||||
data = dataStr.toUtf8();
|
||||
translatedFiles_[path] = data; // cashing translated file
|
||||
}
|
||||
}
|
||||
|
||||
type = CONTENT_TYPE_BY_EXT[ext];
|
||||
return true;
|
||||
}
|
||||
|
||||
WebSessionData *AbstractWebApplication::session()
|
||||
{
|
||||
Q_ASSERT(session_ != 0);
|
||||
return &session_->data;
|
||||
}
|
||||
|
||||
|
||||
QString AbstractWebApplication::generateSid()
|
||||
{
|
||||
QString sid;
|
||||
|
||||
do {
|
||||
const size_t size = 6;
|
||||
quint32 tmp[size];
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
tmp[i] = Utils::Random::rand();
|
||||
|
||||
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64();
|
||||
}
|
||||
while (sessions_.contains(sid));
|
||||
|
||||
return sid;
|
||||
}
|
||||
|
||||
void AbstractWebApplication::translateDocument(QString& data)
|
||||
{
|
||||
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR(\\[CONTEXT=([a-zA-Z_][a-zA-Z0-9_]*)\\])");
|
||||
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?");
|
||||
int i = 0;
|
||||
bool found = true;
|
||||
|
||||
const QString locale = Preferences::instance()->getLocale();
|
||||
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB");
|
||||
|
||||
while(i < data.size() && found) {
|
||||
i = regex.indexIn(data, i);
|
||||
if (i >= 0) {
|
||||
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data());
|
||||
QByteArray word = regex.cap(1).toUtf8();
|
||||
|
||||
QString translation = word;
|
||||
if (isTranslationNeeded) {
|
||||
QString context = regex.cap(4);
|
||||
translation = qApp->translate(context.toUtf8().constData(), word.constData(), 0, 1);
|
||||
}
|
||||
// Remove keyboard shortcuts
|
||||
translation.replace(mnemonic, "");
|
||||
|
||||
// Use HTML code for quotes to prevent issues with JS
|
||||
translation.replace("'", "'");
|
||||
translation.replace("\"", """);
|
||||
|
||||
data.replace(i, regex.matchedLength(), translation);
|
||||
i += translation.length();
|
||||
}
|
||||
else {
|
||||
found = false; // no more translatable strings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::isBanned() const
|
||||
{
|
||||
return clientFailedAttempts_.value(env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS;
|
||||
}
|
||||
|
||||
int AbstractWebApplication::failedAttempts() const
|
||||
{
|
||||
return clientFailedAttempts_.value(env_.clientAddress, 0);
|
||||
}
|
||||
|
||||
void AbstractWebApplication::resetFailedAttempts()
|
||||
{
|
||||
clientFailedAttempts_.remove(env_.clientAddress);
|
||||
}
|
||||
|
||||
void AbstractWebApplication::increaseFailedAttempts()
|
||||
{
|
||||
const int nb_fail = clientFailedAttempts_.value(env_.clientAddress, 0) + 1;
|
||||
|
||||
clientFailedAttempts_[env_.clientAddress] = nb_fail;
|
||||
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS) {
|
||||
// Max number of failed attempts reached
|
||||
// Start ban period
|
||||
UnbanTimer* ubantimer = new UnbanTimer(env_.clientAddress, this);
|
||||
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent()));
|
||||
ubantimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::isAuthNeeded()
|
||||
{
|
||||
qDebug("Checking auth rules against client address %s", qPrintable(env().clientAddress.toString()));
|
||||
const Preferences *pref = Preferences::instance();
|
||||
if (!pref->isWebUiLocalAuthEnabled() && Utils::Net::isLoopbackAddress(env().clientAddress))
|
||||
return false;
|
||||
if (pref->isWebUiAuthSubnetWhitelistEnabled() && Utils::Net::isIPInRange(env().clientAddress, pref->getWebUiAuthSubnetWhitelist()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbstractWebApplication::printFile(const QString& path)
|
||||
{
|
||||
QByteArray data;
|
||||
QString type;
|
||||
|
||||
if (!readFile(path, data, type)) {
|
||||
status(404, "Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
print(data, type);
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::sessionStart()
|
||||
{
|
||||
if (session_ == 0) {
|
||||
session_ = new WebSession(generateSid());
|
||||
sessions_[session_->id] = session_;
|
||||
|
||||
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setPath(QLatin1String("/"));
|
||||
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::sessionEnd()
|
||||
{
|
||||
if ((session_ != 0) && (sessions_.contains(session_->id))) {
|
||||
QNetworkCookie cookie(C_SID);
|
||||
cookie.setPath(QLatin1String("/"));
|
||||
cookie.setExpirationDate(QDateTime::currentDateTime().addDays(-1));
|
||||
|
||||
sessions_.remove(session_->id);
|
||||
delete session_;
|
||||
session_ = 0;
|
||||
|
||||
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString AbstractWebApplication::saveTmpFile(const QByteArray &data)
|
||||
{
|
||||
QTemporaryFile tmpfile(Utils::Fs::tempPath() + "XXXXXX.torrent");
|
||||
tmpfile.setAutoRemove(false);
|
||||
if (tmpfile.open()) {
|
||||
tmpfile.write(data);
|
||||
tmpfile.close();
|
||||
return tmpfile.fileName();
|
||||
}
|
||||
|
||||
qWarning() << "I/O Error: Could not create temporary file";
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::isCrossSiteRequest(const Http::Request &request) const
|
||||
{
|
||||
// https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers
|
||||
|
||||
const auto isSameOrigin = [](const QUrl &left, const QUrl &right) -> bool
|
||||
{
|
||||
// [rfc6454] 5. Comparing Origins
|
||||
return ((left.port() == right.port())
|
||||
// && (left.scheme() == right.scheme()) // not present in this context
|
||||
&& (left.host() == right.host()));
|
||||
};
|
||||
|
||||
const QString targetOrigin = request.headers.value(Http::HEADER_X_FORWARDED_HOST, request.headers.value(Http::HEADER_HOST));
|
||||
const QString originValue = request.headers.value(Http::HEADER_ORIGIN);
|
||||
const QString refererValue = request.headers.value(Http::HEADER_REFERER);
|
||||
|
||||
if (originValue.isEmpty() && refererValue.isEmpty()) {
|
||||
// owasp.org recommends to block this request, but doing so will inevitably lead Web API users to spoof headers
|
||||
// so lets be permissive here
|
||||
return false;
|
||||
}
|
||||
|
||||
// sent with CORS requests, as well as with POST requests
|
||||
if (!originValue.isEmpty()) {
|
||||
const bool isInvalid = !isSameOrigin(urlFromHostHeader(targetOrigin), originValue);
|
||||
if (isInvalid)
|
||||
Logger::instance()->addMessage(tr("WebUI: Origin header & Target origin mismatch!") + "\n"
|
||||
+ tr("Source IP: '%1'. Origin header: '%2'. Target origin: '%3'")
|
||||
.arg(env_.clientAddress.toString()).arg(originValue).arg(targetOrigin)
|
||||
, Log::WARNING);
|
||||
return isInvalid;
|
||||
}
|
||||
|
||||
if (!refererValue.isEmpty()) {
|
||||
const bool isInvalid = !isSameOrigin(urlFromHostHeader(targetOrigin), refererValue);
|
||||
if (isInvalid)
|
||||
Logger::instance()->addMessage(tr("WebUI: Referer header & Target origin mismatch!") + "\n"
|
||||
+ tr("Source IP: '%1'. Referer header: '%2'. Target origin: '%3'")
|
||||
.arg(env_.clientAddress.toString()).arg(refererValue).arg(targetOrigin)
|
||||
, Log::WARNING);
|
||||
return isInvalid;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AbstractWebApplication::validateHostHeader(const QStringList &domains) const
|
||||
{
|
||||
const QUrl hostHeader = urlFromHostHeader(request().headers[Http::HEADER_HOST]);
|
||||
const QString requestHost = hostHeader.host();
|
||||
|
||||
// (if present) try matching host header's port with local port
|
||||
const int requestPort = hostHeader.port();
|
||||
if ((requestPort != -1) && (env().localPort != requestPort)) {
|
||||
Logger::instance()->addMessage(tr("WebUI: Invalid Host header, port mismatch.") + "\n"
|
||||
+ tr("Request source IP: '%1'. Server port: '%2'. Received Host header: '%3'")
|
||||
.arg(env().clientAddress.toString()).arg(env().localPort)
|
||||
.arg(request().headers[Http::HEADER_HOST])
|
||||
, Log::WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
// try matching host header with local address
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
|
||||
const bool sameAddr = env().localAddress.isEqual(QHostAddress(requestHost));
|
||||
#else
|
||||
const auto equal = [](const Q_IPV6ADDR &l, const Q_IPV6ADDR &r) -> bool {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (l[i] != r[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const bool sameAddr = equal(env().localAddress.toIPv6Address(), QHostAddress(requestHost).toIPv6Address());
|
||||
#endif
|
||||
|
||||
if (sameAddr)
|
||||
return true;
|
||||
|
||||
// try matching host header with domain list
|
||||
for (const auto &domain : domains) {
|
||||
QRegExp domainRegex(domain, Qt::CaseInsensitive, QRegExp::Wildcard);
|
||||
if (requestHost.contains(domainRegex))
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger::instance()->addMessage(tr("WebUI: Invalid Host header.") + "\n"
|
||||
+ tr("Request source IP: '%1'. Received Host header: '%2'")
|
||||
.arg(env().clientAddress.toString()).arg(request().headers[Http::HEADER_HOST])
|
||||
, Log::WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
const QStringMap AbstractWebApplication::CONTENT_TYPE_BY_EXT = {
|
||||
{ "htm", Http::CONTENT_TYPE_HTML },
|
||||
{ "html", Http::CONTENT_TYPE_HTML },
|
||||
{ "css", Http::CONTENT_TYPE_CSS },
|
||||
{ "gif", Http::CONTENT_TYPE_GIF },
|
||||
{ "png", Http::CONTENT_TYPE_PNG },
|
||||
{ "js", Http::CONTENT_TYPE_JS },
|
||||
{ "svg", Http::CONTENT_TYPE_SVG }
|
||||
};
|
||||
|
||||
QStringMap AbstractWebApplication::parseCookie(const Http::Request &request) const
|
||||
{
|
||||
// [rfc6265] 4.2.1. Syntax
|
||||
QStringMap ret;
|
||||
const QString cookieStr = request.headers.value(QLatin1String("cookie"));
|
||||
const QVector<QStringRef> cookies = cookieStr.splitRef(';', QString::SkipEmptyParts);
|
||||
|
||||
for (const auto &cookie : cookies) {
|
||||
const int idx = cookie.indexOf('=');
|
||||
if (idx < 0)
|
||||
continue;
|
||||
|
||||
const QString name = cookie.left(idx).trimmed().toString();
|
||||
const QString value = Utils::String::unquote(cookie.mid(idx + 1).trimmed())
|
||||
.toString();
|
||||
ret.insert(name, value);
|
||||
}
|
||||
return ret;
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ABSTRACTWEBAPPLICATION_H
|
||||
#define ABSTRACTWEBAPPLICATION_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
#include "base/http/irequesthandler.h"
|
||||
#include "base/http/responsebuilder.h"
|
||||
#include "base/http/types.h"
|
||||
|
||||
struct WebSession;
|
||||
struct WebSessionData;
|
||||
|
||||
const char C_SID[] = "SID"; // name of session id cookie
|
||||
const int BAN_TIME = 3600000; // 1 hour
|
||||
const int INACTIVE_TIME = 900; // Session inactive time (in secs = 15 min.)
|
||||
const int MAX_AUTH_FAILED_ATTEMPTS = 5;
|
||||
|
||||
class AbstractWebApplication : public Http::ResponseBuilder, public Http::IRequestHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(AbstractWebApplication)
|
||||
|
||||
public:
|
||||
explicit AbstractWebApplication(QObject *parent = 0);
|
||||
virtual ~AbstractWebApplication();
|
||||
|
||||
Http::Response processRequest(const Http::Request &request, const Http::Environment &env) final;
|
||||
|
||||
protected:
|
||||
virtual void doProcessRequest() = 0;
|
||||
|
||||
bool isBanned() const;
|
||||
int failedAttempts() const;
|
||||
void resetFailedAttempts();
|
||||
void increaseFailedAttempts();
|
||||
|
||||
void printFile(const QString &path);
|
||||
|
||||
// Session management
|
||||
bool sessionActive() const { return session_ != 0; }
|
||||
bool sessionStart();
|
||||
bool sessionEnd();
|
||||
|
||||
bool isAuthNeeded();
|
||||
|
||||
bool readFile(const QString &path, QByteArray &data, QString &type);
|
||||
|
||||
// save data to temporary file on disk and return its name (or empty string if fails)
|
||||
static QString saveTmpFile(const QByteArray &data);
|
||||
|
||||
WebSessionData *session();
|
||||
const Http::Request &request() const { return request_; }
|
||||
const Http::Environment &env() const { return env_; }
|
||||
|
||||
private slots:
|
||||
void UnbanTimerEvent();
|
||||
void removeInactiveSessions();
|
||||
|
||||
void reloadDomainList();
|
||||
|
||||
private:
|
||||
// Persistent data
|
||||
QMap<QString, WebSession *> sessions_;
|
||||
QHash<QHostAddress, int> clientFailedAttempts_;
|
||||
QMap<QString, QByteArray> translatedFiles_;
|
||||
|
||||
// Current data
|
||||
WebSession *session_;
|
||||
Http::Request request_;
|
||||
Http::Environment env_;
|
||||
|
||||
QStringList domainList;
|
||||
|
||||
QString generateSid();
|
||||
bool sessionInitialize();
|
||||
|
||||
QStringMap parseCookie(const Http::Request &request) const;
|
||||
bool isCrossSiteRequest(const Http::Request &request) const;
|
||||
bool validateHostHeader(const QStringList &domains) const;
|
||||
|
||||
static void translateDocument(QString &data);
|
||||
|
||||
static const QStringMap CONTENT_TYPE_BY_EXT;
|
||||
};
|
||||
|
||||
#endif // ABSTRACTWEBAPPLICATION_H
|
91
src/webui/api/apicontroller.cpp
Normal file
91
src/webui/api/apicontroller.cpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QMetaObject>
|
||||
|
||||
#include "apierror.h"
|
||||
|
||||
APIController::APIController(ISessionManager *sessionManager, QObject *parent)
|
||||
: QObject {parent}
|
||||
, m_sessionManager {sessionManager}
|
||||
{
|
||||
}
|
||||
|
||||
QVariant APIController::run(const QString &action, const StringMap ¶ms, const DataMap &data)
|
||||
{
|
||||
m_result.clear(); // clear result
|
||||
m_params = params;
|
||||
m_data = data;
|
||||
|
||||
const QString methodName {action + QLatin1String("Action")};
|
||||
if (!QMetaObject::invokeMethod(this, methodName.toLatin1().constData()))
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
return m_result;
|
||||
}
|
||||
|
||||
ISessionManager *APIController::sessionManager() const
|
||||
{
|
||||
return m_sessionManager;
|
||||
}
|
||||
|
||||
const StringMap &APIController::params() const
|
||||
{
|
||||
return m_params;
|
||||
}
|
||||
|
||||
const DataMap &APIController::data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
void APIController::checkParams(const QSet<QString> &requiredParams) const
|
||||
{
|
||||
const QSet<QString> params {this->params().keys().toSet()};
|
||||
|
||||
if (!params.contains(requiredParams))
|
||||
throw APIError(APIErrorType::BadParams);
|
||||
}
|
||||
|
||||
void APIController::setResult(const QString &result)
|
||||
{
|
||||
m_result = result;
|
||||
}
|
||||
|
||||
void APIController::setResult(const QJsonArray &result)
|
||||
{
|
||||
m_result = QJsonDocument(result);
|
||||
}
|
||||
|
||||
void APIController::setResult(const QJsonObject &result)
|
||||
{
|
||||
m_result = QJsonDocument(result);
|
||||
}
|
72
src/webui/api/apicontroller.h
Normal file
72
src/webui/api/apicontroller.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
struct ISessionManager;
|
||||
using StringMap = QMap<QString, QString>;
|
||||
using DataMap = QMap<QString, QByteArray>;
|
||||
|
||||
class APIController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(APIController)
|
||||
|
||||
#ifndef Q_MOC_RUN
|
||||
#define WEBAPI_PUBLIC
|
||||
#define WEBAPI_PRIVATE
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit APIController(ISessionManager *sessionManager, QObject *parent = nullptr);
|
||||
|
||||
QVariant run(const QString &action, const StringMap ¶ms, const DataMap &data = {});
|
||||
|
||||
ISessionManager *sessionManager() const;
|
||||
|
||||
protected:
|
||||
const StringMap ¶ms() const;
|
||||
const DataMap &data() const;
|
||||
void checkParams(const QSet<QString> &requiredParams) const;
|
||||
|
||||
void setResult(const QString &result);
|
||||
void setResult(const QJsonArray &result);
|
||||
void setResult(const QJsonObject &result);
|
||||
|
||||
private:
|
||||
ISessionManager *m_sessionManager;
|
||||
StringMap m_params;
|
||||
DataMap m_data;
|
||||
QVariant m_result;
|
||||
};
|
40
src/webui/api/apierror.cpp
Normal file
40
src/webui/api/apierror.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "apierror.h"
|
||||
|
||||
APIError::APIError(APIErrorType type, const QString &message)
|
||||
: RuntimeError {message}
|
||||
, m_type {type}
|
||||
{
|
||||
}
|
||||
|
||||
APIErrorType APIError::type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -26,26 +26,26 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef JSONUTILS_H
|
||||
#define JSONUTILS_H
|
||||
#pragma once
|
||||
|
||||
#include <QVariant>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include "base/exceptions.h"
|
||||
|
||||
namespace json {
|
||||
enum class APIErrorType
|
||||
{
|
||||
BadParams,
|
||||
BadData,
|
||||
NotFound,
|
||||
AccessDenied,
|
||||
Conflict
|
||||
};
|
||||
|
||||
inline QByteArray toJson(const QVariant& var)
|
||||
{
|
||||
return QJsonDocument::fromVariant(var).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
class APIError : public RuntimeError
|
||||
{
|
||||
public:
|
||||
explicit APIError(APIErrorType type, const QString &message = "");
|
||||
|
||||
inline QVariant fromJson(const QString& json)
|
||||
{
|
||||
return QJsonDocument::fromJson(json.toUtf8()).toVariant();
|
||||
}
|
||||
APIErrorType type() const;
|
||||
|
||||
}
|
||||
|
||||
#endif // JSONUTILS_H
|
||||
private:
|
||||
const APIErrorType m_type;
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006-2012 Ishan Arora and Christophe Dumez
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2006-2012 Ishan Arora <ishan@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -24,37 +26,58 @@
|
|||
* 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 "prefjson.h"
|
||||
#include "appcontroller.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#endif
|
||||
#include <QStringList>
|
||||
#include <QTranslator>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/net/portforwarder.h"
|
||||
#include "base/net/proxyconfigurationmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/rss/rss_autodownloader.h"
|
||||
#include "base/rss/rss_session.h"
|
||||
#include "base/scanfoldersmodel.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "jsonutils.h"
|
||||
#include "../webapplication.h"
|
||||
|
||||
prefjson::prefjson()
|
||||
void AppController::webapiVersionAction()
|
||||
{
|
||||
setResult(static_cast<QString>(API_VERSION));
|
||||
}
|
||||
|
||||
QByteArray prefjson::getPreferences()
|
||||
void AppController::versionAction()
|
||||
{
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
setResult(QBT_VERSION);
|
||||
}
|
||||
|
||||
void AppController::shutdownAction()
|
||||
{
|
||||
qDebug() << "Shutdown request from Web UI";
|
||||
|
||||
// Special case handling for shutdown, we
|
||||
// need to reply to the Web UI before
|
||||
// actually shutting down.
|
||||
QTimer::singleShot(100, qApp, &QCoreApplication::quit);
|
||||
}
|
||||
|
||||
void AppController::preferencesAction()
|
||||
{
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
auto session = BitTorrent::Session::instance();
|
||||
QVariantMap data;
|
||||
|
||||
|
@ -187,14 +210,22 @@ QByteArray prefjson::getPreferences()
|
|||
data["dyndns_password"] = pref->getDynDNSPassword();
|
||||
data["dyndns_domain"] = pref->getDynDomainName();
|
||||
|
||||
return json::toJson(data);
|
||||
// RSS settings
|
||||
data["RSSRefreshInterval"] = RSS::Session::instance()->refreshInterval();
|
||||
data["RSSMaxArticlesPerFeed"] = RSS::Session::instance()->maxArticlesPerFeed();
|
||||
data["RSSProcessingEnabled"] = RSS::Session::instance()->isProcessingEnabled();
|
||||
data["RSSAutoDownloadingEnabled"] = RSS::AutoDownloader::instance()->isProcessingEnabled();
|
||||
|
||||
setResult(QJsonObject::fromVariantMap(data));
|
||||
}
|
||||
|
||||
void prefjson::setPreferences(const QString& json)
|
||||
void AppController::setPreferencesAction()
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
checkParams({"json"});
|
||||
|
||||
Preferences *const pref = Preferences::instance();
|
||||
auto session = BitTorrent::Session::instance();
|
||||
const QVariantMap m = json::fromJson(json).toMap();
|
||||
const QVariantMap m = QJsonDocument::fromJson(params()["json"].toUtf8()).toVariant().toMap();
|
||||
|
||||
// Downloads
|
||||
// Hard Disk
|
||||
|
@ -454,4 +485,14 @@ void prefjson::setPreferences(const QString& json)
|
|||
|
||||
// Save preferences
|
||||
pref->apply();
|
||||
|
||||
RSS::Session::instance()->setRefreshInterval(m["RSSRefreshInterval"].toUInt());
|
||||
RSS::Session::instance()->setMaxArticlesPerFeed(m["RSSMaxArticlesPerFeed"].toInt());
|
||||
RSS::Session::instance()->setProcessingEnabled(m["RSSProcessingEnabled"].toBool());
|
||||
RSS::AutoDownloader::instance()->setProcessingEnabled(m["RSSAutoDownloadingEnabled"].toBool());
|
||||
}
|
||||
|
||||
void AppController::defaultSavePathAction()
|
||||
{
|
||||
setResult(BitTorrent::Session::instance()->defaultSavePath());
|
||||
}
|
50
src/webui/api/appcontroller.h
Normal file
50
src/webui/api/appcontroller.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2006-2012 Ishan Arora <ishan@qbittorrent.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
class AppController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(AppController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void webapiVersionAction();
|
||||
void versionAction();
|
||||
void shutdownAction();
|
||||
void preferencesAction();
|
||||
void setPreferencesAction();
|
||||
void defaultSavePathAction();
|
||||
};
|
108
src/webui/api/authcontroller.cpp
Normal file
108
src/webui/api/authcontroller.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "authcontroller.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "apierror.h"
|
||||
#include "isessionmanager.h"
|
||||
|
||||
constexpr int BAN_TIME = 3600000; // 1 hour
|
||||
constexpr int MAX_AUTH_FAILED_ATTEMPTS = 5;
|
||||
|
||||
void AuthController::loginAction()
|
||||
{
|
||||
if (sessionManager()->session()) {
|
||||
setResult(QLatin1String("Ok."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBanned())
|
||||
throw APIError(APIErrorType::AccessDenied
|
||||
, tr("Your IP address has been banned after too many failed authentication attempts."));
|
||||
|
||||
QCryptographicHash md5(QCryptographicHash::Md5);
|
||||
md5.addData(params()["password"].toLocal8Bit());
|
||||
QString pass = md5.result().toHex();
|
||||
|
||||
const QString username {Preferences::instance()->getWebUiUsername()};
|
||||
const QString password {Preferences::instance()->getWebUiPassword()};
|
||||
|
||||
const bool equalUser = Utils::String::slowEquals(params()["username"].toUtf8(), username.toUtf8());
|
||||
const bool equalPass = Utils::String::slowEquals(pass.toUtf8(), password.toUtf8());
|
||||
|
||||
if (equalUser && equalPass) {
|
||||
sessionManager()->sessionStart();
|
||||
setResult(QLatin1String("Ok."));
|
||||
}
|
||||
else {
|
||||
QString addr = sessionManager()->clientId();
|
||||
increaseFailedAttempts();
|
||||
qDebug("client IP: %s (%d failed attempts)", qUtf8Printable(addr), failedAttemptsCount());
|
||||
setResult(QLatin1String("Fails."));
|
||||
}
|
||||
}
|
||||
|
||||
void AuthController::logoutAction()
|
||||
{
|
||||
sessionManager()->sessionEnd();
|
||||
}
|
||||
|
||||
bool AuthController::isBanned() const
|
||||
{
|
||||
const uint now = QDateTime::currentDateTime().toTime_t();
|
||||
const FailedLogin failedLogin = m_clientFailedLogins.value(sessionManager()->clientId());
|
||||
|
||||
bool isBanned = (failedLogin.bannedAt > 0);
|
||||
if (isBanned && ((now - failedLogin.bannedAt) > BAN_TIME)) {
|
||||
m_clientFailedLogins.remove(sessionManager()->clientId());
|
||||
isBanned = false;
|
||||
}
|
||||
|
||||
return isBanned;
|
||||
}
|
||||
|
||||
int AuthController::failedAttemptsCount() const
|
||||
{
|
||||
return m_clientFailedLogins.value(sessionManager()->clientId()).failedAttemptsCount;
|
||||
}
|
||||
|
||||
void AuthController::increaseFailedAttempts()
|
||||
{
|
||||
FailedLogin &failedLogin = m_clientFailedLogins[sessionManager()->clientId()];
|
||||
++failedLogin.failedAttemptsCount;
|
||||
|
||||
if (failedLogin.failedAttemptsCount == MAX_AUTH_FAILED_ATTEMPTS) {
|
||||
// Max number of failed attempts reached
|
||||
// Start ban period
|
||||
failedLogin.bannedAt = QDateTime::currentDateTime().toTime_t();
|
||||
}
|
||||
}
|
59
src/webui/api/authcontroller.h
Normal file
59
src/webui/api/authcontroller.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
class AuthController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(AuthController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void loginAction();
|
||||
void logoutAction();
|
||||
|
||||
private:
|
||||
bool isBanned() const;
|
||||
int failedAttemptsCount() const;
|
||||
void increaseFailedAttempts();
|
||||
|
||||
struct FailedLogin
|
||||
{
|
||||
int failedAttemptsCount = 0;
|
||||
uint bannedAt = 0;
|
||||
};
|
||||
mutable QHash<QString, FailedLogin> m_clientFailedLogins;
|
||||
};
|
49
src/webui/api/isessionmanager.h
Normal file
49
src/webui/api/isessionmanager.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
struct ISession
|
||||
{
|
||||
virtual ~ISession() = default;
|
||||
virtual QString id() const = 0;
|
||||
virtual QVariant getData(const QString &id) const = 0;
|
||||
virtual void setData(const QString &id, const QVariant &data) = 0;
|
||||
};
|
||||
|
||||
struct ISessionManager
|
||||
{
|
||||
virtual ~ISessionManager() = default;
|
||||
virtual QString clientId() const = 0;
|
||||
virtual ISession *session() = 0;
|
||||
virtual void sessionStart() = 0;
|
||||
virtual void sessionEnd() = 0;
|
||||
};
|
124
src/webui/api/logcontroller.cpp
Normal file
124
src/webui/api/logcontroller.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "logcontroller.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
const char KEY_LOG_ID[] = "id";
|
||||
const char KEY_LOG_TIMESTAMP[] = "timestamp";
|
||||
const char KEY_LOG_MSG_TYPE[] = "type";
|
||||
const char KEY_LOG_MSG_MESSAGE[] = "message";
|
||||
const char KEY_LOG_PEER_IP[] = "ip";
|
||||
const char KEY_LOG_PEER_BLOCKED[] = "blocked";
|
||||
const char KEY_LOG_PEER_REASON[] = "reason";
|
||||
|
||||
// Returns the log in JSON format.
|
||||
// The return value is an array of dictionaries.
|
||||
// The dictionary keys are:
|
||||
// - "id": id of the message
|
||||
// - "timestamp": milliseconds since epoch
|
||||
// - "type": type of the message (int, see MsgType)
|
||||
// - "message": text of the message
|
||||
// GET params:
|
||||
// - normal (bool): include normal messages (default true)
|
||||
// - info (bool): include info messages (default true)
|
||||
// - warning (bool): include warning messages (default true)
|
||||
// - critical (bool): include critical messages (default true)
|
||||
// - last_known_id (int): exclude messages with id <= 'last_known_id' (default -1)
|
||||
void LogController::mainAction()
|
||||
{
|
||||
using Utils::String::parseBool;
|
||||
|
||||
const bool isNormal = parseBool(params()["normal"], true);
|
||||
const bool isInfo = parseBool(params()["info"], true);
|
||||
const bool isWarning = parseBool(params()["warning"], true);
|
||||
const bool isCritical = parseBool(params()["critical"], true);
|
||||
|
||||
bool ok = false;
|
||||
int lastKnownId = params()["last_known_id"].toInt(&ok);
|
||||
if (!ok)
|
||||
lastKnownId = -1;
|
||||
|
||||
Logger *const logger = Logger::instance();
|
||||
QVariantList msgList;
|
||||
|
||||
foreach (const Log::Msg &msg, logger->getMessages(lastKnownId)) {
|
||||
if (!((msg.type == Log::NORMAL && isNormal)
|
||||
|| (msg.type == Log::INFO && isInfo)
|
||||
|| (msg.type == Log::WARNING && isWarning)
|
||||
|| (msg.type == Log::CRITICAL && isCritical)))
|
||||
continue;
|
||||
QVariantMap map;
|
||||
map[KEY_LOG_ID] = msg.id;
|
||||
map[KEY_LOG_TIMESTAMP] = msg.timestamp;
|
||||
map[KEY_LOG_MSG_TYPE] = msg.type;
|
||||
map[KEY_LOG_MSG_MESSAGE] = msg.message;
|
||||
msgList.append(map);
|
||||
}
|
||||
|
||||
setResult(QJsonArray::fromVariantList(msgList));
|
||||
}
|
||||
|
||||
// Returns the peer log in JSON format.
|
||||
// The return value is an array of dictionaries.
|
||||
// The dictionary keys are:
|
||||
// - "id": id of the message
|
||||
// - "timestamp": milliseconds since epoch
|
||||
// - "ip": IP of the peer
|
||||
// - "blocked": whether or not the peer was blocked
|
||||
// - "reason": reason of the block
|
||||
// GET params:
|
||||
// - last_known_id (int): exclude messages with id <= 'last_known_id' (default -1)
|
||||
void LogController::peersAction()
|
||||
{
|
||||
int lastKnownId;
|
||||
bool ok;
|
||||
|
||||
lastKnownId = params()["last_known_id"].toInt(&ok);
|
||||
if (!ok)
|
||||
lastKnownId = -1;
|
||||
|
||||
Logger *const logger = Logger::instance();
|
||||
QVariantList peerList;
|
||||
|
||||
foreach (const Log::Peer &peer, logger->getPeers(lastKnownId)) {
|
||||
QVariantMap map;
|
||||
map[KEY_LOG_ID] = peer.id;
|
||||
map[KEY_LOG_TIMESTAMP] = peer.timestamp;
|
||||
map[KEY_LOG_PEER_IP] = peer.ip;
|
||||
map[KEY_LOG_PEER_BLOCKED] = peer.blocked;
|
||||
map[KEY_LOG_PEER_REASON] = peer.reason;
|
||||
peerList.append(map);
|
||||
}
|
||||
|
||||
setResult(QJsonArray::fromVariantList(peerList));
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -26,18 +26,19 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef WEBSESSIONDATA
|
||||
#define WEBSESSIONDATA
|
||||
#pragma once
|
||||
|
||||
#include <QVariant>
|
||||
#include "apicontroller.h"
|
||||
|
||||
struct WebSessionData
|
||||
class LogController : public APIController
|
||||
{
|
||||
QVariantMap syncMainDataLastResponse;
|
||||
QVariantMap syncMainDataLastAcceptedResponse;
|
||||
QVariantMap syncTorrentPeersLastResponse;
|
||||
QVariantMap syncTorrentPeersLastAcceptedResponse;
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(LogController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void mainAction();
|
||||
void peersAction();
|
||||
};
|
||||
|
||||
#endif // WEBSESSIONDATA
|
||||
|
131
src/webui/api/rsscontroller.cpp
Normal file
131
src/webui/api/rsscontroller.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "rsscontroller.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "base/rss/rss_autodownloader.h"
|
||||
#include "base/rss/rss_autodownloadrule.h"
|
||||
#include "base/rss/rss_folder.h"
|
||||
#include "base/rss/rss_session.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "apierror.h"
|
||||
|
||||
using Utils::String::parseBool;
|
||||
|
||||
void RSSController::addFolderAction()
|
||||
{
|
||||
checkParams({"path"});
|
||||
|
||||
const QString path = params()["path"].trimmed();
|
||||
QString error;
|
||||
if (!RSS::Session::instance()->addFolder(path, &error))
|
||||
throw APIError(APIErrorType::Conflict, error);
|
||||
}
|
||||
|
||||
void RSSController::addFeedAction()
|
||||
{
|
||||
checkParams({"url", "path"});
|
||||
|
||||
const QString url = params()["url"].trimmed();
|
||||
const QString path = params()["path"].trimmed();
|
||||
QString error;
|
||||
if (!RSS::Session::instance()->addFeed(url, (path.isEmpty() ? url : path), &error))
|
||||
throw APIError(APIErrorType::Conflict, error);
|
||||
}
|
||||
|
||||
void RSSController::removeItemAction()
|
||||
{
|
||||
checkParams({"path"});
|
||||
|
||||
const QString path = params()["path"].trimmed();
|
||||
QString error;
|
||||
if (!RSS::Session::instance()->removeItem(path, &error))
|
||||
throw APIError(APIErrorType::Conflict, error);
|
||||
}
|
||||
|
||||
void RSSController::moveItemAction()
|
||||
{
|
||||
checkParams({"itemPath", "destPath"});
|
||||
|
||||
const QString itemPath = params()["itemPath"].trimmed();
|
||||
const QString destPath = params()["destPath"].trimmed();
|
||||
QString error;
|
||||
if (!RSS::Session::instance()->moveItem(itemPath, destPath, &error))
|
||||
throw APIError(APIErrorType::Conflict, error);
|
||||
}
|
||||
|
||||
void RSSController::itemsAction()
|
||||
{
|
||||
const bool withData {parseBool(params()["withData"], false)};
|
||||
|
||||
const auto jsonVal = RSS::Session::instance()->rootFolder()->toJsonValue(withData);
|
||||
setResult(jsonVal.toObject());
|
||||
}
|
||||
|
||||
void RSSController::setRuleAction()
|
||||
{
|
||||
checkParams({"ruleName", "ruleDef"});
|
||||
|
||||
const QString ruleName {params()["ruleName"].trimmed()};
|
||||
const QByteArray ruleDef {params()["ruleDef"].trimmed().toUtf8()};
|
||||
|
||||
const auto jsonObj = QJsonDocument::fromJson(ruleDef).object();
|
||||
RSS::AutoDownloader::instance()->insertRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName));
|
||||
}
|
||||
|
||||
void RSSController::renameRuleAction()
|
||||
{
|
||||
checkParams({"ruleName", "newRuleName"});
|
||||
|
||||
const QString ruleName {params()["ruleName"].trimmed()};
|
||||
const QString newRuleName {params()["newRuleName"].trimmed()};
|
||||
|
||||
RSS::AutoDownloader::instance()->renameRule(ruleName, newRuleName);
|
||||
}
|
||||
|
||||
void RSSController::removeRuleAction()
|
||||
{
|
||||
checkParams({"ruleName"});
|
||||
|
||||
const QString ruleName {params()["ruleName"].trimmed()};
|
||||
RSS::AutoDownloader::instance()->removeRule(ruleName);
|
||||
}
|
||||
|
||||
void RSSController::rulesAction()
|
||||
{
|
||||
const QList<RSS::AutoDownloadRule> rules {RSS::AutoDownloader::instance()->rules()};
|
||||
QJsonObject jsonObj;
|
||||
for (const auto &rule : rules)
|
||||
jsonObj.insert(rule.name(), rule.toJsonObject());
|
||||
|
||||
setResult(jsonObj);
|
||||
}
|
51
src/webui/api/rsscontroller.h
Normal file
51
src/webui/api/rsscontroller.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
class RSSController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(RSSController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void addFolderAction();
|
||||
void addFeedAction();
|
||||
void removeItemAction();
|
||||
void moveItemAction();
|
||||
void itemsAction();
|
||||
void setRuleAction();
|
||||
void renameRuleAction();
|
||||
void removeRuleAction();
|
||||
void rulesAction();
|
||||
};
|
140
src/webui/api/serialize/serialize_torrent.cpp
Normal file
140
src/webui/api/serialize/serialize_torrent.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "serialize_torrent.h"
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
QString torrentStateToString(const BitTorrent::TorrentState state)
|
||||
{
|
||||
switch (state) {
|
||||
case BitTorrent::TorrentState::Error:
|
||||
return QLatin1String("error");
|
||||
case BitTorrent::TorrentState::MissingFiles:
|
||||
return QLatin1String("missingFiles");
|
||||
case BitTorrent::TorrentState::Uploading:
|
||||
return QLatin1String("uploading");
|
||||
case BitTorrent::TorrentState::PausedUploading:
|
||||
return QLatin1String("pausedUP");
|
||||
case BitTorrent::TorrentState::QueuedUploading:
|
||||
return QLatin1String("queuedUP");
|
||||
case BitTorrent::TorrentState::StalledUploading:
|
||||
return QLatin1String("stalledUP");
|
||||
case BitTorrent::TorrentState::CheckingUploading:
|
||||
return QLatin1String("checkingUP");
|
||||
case BitTorrent::TorrentState::ForcedUploading:
|
||||
return QLatin1String("forcedUP");
|
||||
case BitTorrent::TorrentState::Allocating:
|
||||
return QLatin1String("allocating");
|
||||
case BitTorrent::TorrentState::Downloading:
|
||||
return QLatin1String("downloading");
|
||||
case BitTorrent::TorrentState::DownloadingMetadata:
|
||||
return QLatin1String("metaDL");
|
||||
case BitTorrent::TorrentState::PausedDownloading:
|
||||
return QLatin1String("pausedDL");
|
||||
case BitTorrent::TorrentState::QueuedDownloading:
|
||||
return QLatin1String("queuedDL");
|
||||
case BitTorrent::TorrentState::StalledDownloading:
|
||||
return QLatin1String("stalledDL");
|
||||
case BitTorrent::TorrentState::CheckingDownloading:
|
||||
return QLatin1String("checkingDL");
|
||||
case BitTorrent::TorrentState::ForcedDownloading:
|
||||
return QLatin1String("forcedDL");
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
case BitTorrent::TorrentState::QueuedForChecking:
|
||||
return QLatin1String("queuedForChecking");
|
||||
#endif
|
||||
case BitTorrent::TorrentState::CheckingResumeData:
|
||||
return QLatin1String("checkingResumeData");
|
||||
default:
|
||||
return QLatin1String("unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap serialize(const BitTorrent::TorrentHandle &torrent)
|
||||
{
|
||||
QVariantMap ret;
|
||||
ret[KEY_TORRENT_HASH] = QString(torrent.hash());
|
||||
ret[KEY_TORRENT_NAME] = torrent.name();
|
||||
ret[KEY_TORRENT_MAGNET_URI] = torrent.toMagnetUri();
|
||||
ret[KEY_TORRENT_SIZE] = torrent.wantedSize();
|
||||
ret[KEY_TORRENT_PROGRESS] = torrent.progress();
|
||||
ret[KEY_TORRENT_DLSPEED] = torrent.downloadPayloadRate();
|
||||
ret[KEY_TORRENT_UPSPEED] = torrent.uploadPayloadRate();
|
||||
ret[KEY_TORRENT_PRIORITY] = torrent.queuePosition();
|
||||
ret[KEY_TORRENT_SEEDS] = torrent.seedsCount();
|
||||
ret[KEY_TORRENT_NUM_COMPLETE] = torrent.totalSeedsCount();
|
||||
ret[KEY_TORRENT_LEECHS] = torrent.leechsCount();
|
||||
ret[KEY_TORRENT_NUM_INCOMPLETE] = torrent.totalLeechersCount();
|
||||
const qreal ratio = torrent.realRatio();
|
||||
ret[KEY_TORRENT_RATIO] = (ratio > BitTorrent::TorrentHandle::MAX_RATIO) ? -1 : ratio;
|
||||
ret[KEY_TORRENT_STATE] = torrentStateToString(torrent.state());
|
||||
ret[KEY_TORRENT_ETA] = torrent.eta();
|
||||
ret[KEY_TORRENT_SEQUENTIAL_DOWNLOAD] = torrent.isSequentialDownload();
|
||||
if (torrent.hasMetadata())
|
||||
ret[KEY_TORRENT_FIRST_LAST_PIECE_PRIO] = torrent.hasFirstLastPiecePriority();
|
||||
ret[KEY_TORRENT_CATEGORY] = torrent.category();
|
||||
ret[KEY_TORRENT_TAGS] = torrent.tags().toList().join(", ");
|
||||
ret[KEY_TORRENT_SUPER_SEEDING] = torrent.superSeeding();
|
||||
ret[KEY_TORRENT_FORCE_START] = torrent.isForced();
|
||||
ret[KEY_TORRENT_SAVE_PATH] = Utils::Fs::toNativePath(torrent.savePath());
|
||||
ret[KEY_TORRENT_ADDED_ON] = torrent.addedTime().toTime_t();
|
||||
ret[KEY_TORRENT_COMPLETION_ON] = torrent.completedTime().toTime_t();
|
||||
ret[KEY_TORRENT_TRACKER] = torrent.currentTracker();
|
||||
ret[KEY_TORRENT_DL_LIMIT] = torrent.downloadLimit();
|
||||
ret[KEY_TORRENT_UP_LIMIT] = torrent.uploadLimit();
|
||||
ret[KEY_TORRENT_AMOUNT_DOWNLOADED] = torrent.totalDownload();
|
||||
ret[KEY_TORRENT_AMOUNT_UPLOADED] = torrent.totalUpload();
|
||||
ret[KEY_TORRENT_AMOUNT_DOWNLOADED_SESSION] = torrent.totalPayloadDownload();
|
||||
ret[KEY_TORRENT_AMOUNT_UPLOADED_SESSION] = torrent.totalPayloadUpload();
|
||||
ret[KEY_TORRENT_AMOUNT_LEFT] = torrent.incompletedSize();
|
||||
ret[KEY_TORRENT_AMOUNT_COMPLETED] = torrent.completedSize();
|
||||
ret[KEY_TORRENT_RATIO_LIMIT] = torrent.maxRatio();
|
||||
ret[KEY_TORRENT_LAST_SEEN_COMPLETE_TIME] = torrent.lastSeenComplete().toTime_t();
|
||||
ret[KEY_TORRENT_AUTO_TORRENT_MANAGEMENT] = torrent.isAutoTMMEnabled();
|
||||
ret[KEY_TORRENT_TIME_ACTIVE] = torrent.activeTime();
|
||||
|
||||
if (torrent.isPaused() || torrent.isChecking()) {
|
||||
ret[KEY_TORRENT_LAST_ACTIVITY_TIME] = 0;
|
||||
}
|
||||
else {
|
||||
QDateTime dt = QDateTime::currentDateTime();
|
||||
dt = dt.addSecs(-torrent.timeSinceActivity());
|
||||
ret[KEY_TORRENT_LAST_ACTIVITY_TIME] = dt.toTime_t();
|
||||
}
|
||||
|
||||
ret[KEY_TORRENT_TOTAL_SIZE] = torrent.totalSize();
|
||||
|
||||
return ret;
|
||||
}
|
79
src/webui/api/serialize/serialize_torrent.h
Normal file
79
src/webui/api/serialize/serialize_torrent.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVariantMap>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentHandle;
|
||||
}
|
||||
|
||||
// Torrent keys
|
||||
const char KEY_TORRENT_HASH[] = "hash";
|
||||
const char KEY_TORRENT_NAME[] = "name";
|
||||
const char KEY_TORRENT_MAGNET_URI[] = "magnet_uri";
|
||||
const char KEY_TORRENT_SIZE[] = "size";
|
||||
const char KEY_TORRENT_PROGRESS[] = "progress";
|
||||
const char KEY_TORRENT_DLSPEED[] = "dlspeed";
|
||||
const char KEY_TORRENT_UPSPEED[] = "upspeed";
|
||||
const char KEY_TORRENT_PRIORITY[] = "priority";
|
||||
const char KEY_TORRENT_SEEDS[] = "num_seeds";
|
||||
const char KEY_TORRENT_NUM_COMPLETE[] = "num_complete";
|
||||
const char KEY_TORRENT_LEECHS[] = "num_leechs";
|
||||
const char KEY_TORRENT_NUM_INCOMPLETE[] = "num_incomplete";
|
||||
const char KEY_TORRENT_RATIO[] = "ratio";
|
||||
const char KEY_TORRENT_ETA[] = "eta";
|
||||
const char KEY_TORRENT_STATE[] = "state";
|
||||
const char KEY_TORRENT_SEQUENTIAL_DOWNLOAD[] = "seq_dl";
|
||||
const char KEY_TORRENT_FIRST_LAST_PIECE_PRIO[] = "f_l_piece_prio";
|
||||
const char KEY_TORRENT_CATEGORY[] = "category";
|
||||
const char KEY_TORRENT_TAGS[] = "tags";
|
||||
const char KEY_TORRENT_SUPER_SEEDING[] = "super_seeding";
|
||||
const char KEY_TORRENT_FORCE_START[] = "force_start";
|
||||
const char KEY_TORRENT_SAVE_PATH[] = "save_path";
|
||||
const char KEY_TORRENT_ADDED_ON[] = "added_on";
|
||||
const char KEY_TORRENT_COMPLETION_ON[] = "completion_on";
|
||||
const char KEY_TORRENT_TRACKER[] = "tracker";
|
||||
const char KEY_TORRENT_DL_LIMIT[] = "dl_limit";
|
||||
const char KEY_TORRENT_UP_LIMIT[] = "up_limit";
|
||||
const char KEY_TORRENT_AMOUNT_DOWNLOADED[] = "downloaded";
|
||||
const char KEY_TORRENT_AMOUNT_UPLOADED[] = "uploaded";
|
||||
const char KEY_TORRENT_AMOUNT_DOWNLOADED_SESSION[] = "downloaded_session";
|
||||
const char KEY_TORRENT_AMOUNT_UPLOADED_SESSION[] = "uploaded_session";
|
||||
const char KEY_TORRENT_AMOUNT_LEFT[] = "amount_left";
|
||||
const char KEY_TORRENT_AMOUNT_COMPLETED[] = "completed";
|
||||
const char KEY_TORRENT_RATIO_LIMIT[] = "ratio_limit";
|
||||
const char KEY_TORRENT_LAST_SEEN_COMPLETE_TIME[] = "seen_complete";
|
||||
const char KEY_TORRENT_LAST_ACTIVITY_TIME[] = "last_activity";
|
||||
const char KEY_TORRENT_TOTAL_SIZE[] = "total_size";
|
||||
const char KEY_TORRENT_AUTO_TORRENT_MANAGEMENT[] = "auto_tmm";
|
||||
const char KEY_TORRENT_TIME_ACTIVE[] = "time_active";
|
||||
|
||||
QVariantMap serialize(const BitTorrent::TorrentHandle &torrent);
|
473
src/webui/api/synccontroller.cpp
Normal file
473
src/webui/api/synccontroller.cpp
Normal file
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "synccontroller.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "base/bittorrent/peerinfo.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/net/geoipmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "apierror.h"
|
||||
#include "isessionmanager.h"
|
||||
#include "serialize/serialize_torrent.h"
|
||||
|
||||
// Sync main data keys
|
||||
const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing";
|
||||
const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits";
|
||||
const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval";
|
||||
|
||||
// Sync torrent peers keys
|
||||
const char KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS[] = "show_flags";
|
||||
|
||||
// Peer keys
|
||||
const char KEY_PEER_IP[] = "ip";
|
||||
const char KEY_PEER_PORT[] = "port";
|
||||
const char KEY_PEER_COUNTRY_CODE[] = "country_code";
|
||||
const char KEY_PEER_COUNTRY[] = "country";
|
||||
const char KEY_PEER_CLIENT[] = "client";
|
||||
const char KEY_PEER_PROGRESS[] = "progress";
|
||||
const char KEY_PEER_DOWN_SPEED[] = "dl_speed";
|
||||
const char KEY_PEER_UP_SPEED[] = "up_speed";
|
||||
const char KEY_PEER_TOT_DOWN[] = "downloaded";
|
||||
const char KEY_PEER_TOT_UP[] = "uploaded";
|
||||
const char KEY_PEER_CONNECTION_TYPE[] = "connection";
|
||||
const char KEY_PEER_FLAGS[] = "flags";
|
||||
const char KEY_PEER_FLAGS_DESCRIPTION[] = "flags_desc";
|
||||
const char KEY_PEER_RELEVANCE[] = "relevance";
|
||||
const char KEY_PEER_FILES[] = "files";
|
||||
|
||||
// TransferInfo keys
|
||||
const char KEY_TRANSFER_DLSPEED[] = "dl_info_speed";
|
||||
const char KEY_TRANSFER_DLDATA[] = "dl_info_data";
|
||||
const char KEY_TRANSFER_DLRATELIMIT[] = "dl_rate_limit";
|
||||
const char KEY_TRANSFER_UPSPEED[] = "up_info_speed";
|
||||
const char KEY_TRANSFER_UPDATA[] = "up_info_data";
|
||||
const char KEY_TRANSFER_UPRATELIMIT[] = "up_rate_limit";
|
||||
const char KEY_TRANSFER_DHT_NODES[] = "dht_nodes";
|
||||
const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status";
|
||||
|
||||
// Statistics keys
|
||||
const char KEY_TRANSFER_ALLTIME_DL[] = "alltime_dl";
|
||||
const char KEY_TRANSFER_ALLTIME_UL[] = "alltime_ul";
|
||||
const char KEY_TRANSFER_TOTAL_WASTE_SESSION[] = "total_wasted_session";
|
||||
const char KEY_TRANSFER_GLOBAL_RATIO[] = "global_ratio";
|
||||
const char KEY_TRANSFER_TOTAL_PEER_CONNECTIONS[] = "total_peer_connections";
|
||||
const char KEY_TRANSFER_READ_CACHE_HITS[] = "read_cache_hits";
|
||||
const char KEY_TRANSFER_TOTAL_BUFFERS_SIZE[] = "total_buffers_size";
|
||||
const char KEY_TRANSFER_WRITE_CACHE_OVERLOAD[] = "write_cache_overload";
|
||||
const char KEY_TRANSFER_READ_CACHE_OVERLOAD[] = "read_cache_overload";
|
||||
const char KEY_TRANSFER_QUEUED_IO_JOBS[] = "queued_io_jobs";
|
||||
const char KEY_TRANSFER_AVERAGE_TIME_QUEUE[] = "average_time_queue";
|
||||
const char KEY_TRANSFER_TOTAL_QUEUED_SIZE[] = "total_queued_size";
|
||||
|
||||
const char KEY_FULL_UPDATE[] = "full_update";
|
||||
const char KEY_RESPONSE_ID[] = "rid";
|
||||
const char KEY_SUFFIX_REMOVED[] = "_removed";
|
||||
|
||||
namespace
|
||||
{
|
||||
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData);
|
||||
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems);
|
||||
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems);
|
||||
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData);
|
||||
|
||||
QVariantMap getTranserInfo()
|
||||
{
|
||||
QVariantMap map;
|
||||
const BitTorrent::SessionStatus &sessionStatus = BitTorrent::Session::instance()->status();
|
||||
const BitTorrent::CacheStatus &cacheStatus = BitTorrent::Session::instance()->cacheStatus();
|
||||
map[KEY_TRANSFER_DLSPEED] = sessionStatus.payloadDownloadRate;
|
||||
map[KEY_TRANSFER_DLDATA] = sessionStatus.totalPayloadDownload;
|
||||
map[KEY_TRANSFER_UPSPEED] = sessionStatus.payloadUploadRate;
|
||||
map[KEY_TRANSFER_UPDATA] = sessionStatus.totalPayloadUpload;
|
||||
map[KEY_TRANSFER_DLRATELIMIT] = BitTorrent::Session::instance()->downloadSpeedLimit();
|
||||
map[KEY_TRANSFER_UPRATELIMIT] = BitTorrent::Session::instance()->uploadSpeedLimit();
|
||||
|
||||
quint64 atd = BitTorrent::Session::instance()->getAlltimeDL();
|
||||
quint64 atu = BitTorrent::Session::instance()->getAlltimeUL();
|
||||
map[KEY_TRANSFER_ALLTIME_DL] = atd;
|
||||
map[KEY_TRANSFER_ALLTIME_UL] = atu;
|
||||
map[KEY_TRANSFER_TOTAL_WASTE_SESSION] = sessionStatus.totalWasted;
|
||||
map[KEY_TRANSFER_GLOBAL_RATIO] = ((atd > 0) && (atu > 0)) ? Utils::String::fromDouble(static_cast<qreal>(atu) / atd, 2) : "-";
|
||||
map[KEY_TRANSFER_TOTAL_PEER_CONNECTIONS] = sessionStatus.peersCount;
|
||||
|
||||
qreal readRatio = cacheStatus.readRatio;
|
||||
map[KEY_TRANSFER_READ_CACHE_HITS] = (readRatio >= 0) ? Utils::String::fromDouble(100 * readRatio, 2) : "-";
|
||||
map[KEY_TRANSFER_TOTAL_BUFFERS_SIZE] = cacheStatus.totalUsedBuffers * 16 * 1024;
|
||||
|
||||
// num_peers is not reliable (adds up peers, which didn't even overcome tcp handshake)
|
||||
quint32 peers = 0;
|
||||
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents())
|
||||
peers += torrent->peersCount();
|
||||
map[KEY_TRANSFER_WRITE_CACHE_OVERLOAD] = ((sessionStatus.diskWriteQueue > 0) && (peers > 0)) ? Utils::String::fromDouble((100. * sessionStatus.diskWriteQueue) / peers, 2) : "0";
|
||||
map[KEY_TRANSFER_READ_CACHE_OVERLOAD] = ((sessionStatus.diskReadQueue > 0) && (peers > 0)) ? Utils::String::fromDouble((100. * sessionStatus.diskReadQueue) / peers, 2) : "0";
|
||||
|
||||
map[KEY_TRANSFER_QUEUED_IO_JOBS] = cacheStatus.jobQueueLength;
|
||||
map[KEY_TRANSFER_AVERAGE_TIME_QUEUE] = cacheStatus.averageJobTime;
|
||||
map[KEY_TRANSFER_TOTAL_QUEUED_SIZE] = cacheStatus.queuedBytes;
|
||||
|
||||
map[KEY_TRANSFER_DHT_NODES] = sessionStatus.dhtNodes;
|
||||
if (!BitTorrent::Session::instance()->isListening())
|
||||
map[KEY_TRANSFER_CONNECTION_STATUS] = "disconnected";
|
||||
else
|
||||
map[KEY_TRANSFER_CONNECTION_STATUS] = sessionStatus.hasIncomingConnections ? "connected" : "firewalled";
|
||||
return map;
|
||||
}
|
||||
|
||||
// Compare two structures (prevData, data) and calculate difference (syncData).
|
||||
// Structures encoded as map.
|
||||
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData)
|
||||
{
|
||||
// initialize output variable
|
||||
syncData.clear();
|
||||
|
||||
QVariantList removedItems;
|
||||
foreach (QString key, data.keys()) {
|
||||
removedItems.clear();
|
||||
|
||||
switch (static_cast<QMetaType::Type>(data[key].type())) {
|
||||
case QMetaType::QVariantMap: {
|
||||
QVariantMap map;
|
||||
processMap(prevData[key].toMap(), data[key].toMap(), map);
|
||||
if (!map.isEmpty())
|
||||
syncData[key] = map;
|
||||
}
|
||||
break;
|
||||
case QMetaType::QVariantHash: {
|
||||
QVariantMap map;
|
||||
processHash(prevData[key].toHash(), data[key].toHash(), map, removedItems);
|
||||
if (!map.isEmpty())
|
||||
syncData[key] = map;
|
||||
if (!removedItems.isEmpty())
|
||||
syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
|
||||
}
|
||||
break;
|
||||
case QMetaType::QVariantList: {
|
||||
QVariantList list;
|
||||
processList(prevData[key].toList(), data[key].toList(), list, removedItems);
|
||||
if (!list.isEmpty())
|
||||
syncData[key] = list;
|
||||
if (!removedItems.isEmpty())
|
||||
syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
|
||||
}
|
||||
break;
|
||||
case QMetaType::QString:
|
||||
case QMetaType::LongLong:
|
||||
case QMetaType::Float:
|
||||
case QMetaType::Int:
|
||||
case QMetaType::Bool:
|
||||
case QMetaType::Double:
|
||||
case QMetaType::ULongLong:
|
||||
case QMetaType::UInt:
|
||||
case QMetaType::QDateTime:
|
||||
if (prevData[key] != data[key])
|
||||
syncData[key] = data[key];
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT_X(false, "processMap"
|
||||
, QString("Unexpected type: %1")
|
||||
.arg(QMetaType::typeName(static_cast<QMetaType::Type>(data[key].type())))
|
||||
.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare two lists of structures (prevData, data) and calculate difference (syncData, removedItems).
|
||||
// Structures encoded as map.
|
||||
// Lists are encoded as hash table (indexed by structure key value) to improve ease of searching for removed items.
|
||||
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems)
|
||||
{
|
||||
// initialize output variables
|
||||
syncData.clear();
|
||||
removedItems.clear();
|
||||
|
||||
if (prevData.isEmpty()) {
|
||||
// If list was empty before, then difference is a whole new list.
|
||||
foreach (QString key, data.keys())
|
||||
syncData[key] = data[key];
|
||||
}
|
||||
else {
|
||||
foreach (QString key, data.keys()) {
|
||||
switch (data[key].type()) {
|
||||
case QVariant::Map:
|
||||
if (!prevData.contains(key)) {
|
||||
// new list item found - append it to syncData
|
||||
syncData[key] = data[key];
|
||||
}
|
||||
else {
|
||||
QVariantMap map;
|
||||
processMap(prevData[key].toMap(), data[key].toMap(), map);
|
||||
// existing list item found - remove it from prevData
|
||||
prevData.remove(key);
|
||||
if (!map.isEmpty())
|
||||
// changed list item found - append its changes to syncData
|
||||
syncData[key] = map;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!prevData.isEmpty()) {
|
||||
// prevData contains only items that are missing now -
|
||||
// put them in removedItems
|
||||
foreach (QString s, prevData.keys())
|
||||
removedItems << s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare two lists of simple value (prevData, data) and calculate difference (syncData, removedItems).
|
||||
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems)
|
||||
{
|
||||
// initialize output variables
|
||||
syncData.clear();
|
||||
removedItems.clear();
|
||||
|
||||
if (prevData.isEmpty()) {
|
||||
// If list was empty before, then difference is a whole new list.
|
||||
syncData = data;
|
||||
}
|
||||
else {
|
||||
foreach (QVariant item, data) {
|
||||
if (!prevData.contains(item))
|
||||
// new list item found - append it to syncData
|
||||
syncData.append(item);
|
||||
else
|
||||
// unchanged list item found - remove it from prevData
|
||||
prevData.removeOne(item);
|
||||
}
|
||||
|
||||
if (!prevData.isEmpty())
|
||||
// prevData contains only items that are missing now -
|
||||
// put them in removedItems
|
||||
removedItems = prevData;
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
|
||||
{
|
||||
QVariantMap syncData;
|
||||
bool fullUpdate = true;
|
||||
int lastResponseId = 0;
|
||||
if (acceptedResponseId > 0) {
|
||||
lastResponseId = lastData[KEY_RESPONSE_ID].toInt();
|
||||
|
||||
if (lastResponseId == acceptedResponseId)
|
||||
lastAcceptedData = lastData;
|
||||
|
||||
int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt();
|
||||
|
||||
if (lastAcceptedResponseId == acceptedResponseId) {
|
||||
processMap(lastAcceptedData, data, syncData);
|
||||
fullUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullUpdate) {
|
||||
lastAcceptedData.clear();
|
||||
syncData = data;
|
||||
syncData[KEY_FULL_UPDATE] = true;
|
||||
}
|
||||
|
||||
lastResponseId = lastResponseId % 1000000 + 1; // cycle between 1 and 1000000
|
||||
lastData = data;
|
||||
lastData[KEY_RESPONSE_ID] = lastResponseId;
|
||||
syncData[KEY_RESPONSE_ID] = lastResponseId;
|
||||
|
||||
return syncData;
|
||||
}
|
||||
}
|
||||
|
||||
// The function returns the changed data from the server to synchronize with the web client.
|
||||
// Return value is map in JSON format.
|
||||
// Map contain the key:
|
||||
// - "Rid": ID response
|
||||
// Map can contain the keys:
|
||||
// - "full_update": full data update flag
|
||||
// - "torrents": dictionary contains information about torrents.
|
||||
// - "torrents_removed": a list of hashes of removed torrents
|
||||
// - "categories": list of categories
|
||||
// - "categories_removed": list of removed categories
|
||||
// - "server_state": map contains information about the state of the server
|
||||
// The keys of the 'torrents' dictionary are hashes of torrents.
|
||||
// Each value of the 'torrents' dictionary contains map. The map can contain following keys:
|
||||
// - "name": Torrent name
|
||||
// - "size": Torrent size
|
||||
// - "progress: Torrent progress
|
||||
// - "dlspeed": Torrent download speed
|
||||
// - "upspeed": Torrent upload speed
|
||||
// - "priority": Torrent priority (-1 if queuing is disabled)
|
||||
// - "num_seeds": Torrent seeds connected to
|
||||
// - "num_complete": Torrent seeds in the swarm
|
||||
// - "num_leechs": Torrent leechers connected to
|
||||
// - "num_incomplete": Torrent leechers in the swarm
|
||||
// - "ratio": Torrent share ratio
|
||||
// - "eta": Torrent ETA
|
||||
// - "state": Torrent state
|
||||
// - "seq_dl": Torrent sequential download state
|
||||
// - "f_l_piece_prio": Torrent first last piece priority state
|
||||
// - "completion_on": Torrent copletion time
|
||||
// - "tracker": Torrent tracker
|
||||
// - "dl_limit": Torrent download limit
|
||||
// - "up_limit": Torrent upload limit
|
||||
// - "downloaded": Amount of data downloaded
|
||||
// - "uploaded": Amount of data uploaded
|
||||
// - "downloaded_session": Amount of data downloaded since program open
|
||||
// - "uploaded_session": Amount of data uploaded since program open
|
||||
// - "amount_left": Amount of data left to download
|
||||
// - "save_path": Torrent save path
|
||||
// - "completed": Amount of data completed
|
||||
// - "ratio_limit": Upload share ratio limit
|
||||
// - "seen_complete": Indicates the time when the torrent was last seen complete/whole
|
||||
// - "last_activity": Last time when a chunk was downloaded/uploaded
|
||||
// - "total_size": Size including unwanted data
|
||||
// Server state map may contain the following keys:
|
||||
// - "connection_status": connection status
|
||||
// - "dht_nodes": DHT nodes count
|
||||
// - "dl_info_data": bytes downloaded
|
||||
// - "dl_info_speed": download speed
|
||||
// - "dl_rate_limit: download rate limit
|
||||
// - "up_info_data: bytes uploaded
|
||||
// - "up_info_speed: upload speed
|
||||
// - "up_rate_limit: upload speed limit
|
||||
// - "queueing": priority system usage flag
|
||||
// - "refresh_interval": torrents table refresh interval
|
||||
// GET param:
|
||||
// - rid (int): last response id
|
||||
void SyncController::maindataAction()
|
||||
{
|
||||
auto lastResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastResponse")).toMap();
|
||||
auto lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastAcceptedResponse")).toMap();
|
||||
|
||||
QVariantMap data;
|
||||
QVariantHash torrents;
|
||||
|
||||
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
||||
|
||||
foreach (BitTorrent::TorrentHandle *const torrent, session->torrents()) {
|
||||
QVariantMap map = serialize(*torrent);
|
||||
map.remove(KEY_TORRENT_HASH);
|
||||
|
||||
// Calculated last activity time can differ from actual value by up to 10 seconds (this is a libtorrent issue).
|
||||
// So we don't need unnecessary updates of last activity time in response.
|
||||
if (lastResponse.contains("torrents") && lastResponse["torrents"].toHash().contains(torrent->hash()) &&
|
||||
lastResponse["torrents"].toHash()[torrent->hash()].toMap().contains(KEY_TORRENT_LAST_ACTIVITY_TIME)) {
|
||||
uint lastValue = lastResponse["torrents"].toHash()[torrent->hash()].toMap()[KEY_TORRENT_LAST_ACTIVITY_TIME].toUInt();
|
||||
if (qAbs(static_cast<int>(lastValue - map[KEY_TORRENT_LAST_ACTIVITY_TIME].toUInt())) < 15)
|
||||
map[KEY_TORRENT_LAST_ACTIVITY_TIME] = lastValue;
|
||||
}
|
||||
|
||||
torrents[torrent->hash()] = map;
|
||||
}
|
||||
|
||||
data["torrents"] = torrents;
|
||||
|
||||
QVariantList categories;
|
||||
foreach (const QString &category, session->categories().keys())
|
||||
categories << category;
|
||||
|
||||
data["categories"] = categories;
|
||||
|
||||
QVariantMap serverState = getTranserInfo();
|
||||
serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
|
||||
serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
|
||||
serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
|
||||
data["server_state"] = serverState;
|
||||
|
||||
const int acceptedResponseId {params()["rid"].toInt()};
|
||||
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
|
||||
|
||||
sessionManager()->session()->setData(QLatin1String("syncMainDataLastResponse"), lastResponse);
|
||||
sessionManager()->session()->setData(QLatin1String("syncMainDataLastAcceptedResponse"), lastAcceptedResponse);
|
||||
}
|
||||
|
||||
// GET param:
|
||||
// - hash (string): torrent hash
|
||||
// - rid (int): last response id
|
||||
void SyncController::torrentPeersAction()
|
||||
{
|
||||
auto lastResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastResponse")).toMap();
|
||||
auto lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastAcceptedResponse")).toMap();
|
||||
|
||||
const QString hash {params()["hash"]};
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
QVariantMap data;
|
||||
QVariantHash peers;
|
||||
QList<BitTorrent::PeerInfo> peersList = torrent->peers();
|
||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||
bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries();
|
||||
#else
|
||||
bool resolvePeerCountries = false;
|
||||
#endif
|
||||
|
||||
data[KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS] = resolvePeerCountries;
|
||||
|
||||
foreach (const BitTorrent::PeerInfo &pi, peersList) {
|
||||
if (pi.address().ip.isNull()) continue;
|
||||
QVariantMap peer;
|
||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||
if (resolvePeerCountries) {
|
||||
peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();
|
||||
peer[KEY_PEER_COUNTRY] = Net::GeoIPManager::CountryName(pi.country());
|
||||
}
|
||||
#endif
|
||||
peer[KEY_PEER_IP] = pi.address().ip.toString();
|
||||
peer[KEY_PEER_PORT] = pi.address().port;
|
||||
peer[KEY_PEER_CLIENT] = pi.client();
|
||||
peer[KEY_PEER_PROGRESS] = pi.progress();
|
||||
peer[KEY_PEER_DOWN_SPEED] = pi.payloadDownSpeed();
|
||||
peer[KEY_PEER_UP_SPEED] = pi.payloadUpSpeed();
|
||||
peer[KEY_PEER_TOT_DOWN] = pi.totalDownload();
|
||||
peer[KEY_PEER_TOT_UP] = pi.totalUpload();
|
||||
peer[KEY_PEER_CONNECTION_TYPE] = pi.connectionType();
|
||||
peer[KEY_PEER_FLAGS] = pi.flags();
|
||||
peer[KEY_PEER_FLAGS_DESCRIPTION] = pi.flagsDescription();
|
||||
peer[KEY_PEER_RELEVANCE] = pi.relevance();
|
||||
peer[KEY_PEER_FILES] = torrent->info().filesForPiece(pi.downloadingPieceIndex()).join(QLatin1String("\n"));
|
||||
|
||||
peers[pi.address().ip.toString() + ":" + QString::number(pi.address().port)] = peer;
|
||||
}
|
||||
|
||||
data["peers"] = peers;
|
||||
|
||||
const int acceptedResponseId {params()["rid"].toInt()};
|
||||
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
|
||||
|
||||
sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastResponse"), lastResponse);
|
||||
sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastAcceptedResponse"), lastAcceptedResponse);
|
||||
}
|
44
src/webui/api/synccontroller.h
Normal file
44
src/webui/api/synccontroller.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
class SyncController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(SyncController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void maindataAction();
|
||||
void torrentPeersAction();
|
||||
};
|
805
src/webui/api/torrentscontroller.cpp
Normal file
805
src/webui/api/torrentscontroller.cpp
Normal file
|
@ -0,0 +1,805 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "torrentscontroller.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QNetworkCookie>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/bittorrent/torrentinfo.h"
|
||||
#include "base/bittorrent/trackerentry.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "serialize/serialize_torrent.h"
|
||||
#include "apierror.h"
|
||||
|
||||
// Tracker keys
|
||||
const char KEY_TRACKER_URL[] = "url";
|
||||
const char KEY_TRACKER_STATUS[] = "status";
|
||||
const char KEY_TRACKER_MSG[] = "msg";
|
||||
const char KEY_TRACKER_PEERS[] = "num_peers";
|
||||
|
||||
// Web seed keys
|
||||
const char KEY_WEBSEED_URL[] = "url";
|
||||
|
||||
// Torrent keys (Properties)
|
||||
const char KEY_PROP_TIME_ELAPSED[] = "time_elapsed";
|
||||
const char KEY_PROP_SEEDING_TIME[] = "seeding_time";
|
||||
const char KEY_PROP_ETA[] = "eta";
|
||||
const char KEY_PROP_CONNECT_COUNT[] = "nb_connections";
|
||||
const char KEY_PROP_CONNECT_COUNT_LIMIT[] = "nb_connections_limit";
|
||||
const char KEY_PROP_DOWNLOADED[] = "total_downloaded";
|
||||
const char KEY_PROP_DOWNLOADED_SESSION[] = "total_downloaded_session";
|
||||
const char KEY_PROP_UPLOADED[] = "total_uploaded";
|
||||
const char KEY_PROP_UPLOADED_SESSION[] = "total_uploaded_session";
|
||||
const char KEY_PROP_DL_SPEED[] = "dl_speed";
|
||||
const char KEY_PROP_DL_SPEED_AVG[] = "dl_speed_avg";
|
||||
const char KEY_PROP_UP_SPEED[] = "up_speed";
|
||||
const char KEY_PROP_UP_SPEED_AVG[] = "up_speed_avg";
|
||||
const char KEY_PROP_DL_LIMIT[] = "dl_limit";
|
||||
const char KEY_PROP_UP_LIMIT[] = "up_limit";
|
||||
const char KEY_PROP_WASTED[] = "total_wasted";
|
||||
const char KEY_PROP_SEEDS[] = "seeds";
|
||||
const char KEY_PROP_SEEDS_TOTAL[] = "seeds_total";
|
||||
const char KEY_PROP_PEERS[] = "peers";
|
||||
const char KEY_PROP_PEERS_TOTAL[] = "peers_total";
|
||||
const char KEY_PROP_RATIO[] = "share_ratio";
|
||||
const char KEY_PROP_REANNOUNCE[] = "reannounce";
|
||||
const char KEY_PROP_TOTAL_SIZE[] = "total_size";
|
||||
const char KEY_PROP_PIECES_NUM[] = "pieces_num";
|
||||
const char KEY_PROP_PIECE_SIZE[] = "piece_size";
|
||||
const char KEY_PROP_PIECES_HAVE[] = "pieces_have";
|
||||
const char KEY_PROP_CREATED_BY[] = "created_by";
|
||||
const char KEY_PROP_LAST_SEEN[] = "last_seen";
|
||||
const char KEY_PROP_ADDITION_DATE[] = "addition_date";
|
||||
const char KEY_PROP_COMPLETION_DATE[] = "completion_date";
|
||||
const char KEY_PROP_CREATION_DATE[] = "creation_date";
|
||||
const char KEY_PROP_SAVE_PATH[] = "save_path";
|
||||
const char KEY_PROP_COMMENT[] = "comment";
|
||||
|
||||
// File keys
|
||||
const char KEY_FILE_NAME[] = "name";
|
||||
const char KEY_FILE_SIZE[] = "size";
|
||||
const char KEY_FILE_PROGRESS[] = "progress";
|
||||
const char KEY_FILE_PRIORITY[] = "priority";
|
||||
const char KEY_FILE_IS_SEED[] = "is_seed";
|
||||
const char KEY_FILE_PIECE_RANGE[] = "piece_range";
|
||||
const char KEY_FILE_AVAILABILITY[] = "availability";
|
||||
|
||||
namespace
|
||||
{
|
||||
using Utils::String::parseBool;
|
||||
using Utils::String::parseTriStateBool;
|
||||
|
||||
void applyToTorrents(const QStringList &hashes, const std::function<void (BitTorrent::TorrentHandle *torrent)> &func)
|
||||
{
|
||||
if ((hashes.size() == 1) && (hashes[0] == QLatin1String("all"))) {
|
||||
foreach (BitTorrent::TorrentHandle *torrent, BitTorrent::Session::instance()->torrents())
|
||||
func(torrent);
|
||||
}
|
||||
else {
|
||||
for (const QString &hash : hashes) {
|
||||
BitTorrent::TorrentHandle *torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (torrent)
|
||||
func(torrent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns all the torrents in JSON format.
|
||||
// The return value is a JSON-formatted list of dictionaries.
|
||||
// The dictionary keys are:
|
||||
// - "hash": Torrent hash
|
||||
// - "name": Torrent name
|
||||
// - "size": Torrent size
|
||||
// - "progress: Torrent progress
|
||||
// - "dlspeed": Torrent download speed
|
||||
// - "upspeed": Torrent upload speed
|
||||
// - "priority": Torrent priority (-1 if queuing is disabled)
|
||||
// - "num_seeds": Torrent seeds connected to
|
||||
// - "num_complete": Torrent seeds in the swarm
|
||||
// - "num_leechs": Torrent leechers connected to
|
||||
// - "num_incomplete": Torrent leechers in the swarm
|
||||
// - "ratio": Torrent share ratio
|
||||
// - "eta": Torrent ETA
|
||||
// - "state": Torrent state
|
||||
// - "seq_dl": Torrent sequential download state
|
||||
// - "f_l_piece_prio": Torrent first last piece priority state
|
||||
// - "force_start": Torrent force start state
|
||||
// - "category": Torrent category
|
||||
// GET params:
|
||||
// - filter (string): all, downloading, seeding, completed, paused, resumed, active, inactive
|
||||
// - category (string): torrent category for filtering by it (empty string means "uncategorized"; no "category" param presented means "any category")
|
||||
// - sort (string): name of column for sorting by its value
|
||||
// - reverse (bool): enable reverse sorting
|
||||
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
|
||||
// - offset (int): set offset (if less than 0 - offset from end)
|
||||
void TorrentsController::infoAction()
|
||||
{
|
||||
const QString filter {params()["filter"]};
|
||||
const QString category {params()["category"]};
|
||||
const QString sortedColumn {params()["sort"]};
|
||||
const bool reverse {parseBool(params()["reverse"], false)};
|
||||
int limit {params()["limit"].toInt()};
|
||||
int offset {params()["offset"].toInt()};
|
||||
|
||||
QVariantList torrentList;
|
||||
TorrentFilter torrentFilter(filter, TorrentFilter::AnyHash, category);
|
||||
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents()) {
|
||||
if (torrentFilter.match(torrent))
|
||||
torrentList.append(serialize(*torrent));
|
||||
}
|
||||
|
||||
std::sort(torrentList.begin(), torrentList.end()
|
||||
, [sortedColumn, reverse](const QVariant &torrent1, const QVariant &torrent2)
|
||||
{
|
||||
return reverse
|
||||
? (torrent1.toMap().value(sortedColumn) > torrent2.toMap().value(sortedColumn))
|
||||
: (torrent1.toMap().value(sortedColumn) < torrent2.toMap().value(sortedColumn));
|
||||
});
|
||||
|
||||
const int size = torrentList.size();
|
||||
// normalize offset
|
||||
if (offset < 0)
|
||||
offset = size + offset;
|
||||
if ((offset >= size) || (offset < 0))
|
||||
offset = 0;
|
||||
// normalize limit
|
||||
if (limit <= 0)
|
||||
limit = -1; // unlimited
|
||||
|
||||
if ((limit > 0) || (offset > 0))
|
||||
torrentList = torrentList.mid(offset, limit);
|
||||
|
||||
setResult(QJsonArray::fromVariantList(torrentList));
|
||||
}
|
||||
|
||||
// Returns the properties for a torrent in JSON format.
|
||||
// The return value is a JSON-formatted dictionary.
|
||||
// The dictionary keys are:
|
||||
// - "time_elapsed": Torrent elapsed time
|
||||
// - "seeding_time": Torrent elapsed time while complete
|
||||
// - "eta": Torrent ETA
|
||||
// - "nb_connections": Torrent connection count
|
||||
// - "nb_connections_limit": Torrent connection count limit
|
||||
// - "total_downloaded": Total data uploaded for torrent
|
||||
// - "total_downloaded_session": Total data downloaded this session
|
||||
// - "total_uploaded": Total data uploaded for torrent
|
||||
// - "total_uploaded_session": Total data uploaded this session
|
||||
// - "dl_speed": Torrent download speed
|
||||
// - "dl_speed_avg": Torrent average download speed
|
||||
// - "up_speed": Torrent upload speed
|
||||
// - "up_speed_avg": Torrent average upload speed
|
||||
// - "dl_limit": Torrent download limit
|
||||
// - "up_limit": Torrent upload limit
|
||||
// - "total_wasted": Total data wasted for torrent
|
||||
// - "seeds": Torrent connected seeds
|
||||
// - "seeds_total": Torrent total number of seeds
|
||||
// - "peers": Torrent connected peers
|
||||
// - "peers_total": Torrent total number of peers
|
||||
// - "share_ratio": Torrent share ratio
|
||||
// - "reannounce": Torrent next reannounce time
|
||||
// - "total_size": Torrent total size
|
||||
// - "pieces_num": Torrent pieces count
|
||||
// - "piece_size": Torrent piece size
|
||||
// - "pieces_have": Torrent pieces have
|
||||
// - "created_by": Torrent creator
|
||||
// - "last_seen": Torrent last seen complete
|
||||
// - "addition_date": Torrent addition date
|
||||
// - "completion_date": Torrent completion date
|
||||
// - "creation_date": Torrent creation date
|
||||
// - "save_path": Torrent save path
|
||||
// - "comment": Torrent comment
|
||||
void TorrentsController::propertiesAction()
|
||||
{
|
||||
checkParams({"hash"});
|
||||
|
||||
const QString hash {params()["hash"]};
|
||||
QVariantMap dataDict;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
dataDict[KEY_PROP_TIME_ELAPSED] = torrent->activeTime();
|
||||
dataDict[KEY_PROP_SEEDING_TIME] = torrent->seedingTime();
|
||||
dataDict[KEY_PROP_ETA] = torrent->eta();
|
||||
dataDict[KEY_PROP_CONNECT_COUNT] = torrent->connectionsCount();
|
||||
dataDict[KEY_PROP_CONNECT_COUNT_LIMIT] = torrent->connectionsLimit();
|
||||
dataDict[KEY_PROP_DOWNLOADED] = torrent->totalDownload();
|
||||
dataDict[KEY_PROP_DOWNLOADED_SESSION] = torrent->totalPayloadDownload();
|
||||
dataDict[KEY_PROP_UPLOADED] = torrent->totalUpload();
|
||||
dataDict[KEY_PROP_UPLOADED_SESSION] = torrent->totalPayloadUpload();
|
||||
dataDict[KEY_PROP_DL_SPEED] = torrent->downloadPayloadRate();
|
||||
dataDict[KEY_PROP_DL_SPEED_AVG] = torrent->totalDownload() / (1 + torrent->activeTime() - torrent->finishedTime());
|
||||
dataDict[KEY_PROP_UP_SPEED] = torrent->uploadPayloadRate();
|
||||
dataDict[KEY_PROP_UP_SPEED_AVG] = torrent->totalUpload() / (1 + torrent->activeTime());
|
||||
dataDict[KEY_PROP_DL_LIMIT] = torrent->downloadLimit() <= 0 ? -1 : torrent->downloadLimit();
|
||||
dataDict[KEY_PROP_UP_LIMIT] = torrent->uploadLimit() <= 0 ? -1 : torrent->uploadLimit();
|
||||
dataDict[KEY_PROP_WASTED] = torrent->wastedSize();
|
||||
dataDict[KEY_PROP_SEEDS] = torrent->seedsCount();
|
||||
dataDict[KEY_PROP_SEEDS_TOTAL] = torrent->totalSeedsCount();
|
||||
dataDict[KEY_PROP_PEERS] = torrent->leechsCount();
|
||||
dataDict[KEY_PROP_PEERS_TOTAL] = torrent->totalLeechersCount();
|
||||
const qreal ratio = torrent->realRatio();
|
||||
dataDict[KEY_PROP_RATIO] = ratio > BitTorrent::TorrentHandle::MAX_RATIO ? -1 : ratio;
|
||||
dataDict[KEY_PROP_REANNOUNCE] = torrent->nextAnnounce();
|
||||
dataDict[KEY_PROP_TOTAL_SIZE] = torrent->totalSize();
|
||||
dataDict[KEY_PROP_PIECES_NUM] = torrent->piecesCount();
|
||||
dataDict[KEY_PROP_PIECE_SIZE] = torrent->pieceLength();
|
||||
dataDict[KEY_PROP_PIECES_HAVE] = torrent->piecesHave();
|
||||
dataDict[KEY_PROP_CREATED_BY] = torrent->creator();
|
||||
dataDict[KEY_PROP_ADDITION_DATE] = torrent->addedTime().toTime_t();
|
||||
if (torrent->hasMetadata()) {
|
||||
dataDict[KEY_PROP_LAST_SEEN] = torrent->lastSeenComplete().isValid() ? static_cast<int>(torrent->lastSeenComplete().toTime_t()) : -1;
|
||||
dataDict[KEY_PROP_COMPLETION_DATE] = torrent->completedTime().isValid() ? static_cast<int>(torrent->completedTime().toTime_t()) : -1;
|
||||
dataDict[KEY_PROP_CREATION_DATE] = torrent->creationDate().toTime_t();
|
||||
}
|
||||
else {
|
||||
dataDict[KEY_PROP_LAST_SEEN] = -1;
|
||||
dataDict[KEY_PROP_COMPLETION_DATE] = -1;
|
||||
dataDict[KEY_PROP_CREATION_DATE] = -1;
|
||||
}
|
||||
dataDict[KEY_PROP_SAVE_PATH] = Utils::Fs::toNativePath(torrent->savePath());
|
||||
dataDict[KEY_PROP_COMMENT] = torrent->comment();
|
||||
|
||||
setResult(QJsonObject::fromVariantMap(dataDict));
|
||||
}
|
||||
|
||||
// Returns the trackers for a torrent in JSON format.
|
||||
// The return value is a JSON-formatted list of dictionaries.
|
||||
// The dictionary keys are:
|
||||
// - "url": Tracker URL
|
||||
// - "status": Tracker status
|
||||
// - "num_peers": Tracker peer count
|
||||
// - "msg": Tracker message (last)
|
||||
void TorrentsController::trackersAction()
|
||||
{
|
||||
checkParams({"hash"});
|
||||
|
||||
const QString hash {params()["hash"]};
|
||||
QVariantList trackerList;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
QHash<QString, BitTorrent::TrackerInfo> trackersData = torrent->trackerInfos();
|
||||
foreach (const BitTorrent::TrackerEntry &tracker, torrent->trackers()) {
|
||||
QVariantMap trackerDict;
|
||||
trackerDict[KEY_TRACKER_URL] = tracker.url();
|
||||
const BitTorrent::TrackerInfo data = trackersData.value(tracker.url());
|
||||
QString status;
|
||||
switch (tracker.status()) {
|
||||
case BitTorrent::TrackerEntry::NotContacted:
|
||||
status = tr("Not contacted yet"); break;
|
||||
case BitTorrent::TrackerEntry::Updating:
|
||||
status = tr("Updating..."); break;
|
||||
case BitTorrent::TrackerEntry::Working:
|
||||
status = tr("Working"); break;
|
||||
case BitTorrent::TrackerEntry::NotWorking:
|
||||
status = tr("Not working"); break;
|
||||
}
|
||||
trackerDict[KEY_TRACKER_STATUS] = status;
|
||||
trackerDict[KEY_TRACKER_PEERS] = data.numPeers;
|
||||
trackerDict[KEY_TRACKER_MSG] = data.lastMessage.trimmed();
|
||||
|
||||
trackerList.append(trackerDict);
|
||||
}
|
||||
|
||||
setResult(QJsonArray::fromVariantList(trackerList));
|
||||
}
|
||||
|
||||
// Returns the web seeds for a torrent in JSON format.
|
||||
// The return value is a JSON-formatted list of dictionaries.
|
||||
// The dictionary keys are:
|
||||
// - "url": Web seed URL
|
||||
void TorrentsController::webseedsAction()
|
||||
{
|
||||
checkParams({"hash"});
|
||||
|
||||
const QString hash {params()["hash"]};
|
||||
QVariantList webSeedList;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
foreach (const QUrl &webseed, torrent->urlSeeds()) {
|
||||
QVariantMap webSeedDict;
|
||||
webSeedDict[KEY_WEBSEED_URL] = webseed.toString();
|
||||
webSeedList.append(webSeedDict);
|
||||
}
|
||||
|
||||
setResult(QJsonArray::fromVariantList(webSeedList));
|
||||
}
|
||||
|
||||
// Returns the files in a torrent in JSON format.
|
||||
// The return value is a JSON-formatted list of dictionaries.
|
||||
// The dictionary keys are:
|
||||
// - "name": File name
|
||||
// - "size": File size
|
||||
// - "progress": File progress
|
||||
// - "priority": File priority
|
||||
// - "is_seed": Flag indicating if torrent is seeding/complete
|
||||
// - "piece_range": Piece index range, the first number is the starting piece index
|
||||
// and the second number is the ending piece index (inclusive)
|
||||
void TorrentsController::filesAction()
|
||||
{
|
||||
checkParams({"hash"});
|
||||
|
||||
const QString hash {params()["hash"]};
|
||||
QVariantList fileList;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
if (torrent->hasMetadata()) {
|
||||
const QVector<int> priorities = torrent->filePriorities();
|
||||
const QVector<qreal> fp = torrent->filesProgress();
|
||||
const QVector<qreal> fileAvailability = torrent->availableFileFractions();
|
||||
const BitTorrent::TorrentInfo info = torrent->info();
|
||||
for (int i = 0; i < torrent->filesCount(); ++i) {
|
||||
QVariantMap fileDict;
|
||||
fileDict[KEY_FILE_PROGRESS] = fp[i];
|
||||
fileDict[KEY_FILE_PRIORITY] = priorities[i];
|
||||
fileDict[KEY_FILE_SIZE] = torrent->fileSize(i);
|
||||
fileDict[KEY_FILE_AVAILABILITY] = fileAvailability[i];
|
||||
|
||||
QString fileName = torrent->filePath(i);
|
||||
if (fileName.endsWith(QB_EXT, Qt::CaseInsensitive))
|
||||
fileName.chop(QB_EXT.size());
|
||||
fileDict[KEY_FILE_NAME] = Utils::Fs::toNativePath(fileName);
|
||||
|
||||
const BitTorrent::TorrentInfo::PieceRange idx = info.filePieces(i);
|
||||
fileDict[KEY_FILE_PIECE_RANGE] = QVariantList {idx.first(), idx.last()};
|
||||
|
||||
if (i == 0)
|
||||
fileDict[KEY_FILE_IS_SEED] = torrent->isSeed();
|
||||
|
||||
fileList.append(fileDict);
|
||||
}
|
||||
}
|
||||
|
||||
setResult(QJsonArray::fromVariantList(fileList));
|
||||
}
|
||||
|
||||
// Returns an array of hashes (of each pieces respectively) for a torrent in JSON format.
|
||||
// The return value is a JSON-formatted array of strings (hex strings).
|
||||
void TorrentsController::pieceHashesAction()
|
||||
{
|
||||
checkParams({"hash"});
|
||||
|
||||
const QString hash {params()["hash"]};
|
||||
QVariantList pieceHashes;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
const QVector<QByteArray> hashes = torrent->info().pieceHashes();
|
||||
pieceHashes.reserve(hashes.size());
|
||||
foreach (const QByteArray &hash, hashes)
|
||||
pieceHashes.append(hash.toHex());
|
||||
|
||||
setResult(QJsonArray::fromVariantList(pieceHashes));
|
||||
}
|
||||
|
||||
// Returns an array of states (of each pieces respectively) for a torrent in JSON format.
|
||||
// The return value is a JSON-formatted array of ints.
|
||||
// 0: piece not downloaded
|
||||
// 1: piece requested or downloading
|
||||
// 2: piece already downloaded
|
||||
void TorrentsController::pieceStatesAction()
|
||||
{
|
||||
checkParams({"hash"});
|
||||
|
||||
const QString hash {params()["hash"]};
|
||||
QVariantList pieceStates;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
const QBitArray states = torrent->pieces();
|
||||
pieceStates.reserve(states.size());
|
||||
for (int i = 0; i < states.size(); ++i)
|
||||
pieceStates.append(static_cast<int>(states[i]) * 2);
|
||||
|
||||
const QBitArray dlstates = torrent->downloadingPieces();
|
||||
for (int i = 0; i < states.size(); ++i) {
|
||||
if (dlstates[i])
|
||||
pieceStates[i] = 1;
|
||||
}
|
||||
|
||||
setResult(QJsonArray::fromVariantList(pieceStates));
|
||||
}
|
||||
|
||||
void TorrentsController::addAction()
|
||||
{
|
||||
const QString urls = params()["urls"];
|
||||
|
||||
const bool skipChecking = parseBool(params()["skip_checking"], false);
|
||||
const bool seqDownload = parseBool(params()["sequentialDownload"], false);
|
||||
const bool firstLastPiece = parseBool(params()["firstLastPiecePrio"], false);
|
||||
const TriStateBool addPaused = parseTriStateBool(params()["paused"]);
|
||||
const TriStateBool rootFolder = parseTriStateBool(params()["root_folder"]);
|
||||
const QString savepath = params()["savepath"].trimmed();
|
||||
const QString category = params()["category"].trimmed();
|
||||
const QString cookie = params()["cookie"];
|
||||
const QString torrentName = params()["rename"].trimmed();
|
||||
const int upLimit = params()["upLimit"].toInt();
|
||||
const int dlLimit = params()["dlLimit"].toInt();
|
||||
|
||||
QList<QNetworkCookie> cookies;
|
||||
if (!cookie.isEmpty()) {
|
||||
const QStringList cookiesStr = cookie.split("; ");
|
||||
for (QString cookieStr : cookiesStr) {
|
||||
cookieStr = cookieStr.trimmed();
|
||||
int index = cookieStr.indexOf('=');
|
||||
if (index > 1) {
|
||||
QByteArray name = cookieStr.left(index).toLatin1();
|
||||
QByteArray value = cookieStr.right(cookieStr.length() - index - 1).toLatin1();
|
||||
cookies += QNetworkCookie(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::AddTorrentParams params;
|
||||
// TODO: Check if destination actually exists
|
||||
params.skipChecking = skipChecking;
|
||||
params.sequential = seqDownload;
|
||||
params.firstLastPiecePriority = firstLastPiece;
|
||||
params.addPaused = addPaused;
|
||||
params.createSubfolder = rootFolder;
|
||||
params.savePath = savepath;
|
||||
params.category = category;
|
||||
params.name = torrentName;
|
||||
params.uploadLimit = (upLimit > 0) ? upLimit : -1;
|
||||
params.downloadLimit = (dlLimit > 0) ? dlLimit : -1;
|
||||
|
||||
bool partialSuccess = false;
|
||||
for (QString url : urls.split('\n')) {
|
||||
url = url.trimmed();
|
||||
if (!url.isEmpty()) {
|
||||
Net::DownloadManager::instance()->setCookiesFromUrl(cookies, QUrl::fromEncoded(url.toUtf8()));
|
||||
partialSuccess |= BitTorrent::Session::instance()->addTorrent(url, params);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = data().constBegin(); it != data().constEnd(); ++it) {
|
||||
const BitTorrent::TorrentInfo torrentInfo = BitTorrent::TorrentInfo::load(it.value());
|
||||
if (!torrentInfo.isValid()) {
|
||||
throw APIError(APIErrorType::BadData
|
||||
, tr("Error: '%1' is not a valid torrent file.").arg(it.key()));
|
||||
}
|
||||
|
||||
partialSuccess |= BitTorrent::Session::instance()->addTorrent(torrentInfo, params);
|
||||
}
|
||||
|
||||
if (partialSuccess)
|
||||
setResult("Ok.");
|
||||
else
|
||||
setResult("Fails.");
|
||||
}
|
||||
|
||||
void TorrentsController::addTrackersAction()
|
||||
{
|
||||
checkParams({"hash", "urls"});
|
||||
|
||||
const QString hash = params()["hash"];
|
||||
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (torrent) {
|
||||
QList<BitTorrent::TrackerEntry> trackers;
|
||||
foreach (QString url, params()["urls"].split('\n')) {
|
||||
url = url.trimmed();
|
||||
if (!url.isEmpty())
|
||||
trackers << url;
|
||||
}
|
||||
torrent->addTrackers(trackers);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentsController::pauseAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
const QStringList hashes = params()["hashes"].split('|');
|
||||
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->pause(); });
|
||||
}
|
||||
|
||||
void TorrentsController::resumeAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
const QStringList hashes = params()["hashes"].split('|');
|
||||
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->resume(); });
|
||||
}
|
||||
|
||||
void TorrentsController::filePrioAction()
|
||||
{
|
||||
checkParams({"hash", "id", "priority"});
|
||||
|
||||
const QString hash = params()["hash"];
|
||||
int fileID = params()["id"].toInt();
|
||||
int priority = params()["priority"].toInt();
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
|
||||
if (torrent && torrent->hasMetadata())
|
||||
torrent->setFilePriority(fileID, priority);
|
||||
}
|
||||
|
||||
void TorrentsController::uploadLimitAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
QVariantMap map;
|
||||
foreach (const QString &hash, hashes) {
|
||||
int limit = -1;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (torrent)
|
||||
limit = torrent->uploadLimit();
|
||||
map[hash] = limit;
|
||||
}
|
||||
|
||||
setResult(QJsonObject::fromVariantMap(map));
|
||||
}
|
||||
|
||||
void TorrentsController::downloadLimitAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
QVariantMap map;
|
||||
foreach (const QString &hash, hashes) {
|
||||
int limit = -1;
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (torrent)
|
||||
limit = torrent->downloadLimit();
|
||||
map[hash] = limit;
|
||||
}
|
||||
|
||||
setResult(QJsonObject::fromVariantMap(map));
|
||||
}
|
||||
|
||||
void TorrentsController::setUploadLimitAction()
|
||||
{
|
||||
checkParams({"hashes", "limit"});
|
||||
|
||||
qlonglong limit = params()["limit"].toLongLong();
|
||||
if (limit == 0)
|
||||
limit = -1;
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *torrent) { torrent->setUploadLimit(limit); });
|
||||
}
|
||||
|
||||
void TorrentsController::setDownloadLimitAction()
|
||||
{
|
||||
checkParams({"hashes", "limit"});
|
||||
|
||||
qlonglong limit = params()["limit"].toLongLong();
|
||||
if (limit == 0)
|
||||
limit = -1;
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
applyToTorrents(hashes, [limit](BitTorrent::TorrentHandle *torrent) { torrent->setDownloadLimit(limit); });
|
||||
}
|
||||
|
||||
void TorrentsController::toggleSequentialDownloadAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->toggleSequentialDownload(); });
|
||||
}
|
||||
|
||||
void TorrentsController::toggleFirstLastPiecePrioAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->toggleFirstLastPiecePriority(); });
|
||||
}
|
||||
|
||||
void TorrentsController::setSuperSeedingAction()
|
||||
{
|
||||
checkParams({"hashes", "value"});
|
||||
|
||||
const bool value {parseBool(params()["value"], false)};
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *torrent) { torrent->setSuperSeeding(value); });
|
||||
}
|
||||
|
||||
void TorrentsController::setForceStartAction()
|
||||
{
|
||||
checkParams({"hashes", "value"});
|
||||
|
||||
const bool value {parseBool(params()["value"], false)};
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
applyToTorrents(hashes, [value](BitTorrent::TorrentHandle *torrent) { torrent->resume(value); });
|
||||
}
|
||||
|
||||
void TorrentsController::deleteAction()
|
||||
{
|
||||
checkParams({"hashes", "delete_files"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
const bool deleteFiles {parseBool(params()["delete_files"], false)};
|
||||
applyToTorrents(hashes, [deleteFiles](BitTorrent::TorrentHandle *torrent)
|
||||
{
|
||||
BitTorrent::Session::instance()->deleteTorrent(torrent->hash(), deleteFiles);
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentsController::increasePrioAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
|
||||
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
BitTorrent::Session::instance()->increaseTorrentsPriority(hashes);
|
||||
}
|
||||
|
||||
void TorrentsController::decreasePrioAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
|
||||
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
BitTorrent::Session::instance()->decreaseTorrentsPriority(hashes);
|
||||
}
|
||||
|
||||
void TorrentsController::topPrioAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
|
||||
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
BitTorrent::Session::instance()->topTorrentsPriority(hashes);
|
||||
}
|
||||
|
||||
void TorrentsController::bottomPrioAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
|
||||
throw APIError(APIErrorType::Conflict, tr("Torrent queueing must be enabled"));
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
BitTorrent::Session::instance()->bottomTorrentsPriority(hashes);
|
||||
}
|
||||
|
||||
void TorrentsController::setLocationAction()
|
||||
{
|
||||
checkParams({"hashes", "location"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split("|")};
|
||||
const QString newLocation {params()["location"].trimmed()};
|
||||
|
||||
// check if the location exists
|
||||
if (newLocation.isEmpty() || !QDir(newLocation).exists())
|
||||
return;
|
||||
|
||||
applyToTorrents(hashes, [newLocation](BitTorrent::TorrentHandle *torrent)
|
||||
{
|
||||
LogMsg(tr("WebUI Set location: moving \"%1\", from \"%2\" to \"%3\"")
|
||||
.arg(torrent->name()).arg(torrent->savePath()).arg(newLocation));
|
||||
torrent->move(Utils::Fs::expandPathAbs(newLocation));
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentsController::renameAction()
|
||||
{
|
||||
checkParams({"hash", "name"});
|
||||
|
||||
const QString hash = params()["hash"];
|
||||
QString name = params()["name"].trimmed();
|
||||
|
||||
if (name.isEmpty())
|
||||
throw APIError(APIErrorType::Conflict, tr("Incorrect torrent name"));
|
||||
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (!torrent)
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
name.replace(QRegularExpression("\r?\n|\r"), " ");
|
||||
qDebug() << "Renaming" << torrent->name() << "to" << name;
|
||||
torrent->setName(name);
|
||||
}
|
||||
|
||||
void TorrentsController::setAutoManagementAction()
|
||||
{
|
||||
checkParams({"hashes", "enable"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
const bool isEnabled {parseBool(params()["enable"], false)};
|
||||
|
||||
applyToTorrents(hashes, [isEnabled](BitTorrent::TorrentHandle *torrent)
|
||||
{
|
||||
torrent->setAutoTMMEnabled(isEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentsController::recheckAction()
|
||||
{
|
||||
checkParams({"hashes"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
applyToTorrents(hashes, [](BitTorrent::TorrentHandle *torrent) { torrent->forceRecheck(); });
|
||||
}
|
||||
|
||||
void TorrentsController::setCategoryAction()
|
||||
{
|
||||
checkParams({"hashes", "category"});
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
const QString category {params()["category"].trimmed()};
|
||||
applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *torrent)
|
||||
{
|
||||
if (!torrent->setCategory(category))
|
||||
throw APIError(APIErrorType::Conflict, tr("Incorrect category name"));
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentsController::createCategoryAction()
|
||||
{
|
||||
checkParams({"category"});
|
||||
|
||||
const QString category {params()["category"].trimmed()};
|
||||
if (!BitTorrent::Session::isValidCategoryName(category) && !category.isEmpty())
|
||||
throw APIError(APIErrorType::Conflict, tr("Incorrect category name"));
|
||||
|
||||
BitTorrent::Session::instance()->addCategory(category);
|
||||
}
|
||||
|
||||
void TorrentsController::removeCategoriesAction()
|
||||
{
|
||||
checkParams({"categories"});
|
||||
|
||||
const QStringList categories {params()["categories"].split('\n')};
|
||||
for (const QString &category : categories)
|
||||
BitTorrent::Session::instance()->removeCategory(category);
|
||||
}
|
74
src/webui/api/torrentscontroller.h
Normal file
74
src/webui/api/torrentscontroller.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
class TorrentsController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TorrentsController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void infoAction();
|
||||
void propertiesAction();
|
||||
void trackersAction();
|
||||
void webseedsAction();
|
||||
void filesAction();
|
||||
void pieceHashesAction();
|
||||
void pieceStatesAction();
|
||||
void resumeAction();
|
||||
void pauseAction();
|
||||
void recheckAction();
|
||||
void renameAction();
|
||||
void setCategoryAction();
|
||||
void createCategoryAction();
|
||||
void removeCategoriesAction();
|
||||
void addAction();
|
||||
void deleteAction();
|
||||
void addTrackersAction();
|
||||
void filePrioAction();
|
||||
void uploadLimitAction();
|
||||
void downloadLimitAction();
|
||||
void setUploadLimitAction();
|
||||
void setDownloadLimitAction();
|
||||
void increasePrioAction();
|
||||
void decreasePrioAction();
|
||||
void topPrioAction();
|
||||
void bottomPrioAction();
|
||||
void setLocationAction();
|
||||
void setAutoManagementAction();
|
||||
void setSuperSeedingAction();
|
||||
void setForceStartAction();
|
||||
void toggleSequentialDownloadAction();
|
||||
void toggleFirstLastPiecePrioAction();
|
||||
};
|
113
src/webui/api/transfercontroller.cpp
Normal file
113
src/webui/api/transfercontroller.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "transfercontroller.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
|
||||
const char KEY_TRANSFER_DLSPEED[] = "dl_info_speed";
|
||||
const char KEY_TRANSFER_DLDATA[] = "dl_info_data";
|
||||
const char KEY_TRANSFER_DLRATELIMIT[] = "dl_rate_limit";
|
||||
const char KEY_TRANSFER_UPSPEED[] = "up_info_speed";
|
||||
const char KEY_TRANSFER_UPDATA[] = "up_info_data";
|
||||
const char KEY_TRANSFER_UPRATELIMIT[] = "up_rate_limit";
|
||||
const char KEY_TRANSFER_DHT_NODES[] = "dht_nodes";
|
||||
const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status";
|
||||
|
||||
// Returns the global transfer information in JSON format.
|
||||
// The return value is a JSON-formatted dictionary.
|
||||
// The dictionary keys are:
|
||||
// - "dl_info_speed": Global download rate
|
||||
// - "dl_info_data": Data downloaded this session
|
||||
// - "up_info_speed": Global upload rate
|
||||
// - "up_info_data": Data uploaded this session
|
||||
// - "dl_rate_limit": Download rate limit
|
||||
// - "up_rate_limit": Upload rate limit
|
||||
// - "dht_nodes": DHT nodes connected to
|
||||
// - "connection_status": Connection status
|
||||
void TransferController::infoAction()
|
||||
{
|
||||
const BitTorrent::SessionStatus &sessionStatus = BitTorrent::Session::instance()->status();
|
||||
|
||||
QJsonObject dict;
|
||||
|
||||
dict[KEY_TRANSFER_DLSPEED] = static_cast<qint64>(sessionStatus.payloadDownloadRate);
|
||||
dict[KEY_TRANSFER_DLDATA] = static_cast<qint64>(sessionStatus.totalPayloadDownload);
|
||||
dict[KEY_TRANSFER_UPSPEED] = static_cast<qint64>(sessionStatus.payloadUploadRate);
|
||||
dict[KEY_TRANSFER_UPDATA] = static_cast<qint64>(sessionStatus.totalPayloadUpload);
|
||||
dict[KEY_TRANSFER_DLRATELIMIT] = BitTorrent::Session::instance()->downloadSpeedLimit();
|
||||
dict[KEY_TRANSFER_UPRATELIMIT] = BitTorrent::Session::instance()->uploadSpeedLimit();
|
||||
dict[KEY_TRANSFER_DHT_NODES] = static_cast<qint64>(sessionStatus.dhtNodes);
|
||||
if (!BitTorrent::Session::instance()->isListening())
|
||||
dict[KEY_TRANSFER_CONNECTION_STATUS] = QLatin1String("disconnected");
|
||||
else
|
||||
dict[KEY_TRANSFER_CONNECTION_STATUS] = QLatin1String(sessionStatus.hasIncomingConnections ? "connected" : "firewalled");
|
||||
|
||||
setResult(dict);
|
||||
}
|
||||
|
||||
void TransferController::uploadLimitAction()
|
||||
{
|
||||
setResult(QString::number(BitTorrent::Session::instance()->uploadSpeedLimit()));
|
||||
}
|
||||
|
||||
void TransferController::downloadLimitAction()
|
||||
{
|
||||
setResult(QString::number(BitTorrent::Session::instance()->downloadSpeedLimit()));
|
||||
}
|
||||
|
||||
void TransferController::setUploadLimitAction()
|
||||
{
|
||||
checkParams({"limit"});
|
||||
qlonglong limit = params()["limit"].toLongLong();
|
||||
if (limit == 0) limit = -1;
|
||||
|
||||
BitTorrent::Session::instance()->setUploadSpeedLimit(limit);
|
||||
}
|
||||
|
||||
void TransferController::setDownloadLimitAction()
|
||||
{
|
||||
checkParams({"limit"});
|
||||
qlonglong limit = params()["limit"].toLongLong();
|
||||
if (limit == 0) limit = -1;
|
||||
|
||||
BitTorrent::Session::instance()->setDownloadSpeedLimit(limit);
|
||||
}
|
||||
|
||||
void TransferController::toggleSpeedLimitsModeAction()
|
||||
{
|
||||
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
||||
session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
|
||||
}
|
||||
|
||||
void TransferController::speedLimitsModeAction()
|
||||
{
|
||||
setResult(QString::number(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled()));
|
||||
}
|
49
src/webui/api/transfercontroller.h
Normal file
49
src/webui/api/transfercontroller.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
class TransferController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TransferController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void infoAction();
|
||||
void speedLimitsModeAction();
|
||||
void toggleSpeedLimitsModeAction();
|
||||
void uploadLimitAction();
|
||||
void downloadLimitAction();
|
||||
void setUploadLimitAction();
|
||||
void setDownloadLimitAction();
|
||||
};
|
1134
src/webui/btjson.cpp
1134
src/webui/btjson.cpp
File diff suppressed because it is too large
Load diff
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2012, 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
|
||||
*/
|
||||
|
||||
#ifndef BTJSON_H
|
||||
#define BTJSON_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
class btjson
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(misc)
|
||||
|
||||
private:
|
||||
btjson() {}
|
||||
|
||||
public:
|
||||
static QByteArray getTorrents(QString filter = "all", QString category = QString(),
|
||||
QString sortedColumn = "name", bool reverse = false, int limit = 0, int offset = 0);
|
||||
static QByteArray getSyncMainData(int acceptedResponseId, QVariantMap &lastData, QVariantMap &lastAcceptedData);
|
||||
static QByteArray getSyncTorrentPeersData(int acceptedResponseId, QString hash, QVariantMap &lastData, QVariantMap &lastAcceptedData);
|
||||
static QByteArray getTrackersForTorrent(const QString& hash);
|
||||
static QByteArray getWebSeedsForTorrent(const QString& hash);
|
||||
static QByteArray getPropertiesForTorrent(const QString& hash);
|
||||
static QByteArray getFilesForTorrent(const QString& hash);
|
||||
static QByteArray getPieceHashesForTorrent(const QString &hash);
|
||||
static QByteArray getPieceStatesForTorrent(const QString &hash);
|
||||
static QByteArray getTransferInfo();
|
||||
static QByteArray getTorrentsRatesLimits(QStringList& hashes, bool downloadLimits);
|
||||
static QByteArray getLog(bool normal, bool info, bool warning, bool critical, int lastKnownId);
|
||||
static QByteArray getPeerLog(int lastKnownId);
|
||||
}; // class btjson
|
||||
|
||||
#endif // BTJSON_H
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014, 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -26,95 +26,118 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef WEBAPPLICATION_H
|
||||
#define WEBAPPLICATION_H
|
||||
#pragma once
|
||||
|
||||
#include <QStringList>
|
||||
#include "abstractwebapplication.h"
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
|
||||
class WebApplication : public AbstractWebApplication
|
||||
#include "api/isessionmanager.h"
|
||||
#include "base/http/irequesthandler.h"
|
||||
#include "base/http/responsebuilder.h"
|
||||
#include "base/http/types.h"
|
||||
#include "base/utils/version.h"
|
||||
|
||||
constexpr Utils::Version<int, 3, 2> API_VERSION {2, 0, 0};
|
||||
constexpr int COMPAT_API_VERSION = 18;
|
||||
constexpr int COMPAT_API_VERSION_MIN = 18;
|
||||
|
||||
class APIController;
|
||||
class WebApplication;
|
||||
|
||||
constexpr char C_SID[] = "SID"; // name of session id cookie
|
||||
constexpr int INACTIVE_TIME = 900; // Session inactive time (in secs = 15 min.)
|
||||
|
||||
class WebSession : public ISession
|
||||
{
|
||||
friend class WebApplication;
|
||||
|
||||
public:
|
||||
explicit WebSession(const QString &sid);
|
||||
|
||||
QString id() const override;
|
||||
uint timestamp() const;
|
||||
|
||||
QVariant getData(const QString &id) const override;
|
||||
void setData(const QString &id, const QVariant &data) override;
|
||||
|
||||
private:
|
||||
void updateTimestamp();
|
||||
|
||||
const QString m_sid;
|
||||
uint m_timestamp;
|
||||
QVariantHash m_data;
|
||||
};
|
||||
|
||||
class WebApplication
|
||||
: public QObject, public Http::IRequestHandler, public ISessionManager
|
||||
, private Http::ResponseBuilder
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(WebApplication)
|
||||
|
||||
#ifndef Q_MOC_RUN
|
||||
#define WEBAPI_PUBLIC
|
||||
#define WEBAPI_PRIVATE
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit WebApplication(QObject *parent = nullptr);
|
||||
~WebApplication() override;
|
||||
|
||||
Http::Response processRequest(const Http::Request &request, const Http::Environment &env);
|
||||
|
||||
QString clientId() const override;
|
||||
WebSession *session() override;
|
||||
void sessionStart() override;
|
||||
void sessionEnd() override;
|
||||
|
||||
const Http::Request &request() const;
|
||||
const Http::Environment &env() const;
|
||||
|
||||
private:
|
||||
// Actions
|
||||
void action_public_webui();
|
||||
void action_public_index();
|
||||
void action_public_login();
|
||||
void action_public_logout();
|
||||
void action_public_theme();
|
||||
void action_public_images();
|
||||
void action_query_torrents();
|
||||
void action_query_preferences();
|
||||
void action_query_transferInfo();
|
||||
void action_query_propertiesGeneral();
|
||||
void action_query_propertiesTrackers();
|
||||
void action_query_propertiesWebSeeds();
|
||||
void action_query_propertiesFiles();
|
||||
void action_query_getLog();
|
||||
void action_query_getPeerLog();
|
||||
void action_query_getPieceHashes();
|
||||
void action_query_getPieceStates();
|
||||
void action_sync_maindata();
|
||||
void action_sync_torrent_peers();
|
||||
void action_command_shutdown();
|
||||
void action_command_download();
|
||||
void action_command_upload();
|
||||
void action_command_addTrackers();
|
||||
void action_command_resumeAll();
|
||||
void action_command_pauseAll();
|
||||
void action_command_resume();
|
||||
void action_command_pause();
|
||||
void action_command_setPreferences();
|
||||
void action_command_setFilePrio();
|
||||
void action_command_getGlobalUpLimit();
|
||||
void action_command_getGlobalDlLimit();
|
||||
void action_command_setGlobalUpLimit();
|
||||
void action_command_setGlobalDlLimit();
|
||||
void action_command_getTorrentsUpLimit();
|
||||
void action_command_getTorrentsDlLimit();
|
||||
void action_command_setTorrentsUpLimit();
|
||||
void action_command_setTorrentsDlLimit();
|
||||
void action_command_alternativeSpeedLimitsEnabled();
|
||||
void action_command_toggleAlternativeSpeedLimits();
|
||||
void action_command_toggleSequentialDownload();
|
||||
void action_command_toggleFirstLastPiecePrio();
|
||||
void action_command_setSuperSeeding();
|
||||
void action_command_setForceStart();
|
||||
void action_command_delete();
|
||||
void action_command_deletePerm();
|
||||
void action_command_increasePrio();
|
||||
void action_command_decreasePrio();
|
||||
void action_command_topPrio();
|
||||
void action_command_bottomPrio();
|
||||
void action_command_setLocation();
|
||||
void action_command_rename();
|
||||
void action_command_setAutoTMM();
|
||||
void action_command_recheck();
|
||||
void action_command_setCategory();
|
||||
void action_command_addCategory();
|
||||
void action_command_removeCategories();
|
||||
void action_command_getSavePath();
|
||||
void action_version_api();
|
||||
void action_version_api_min();
|
||||
void action_version_qbittorrent();
|
||||
void doProcessRequest();
|
||||
void configure();
|
||||
|
||||
typedef void (WebApplication::*Action)();
|
||||
void registerAPIController(const QString &scope, APIController *controller);
|
||||
void declarePublicAPI(const QString &apiPath);
|
||||
|
||||
QString scope_;
|
||||
QString action_;
|
||||
QStringList args_;
|
||||
void sendFile(const QString &path);
|
||||
void sendWebUIFile();
|
||||
|
||||
void doProcessRequest() override;
|
||||
// Session management
|
||||
QString generateSid() const;
|
||||
void sessionInitialize();
|
||||
bool isAuthNeeded();
|
||||
bool isPublicAPI(const QString &scope, const QString &action) const;
|
||||
|
||||
bool isPublicScope();
|
||||
void parsePath();
|
||||
bool isCrossSiteRequest(const Http::Request &request) const;
|
||||
bool validateHostHeader(const QStringList &domains) const;
|
||||
|
||||
static QMap<QString, QMap<QString, Action> > initializeActions();
|
||||
static QMap<QString, QMap<QString, Action> > actions_;
|
||||
// Persistent data
|
||||
QMap<QString, WebSession *> m_sessions;
|
||||
|
||||
// Current data
|
||||
WebSession *m_currentSession = nullptr;
|
||||
Http::Request m_request;
|
||||
Http::Environment m_env;
|
||||
|
||||
const QRegularExpression m_apiPathPattern {(QLatin1String("^/api/v2/(?<scope>[A-Za-z_][A-Za-z_0-9]*)/(?<action>[A-Za-z_][A-Za-z_0-9]*)$"))};
|
||||
const QRegularExpression m_apiLegacyPathPattern {QLatin1String("^/(?<action>((sync|control|query)/[A-Za-z_][A-Za-z_0-9]*|login|logout))(/(?<hash>[^/]+))?$")};
|
||||
|
||||
QHash<QString, APIController *> m_apiControllers;
|
||||
QSet<QString> m_publicAPIs;
|
||||
bool m_isAltUIUsed = false;
|
||||
QString m_rootFolder;
|
||||
QStringList m_domainList;
|
||||
|
||||
struct TranslatedFile
|
||||
{
|
||||
QByteArray data;
|
||||
QDateTime lastModified;
|
||||
};
|
||||
QMap<QString, TranslatedFile> m_translatedFiles;
|
||||
};
|
||||
|
||||
#endif // WEBAPPLICATION_H
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace Net
|
|||
class DNSUpdater;
|
||||
}
|
||||
|
||||
class AbstractWebApplication;
|
||||
class WebApplication;
|
||||
|
||||
class WebUI : public QObject
|
||||
{
|
||||
|
@ -64,7 +64,7 @@ private:
|
|||
bool m_isErrored;
|
||||
QPointer<Http::Server> m_httpServer;
|
||||
QPointer<Net::DNSUpdater> m_dnsUpdater;
|
||||
QPointer<AbstractWebApplication> m_webapp;
|
||||
QPointer<WebApplication> m_webapp;
|
||||
quint16 m_port;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,17 +1,30 @@
|
|||
HEADERS += \
|
||||
$$PWD/abstractwebapplication.h \
|
||||
$$PWD/btjson.h \
|
||||
$$PWD/api/apicontroller.h \
|
||||
$$PWD/api/apierror.h \
|
||||
$$PWD/api/appcontroller.h \
|
||||
$$PWD/api/authcontroller.h \
|
||||
$$PWD/api/isessionmanager.h \
|
||||
$$PWD/api/logcontroller.h \
|
||||
$$PWD/api/rsscontroller.h \
|
||||
$$PWD/api/synccontroller.h \
|
||||
$$PWD/api/torrentscontroller.h \
|
||||
$$PWD/api/transfercontroller.h \
|
||||
$$PWD/api/serialize/serialize_torrent.h \
|
||||
$$PWD/extra_translations.h \
|
||||
$$PWD/jsonutils.h \
|
||||
$$PWD/prefjson.h \
|
||||
$$PWD/webapplication.h \
|
||||
$$PWD/websessiondata.h \
|
||||
$$PWD/webui.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/abstractwebapplication.cpp \
|
||||
$$PWD/btjson.cpp \
|
||||
$$PWD/prefjson.cpp \
|
||||
$$PWD/api/apicontroller.cpp \
|
||||
$$PWD/api/apierror.cpp \
|
||||
$$PWD/api/appcontroller.cpp \
|
||||
$$PWD/api/authcontroller.cpp \
|
||||
$$PWD/api/logcontroller.cpp \
|
||||
$$PWD/api/rsscontroller.cpp \
|
||||
$$PWD/api/synccontroller.cpp \
|
||||
$$PWD/api/torrentscontroller.cpp \
|
||||
$$PWD/api/transfercontroller.cpp \
|
||||
$$PWD/api/serialize/serialize_torrent.cpp \
|
||||
$$PWD/webapplication.cpp \
|
||||
$$PWD/webui.cpp
|
||||
|
||||
|
|
|
@ -1,47 +1,49 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>www/private/css/Core.css</file>
|
||||
<file>www/private/css/dynamicTable.css</file>
|
||||
<file>www/private/css/Layout.css</file>
|
||||
<file>www/private/css/style.css</file>
|
||||
<file>www/private/css/Tabs.css</file>
|
||||
<file>www/private/css/Window.css</file>
|
||||
<file>www/private/scripts/client.js</file>
|
||||
<file>www/private/scripts/clipboard.min.js</file>
|
||||
<file>www/private/scripts/contextmenu.js</file>
|
||||
<file>www/private/scripts/download.js</file>
|
||||
<file>www/private/scripts/dynamicTable.js</file>
|
||||
<file>www/private/scripts/excanvas-compressed.js</file>
|
||||
<file>www/private/scripts/misc.js</file>
|
||||
<file>www/private/scripts/mocha.js</file>
|
||||
<file>www/private/scripts/mocha-init.js</file>
|
||||
<file>www/private/scripts/mocha-yc.js</file>
|
||||
<file>www/private/scripts/mootools-1.2-core-yc.js</file>
|
||||
<file>www/private/scripts/mootools-1.2-more.js</file>
|
||||
<file>www/private/scripts/parametrics.js</file>
|
||||
<file>www/private/scripts/progressbar.js</file>
|
||||
<file>www/private/scripts/prop-files.js</file>
|
||||
<file>www/private/scripts/prop-general.js</file>
|
||||
<file>www/private/scripts/prop-trackers.js</file>
|
||||
<file>www/private/scripts/prop-webseeds.js</file>
|
||||
<file>www/private/about.html</file>
|
||||
<file>www/private/addtrackers.html</file>
|
||||
<file>www/private/confirmdeletion.html</file>
|
||||
<file>www/private/download.html</file>
|
||||
<file>www/private/downloadlimit.html</file>
|
||||
<file>www/private/filters.html</file>
|
||||
<file>www/private/index.html</file>
|
||||
<file>www/private/login.html</file>
|
||||
<file>www/public/about.html</file>
|
||||
<file>www/public/addtrackers.html</file>
|
||||
<file>www/public/confirmdeletion.html</file>
|
||||
<file>www/public/css/Core.css</file>
|
||||
<file>www/public/css/dynamicTable.css</file>
|
||||
<file>www/public/css/Layout.css</file>
|
||||
<file>www/private/newcategory.html</file>
|
||||
<file>www/private/preferences.html</file>
|
||||
<file>www/private/preferences_content.html</file>
|
||||
<file>www/private/properties.html</file>
|
||||
<file>www/private/properties_content.html</file>
|
||||
<file>www/private/rename.html</file>
|
||||
<file>www/private/setlocation.html</file>
|
||||
<file>www/private/statistics.html</file>
|
||||
<file>www/private/transferlist.html</file>
|
||||
<file>www/private/upload.html</file>
|
||||
<file>www/private/uploadlimit.html</file>
|
||||
<file>www/public/css/style.css</file>
|
||||
<file>www/public/css/Tabs.css</file>
|
||||
<file>www/public/css/Window.css</file>
|
||||
<file>www/public/download.html</file>
|
||||
<file>www/public/downloadlimit.html</file>
|
||||
<file>www/public/filters.html</file>
|
||||
<file>www/public/newcategory.html</file>
|
||||
<file>www/public/preferences.html</file>
|
||||
<file>www/public/preferences_content.html</file>
|
||||
<file>www/public/properties.html</file>
|
||||
<file>www/public/properties_content.html</file>
|
||||
<file>www/public/rename.html</file>
|
||||
<file>www/public/scripts/client.js</file>
|
||||
<file>www/public/scripts/clipboard.min.js</file>
|
||||
<file>www/public/scripts/contextmenu.js</file>
|
||||
<file>www/public/scripts/download.js</file>
|
||||
<file>www/public/scripts/dynamicTable.js</file>
|
||||
<file>www/public/scripts/excanvas-compressed.js</file>
|
||||
<file>www/public/scripts/misc.js</file>
|
||||
<file>www/public/scripts/mocha-init.js</file>
|
||||
<file>www/public/scripts/mocha-yc.js</file>
|
||||
<file>www/public/scripts/mocha.js</file>
|
||||
<file>www/public/scripts/mootools-1.2-core-yc.js</file>
|
||||
<file>www/public/scripts/mootools-1.2-more.js</file>
|
||||
<file>www/public/scripts/parametrics.js</file>
|
||||
<file>www/public/scripts/progressbar.js</file>
|
||||
<file>www/public/scripts/prop-files.js</file>
|
||||
<file>www/public/scripts/prop-general.js</file>
|
||||
<file>www/public/scripts/prop-trackers.js</file>
|
||||
<file>www/public/scripts/prop-webseeds.js</file>
|
||||
<file>www/public/setlocation.html</file>
|
||||
<file>www/public/statistics.html</file>
|
||||
<file>www/public/transferlist.html</file>
|
||||
<file>www/public/upload.html</file>
|
||||
<file>www/public/uploadlimit.html</file>
|
||||
<file>www/public/login.html</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<img src="images/skin/mascot.png" align="right"></img>
|
||||
<img src="images/skin/mascot.png" style="float: right;" alt="qBittorrent Mascot" />
|
||||
<h3>qBittorrent ${VERSION} QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]</h3>
|
||||
<p>QBT_TR(An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.)QBT_TR[CONTEXT=about]</p>
|
||||
<p>Copyright (c) 2011-2018 The qBittorrent project</p>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Trackers addition dialog)QBT_TR[CONTEXT=TrackersAdditionDlg]</title>
|
||||
|
@ -13,9 +13,12 @@
|
|||
new Event(e).stop();
|
||||
var hash = new URI().getData('hash');
|
||||
new Request({
|
||||
url: 'command/addTrackers',
|
||||
url: 'api/v2/torrents/addTrackers',
|
||||
method: 'post',
|
||||
data: {hash: hash, urls: $('trackersUrls').value},
|
||||
data: {
|
||||
hash: hash,
|
||||
urls: $('trackersUrls').value
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
|
@ -25,12 +28,12 @@
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<div style="text-align: center;">
|
||||
<br/>
|
||||
<h2 class="vcenter">QBT_TR(List of trackers to add (one per line):)QBT_TR[CONTEXT=TrackersAdditionDlg]</h2>
|
||||
<textarea name="list" id="trackersUrls" rows="10" cols="1"></textarea>
|
||||
<br/>
|
||||
<input type="button" value="QBT_TR(Add)QBT_TR[CONTEXT=HttpServer]" id="addTrackersButton"/>
|
||||
</center>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Deletion confirmation - qBittorrent)QBT_TR[CONTEXT=confirmDeletionDlg]</title>
|
||||
|
@ -17,14 +17,14 @@
|
|||
$('confirmBtn').addEvent('click', function(e){
|
||||
parent.torrentsTable.deselectAll();
|
||||
new Event(e).stop();
|
||||
var cmd = 'command/delete';
|
||||
if($('deleteFromDiskCB').get('checked'))
|
||||
cmd = 'command/deletePerm';
|
||||
var cmd = 'api/v2/torrents/delete';
|
||||
var deleteFiles = $('deleteFromDiskCB').get('checked');
|
||||
new Request({
|
||||
url: cmd,
|
||||
method: 'post',
|
||||
data: {
|
||||
'hashes': hashes.join('|')
|
||||
'hashes': hashes.join('|'),
|
||||
'deleteFiles': deleteFiles
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.closeWindows();
|
481
src/webui/www/private/css/style.css
Normal file
481
src/webui/www/private/css/style.css
Normal file
|
@ -0,0 +1,481 @@
|
|||
/* Reset */
|
||||
|
||||
/*ul,ol,dl,li,dt,dd,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input,object,iframe { margin: 0; padding: 0; }*/
|
||||
a img,:link img,:visited img { border: none; }
|
||||
/*table { border-collapse: collapse; border-spacing: 0; }*/
|
||||
:focus { outline: none; }
|
||||
|
||||
/* Structure */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.aside {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
h2, h3, h4 {
|
||||
margin: 0;
|
||||
padding: 0 0 5px 0;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#mochaPage h3 {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
padding: 6px 0 6px 0;
|
||||
margin: 0 0 8px 0;
|
||||
border-bottom: 1px solid #bbb;
|
||||
}
|
||||
|
||||
#error_div {
|
||||
color: #f00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #e60;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 0 9px 0;
|
||||
}
|
||||
|
||||
/* List Elements */
|
||||
|
||||
ul {
|
||||
list-style: outside;
|
||||
margin: 0 0 9px 16px;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
padding: 0 0 9px 0;
|
||||
}
|
||||
|
||||
/* Code */
|
||||
|
||||
pre {
|
||||
background-color: #f6f6f6;
|
||||
color: #006600;
|
||||
display: block;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 11px;
|
||||
max-height: 250px;
|
||||
overflow: auto;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 10px;
|
||||
border: 1px solid #d1d7dc;
|
||||
}
|
||||
|
||||
/* Dividers */
|
||||
|
||||
hr {
|
||||
background-color: #ddd;
|
||||
color: #ccc;
|
||||
height: 1px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.vcenter {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#urls {
|
||||
width:90%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#trackersUrls {
|
||||
width:90%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#Filters ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#Filters ul li {
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
#Filters ul img {
|
||||
padding: 2px 4px;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.selectedFilter {
|
||||
background-color: #415A8D;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.selectedFilter a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#properties {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
a.propButton {
|
||||
border: 1px solid rgb(85, 81, 91);
|
||||
/*border-radius: 3px;*/
|
||||
padding: 2px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
a.propButton img {
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
.scrollableMenu {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* context menu specific */
|
||||
|
||||
.contextMenu { border:1px solid #999; padding:0; background:#eee; list-style-type:none; display:none;}
|
||||
.contextMenu .separator { border-top:1px solid #999; }
|
||||
.contextMenu li { margin:0; padding:0;}
|
||||
.contextMenu li a {
|
||||
display: block;
|
||||
padding: 5px 20px 5px 5px;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
font-family: tahoma,arial,sans-serif;
|
||||
color: #000;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.contextMenu li a:hover { background-color:#ddd; }
|
||||
.contextMenu li a.disabled { color:#ccc; font-style:italic; }
|
||||
.contextMenu li a.disabled:hover { background-color:#eee; }
|
||||
.contextMenu li ul {
|
||||
padding: 0;
|
||||
border:1px solid #999; padding:0; background:#eee;
|
||||
list-style-type:none;
|
||||
position: absolute;
|
||||
left: -999em;
|
||||
z-index: 8000;
|
||||
margin: -29px 0 0 100%;
|
||||
width: 164px;
|
||||
}
|
||||
.contextMenu li ul li a {
|
||||
position: relative;
|
||||
}
|
||||
.contextMenu li a.arrow-right, .contextMenu li a:hover.arrow-right {
|
||||
background-image: url(../images/skin/arrow-right.gif);
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
}
|
||||
.contextMenu li:hover ul,
|
||||
.contextMenu li.ieHover ul,
|
||||
.contextMenu li li.ieHover ul,
|
||||
.contextMenu li li li.ieHover ul,
|
||||
.contextMenu li li:hover ul,
|
||||
.contextMenu li li li:hover ul { /* lists nested under hovered list items */
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.contextMenu li img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-bottom: -4px;
|
||||
-ms-interpolation-mode : bicubic;
|
||||
}
|
||||
|
||||
/* Sliders */
|
||||
|
||||
.slider {
|
||||
clear: both;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
width: 400px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.sliderWrapper {
|
||||
position: relative;
|
||||
font-size: 1px;
|
||||
line-height: 1px;
|
||||
height: 9px;
|
||||
width: 422px;
|
||||
}
|
||||
|
||||
.sliderarea {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 7px;
|
||||
width: 420px;
|
||||
font-size: 1px;
|
||||
line-height: 1px;
|
||||
background: #f2f2f2 url(../images/skin/slider-area.gif) repeat-x;
|
||||
border: 1px solid #a3a3a3;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sliderknob {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 9px;
|
||||
width: 19px;
|
||||
font-size: 1px;
|
||||
line-height: 1px;
|
||||
background: url(../images/skin/knob.gif) no-repeat;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.update {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.mochaToolButton {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Mocha Customization */
|
||||
#mochaToolbar {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#mochaToolbar .divider {
|
||||
background-image: url(../images/skin/toolbox-divider.gif);
|
||||
background-repeat: no-repeat;
|
||||
background-position: left center;
|
||||
padding-left: 14px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.MyMenuIcon {
|
||||
margin-left: -18px;
|
||||
margin-bottom: -3px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
/* Tri-state checkbox */
|
||||
|
||||
label.tristate {
|
||||
background: url(../images/3-state-checkbox.gif) 0 0 no-repeat;
|
||||
display: block;
|
||||
float: left;
|
||||
height: 13px;
|
||||
margin: .15em 8px 5px 0px;
|
||||
overflow: hidden;
|
||||
text-indent: -999em;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
label.checked {
|
||||
background-position: 0 -13px;
|
||||
}
|
||||
|
||||
label.partial {
|
||||
background-position: 0 -26px;
|
||||
}
|
||||
|
||||
fieldset.settings {
|
||||
border: solid 1px black;
|
||||
border-radius: 8px;
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
padding: 4px 4px 4px 10px;
|
||||
}
|
||||
|
||||
fieldset.settings legend {
|
||||
margin-left: 8px;
|
||||
padding: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
fieldset.settings label {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
fieldset.settings .leftLabelSmall {
|
||||
width: 5em;
|
||||
float: left;
|
||||
text-align: right;
|
||||
margin-right: 0.5em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
fieldset.settings .leftLabelLarge {
|
||||
width: 14em;
|
||||
float: left;
|
||||
text-align: right;
|
||||
margin-right: 0.5em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.formRow {
|
||||
clear: left;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.filterTitle {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
ul.filterList {
|
||||
margin: 0 0 0 16px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
ul.filterList a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul.filterList li:hover {
|
||||
background-color: #e60;
|
||||
}
|
||||
|
||||
ul.filterList li:hover a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
td.generalLabel {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
width: 1px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#filesTable {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#trackersTable, #webseedsTable {
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
#addTrackersPlus {
|
||||
width: 16px;
|
||||
cursor: pointer;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
|
||||
.unselectable {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#prop_general {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#watched_folders_tab {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#watched_folders_tab td, #watched_folders_tab th {
|
||||
padding: 2px 4px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.select-watched-folder-editable {
|
||||
position:relative;
|
||||
background-color: white;
|
||||
border: solid grey 1px;
|
||||
width: 160px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.select-watched-folder-editable select {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
border: none;
|
||||
width: 160px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.select-watched-folder-editable input {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 140px;
|
||||
padding: 1px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.select-watched-folder-editable select:focus, .select-editable input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Workaround to prevent the transfer list from
|
||||
* disappearing when zooming in the browser.
|
||||
*/
|
||||
#filtersColumn_handle {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
#error_div {
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.combo_priority {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
td.statusBarSeparator {
|
||||
width: 22px;
|
||||
background-image: url('../images/skin/toolbox-divider.gif');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 1px;
|
||||
background-size: 2px 18px;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Add Torrent Links)QBT_TR[CONTEXT=downloadFromURL]</title>
|
||||
|
@ -10,8 +10,8 @@
|
|||
</head>
|
||||
<body>
|
||||
<iframe id="download_frame" name="download_frame" class="invisible" src="javascript:false;"></iframe>
|
||||
<form action="command/download" enctype="multipart/form-data" method="post" id="downloadForm" style="text-align: center;" target="download_frame">
|
||||
<center>
|
||||
<form action="api/v2/torrents/add" enctype="multipart/form-data" method="post" id="downloadForm" style="text-align: center;" target="download_frame">
|
||||
<div style="text-align: center;">
|
||||
<br/>
|
||||
<h2 class="vcenter">QBT_TR(Download Torrents from their URLs or Magnet links)QBT_TR[CONTEXT=HttpServer]</h2>
|
||||
<textarea id="urls" rows="10" name="urls"></textarea>
|
||||
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
<div class="formRow">
|
||||
<label for="rename" class="leftLabelLarge">QBT_TR(Rename torrent)QBT_TR[CONTEXT=HttpServer]</label>
|
||||
<input type="text" name="rename" style="width: 16em;"/>
|
||||
<input type="text" id="rename" name="rename" style="width: 16em;"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="category" class="leftLabelLarge">QBT_TR(Category:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
|
@ -40,33 +40,33 @@
|
|||
</div>
|
||||
<div class="formRow">
|
||||
<label for="skip_checking" class="leftLabelLarge">QBT_TR(Skip hash check)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
<input type="checkbox" name="skip_checking" value="true"/>
|
||||
<input type="checkbox" id="skip_checking" name="skip_checking" value="true"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="root_folder" class="leftLabelLarge">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
<input type="checkbox" name="root_folder" value="true" checked="checked"/>
|
||||
</div>
|
||||
<input type="checkbox" id="root_folder" name="root_folder" value="true" checked="checked"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="sequentialDownload" class="leftLabelLarge">QBT_TR(Download in sequential order)QBT_TR[CONTEXT=TransferListWidget]</label>
|
||||
<input type="checkbox" name="sequentialDownload" value="true"/>
|
||||
<input type="checkbox" id="sequentialDownload" name="sequentialDownload" value="true"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="firstLastPiecePrio" class="leftLabelLarge">QBT_TR(Download first and last pieces first)QBT_TR[CONTEXT=TransferListWidget]</label>
|
||||
<input type="checkbox" name="firstLastPiecePrio" value="true"/>
|
||||
<input type="checkbox" id="firstLastPiecePrio" name="firstLastPiecePrio" value="true"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="dlLimit" class="leftLabelLarge">QBT_TR(Limit download rate)QBT_TR[CONTEXT=HttpServer]</label>
|
||||
<input type="text" name="dlLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
<input type="text" id="dlLimit" name="dlLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="upLimit" class="leftLabelLarge">QBT_TR(Limit upload rate)QBT_TR[CONTEXT=HttpServer]</label>
|
||||
<input type="text" name="upLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
<input type="text" id="upLimit" name="upLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
</div>
|
||||
<div id="submitbutton" style="margin-top: 12px; text-align: center;">
|
||||
<button type="submit" id="submitButton">QBT_TR(Download)QBT_TR[CONTEXT=downloadFromURL]</button>
|
||||
</div>
|
||||
</center>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Torrent Download Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]</title>
|
||||
|
@ -25,7 +25,7 @@
|
|||
var limit = $("dllimitUpdatevalue").value.toInt() * 1024;
|
||||
if (hashes[0] == "global") {
|
||||
new Request({
|
||||
url: 'command/setGlobalDlLimit',
|
||||
url: 'api/v2/transfer/setDownloadLimit',
|
||||
method: 'post',
|
||||
data: {
|
||||
'limit': limit
|
||||
|
@ -38,7 +38,7 @@
|
|||
}
|
||||
else {
|
||||
new Request({
|
||||
url: 'command/setTorrentsDlLimit',
|
||||
url: 'api/v2/torrents/setDownloadLimit',
|
||||
method: 'post',
|
||||
data: {
|
||||
'hashes': hashes.join('|'),
|
|
@ -1,14 +1,14 @@
|
|||
<span class="filterTitle">QBT_TR(Status)QBT_TR[CONTEXT=TransferListFiltersWidget]</span>
|
||||
<ul class="filterList">
|
||||
<li id="all_filter"><a href="#" onclick="setFilter('all');return false;"><img src="images/skin/filterall.png"/>QBT_TR(All (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="downloading_filter"><a href="#" onclick="setFilter('downloading');return false;"><img src="images/skin/downloading.png"/>QBT_TR(Downloading (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="seeding_filter"><a href="#" onclick="setFilter('seeding');return false;"><img src="images/skin/uploading.png"/>QBT_TR(Seeding (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="completed_filter"><a href="#" onclick="setFilter('completed');return false;"><img src="images/skin/completed.png"/>QBT_TR(Completed (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="resumed_filter"><a href="#" onclick="setFilter('resumed');return false;"><img src="images/skin/resumed.png"/>QBT_TR(Resumed (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="paused_filter"><a href="#" onclick="setFilter('paused');return false;"><img src="images/skin/paused.png"/>QBT_TR(Paused (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="active_filter"><a href="#" onclick="setFilter('active');return false;"><img src="images/skin/filteractive.png"/>QBT_TR(Active (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="inactive_filter"><a href="#" onclick="setFilter('inactive');return false;"><img src="images/skin/filterinactive.png"/>QBT_TR(Inactive (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="errored_filter"><a href="#" onclick="setFilter('errored');return false;"><img src="images/skin/error.png"/>QBT_TR(Errored (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="all_filter"><a href="#" onclick="setFilter('all');return false;"><img src="images/skin/filterall.png" alt="All" />QBT_TR(All (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="downloading_filter"><a href="#" onclick="setFilter('downloading');return false;"><img src="images/skin/downloading.png" alt="Downloading" />QBT_TR(Downloading (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="seeding_filter"><a href="#" onclick="setFilter('seeding');return false;"><img src="images/skin/uploading.png" alt="Seeding" />QBT_TR(Seeding (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="completed_filter"><a href="#" onclick="setFilter('completed');return false;"><img src="images/skin/completed.png" alt="Completed" />QBT_TR(Completed (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="resumed_filter"><a href="#" onclick="setFilter('resumed');return false;"><img src="images/skin/resumed.png" alt="Resumed" />QBT_TR(Resumed (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="paused_filter"><a href="#" onclick="setFilter('paused');return false;"><img src="images/skin/paused.png" alt="Paused" />QBT_TR(Paused (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="active_filter"><a href="#" onclick="setFilter('active');return false;"><img src="images/skin/filteractive.png" alt="Active" />QBT_TR(Active (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="inactive_filter"><a href="#" onclick="setFilter('inactive');return false;"><img src="images/skin/filterinactive.png" alt="Inactive" />QBT_TR(Inactive (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
<li id="errored_filter"><a href="#" onclick="setFilter('errored');return false;"><img src="images/skin/error.png" alt="Errored" />QBT_TR(Errored (0))QBT_TR[CONTEXT=StatusFiltersWidget]</a></li>
|
||||
</ul>
|
||||
<br/>
|
||||
<span class="filterTitle">QBT_TR(Categories)QBT_TR[CONTEXT=TransferListFiltersWidget]</span>
|
|
@ -1,9 +1,10 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=10; IE=9; IE=8;" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=10" />
|
||||
<title>qBittorrent ${VERSION} QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]</title>
|
||||
<link rel="icon" type="image/png" href="images/skin/qbittorrent16.png" />
|
||||
<link rel="stylesheet" href="css/dynamicTable.css" type="text/css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||
<!--<link rel="stylesheet" type="text/css" href="css/Content.css" />-->
|
||||
|
@ -50,12 +51,10 @@
|
|||
<li class="divider"><a id="resumeLink"><img class="MyMenuIcon" alt="QBT_TR(&Resume)QBT_TR[CONTEXT=MainWindow]" src="theme/media-playback-start" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(&Resume)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="pauseLink"><img class="MyMenuIcon" src="theme/media-playback-pause" alt="QBT_TR(&Pause)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(&Pause)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li class="divider"><a id="deleteLink"><img class="MyMenuIcon" src="theme/list-remove" alt="QBT_TR(&Delete)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(&Delete)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<span id="queueingLinks">
|
||||
<li class="divider"><a id="topPrioLink"><img class="MyMenuIcon" src="theme/go-top" alt="QBT_TR(Top Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Top Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="increasePrioLink"><img class="MyMenuIcon" src="theme/go-up" alt="QBT_TR(Increase Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Increase Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="decreasePrioLink"><img class="MyMenuIcon" src="theme/go-down" alt="QBT_TR(Decrease Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Decrease Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="bottomPrioLink"><img class="MyMenuIcon" src="theme/go-bottom" alt="QBT_TR(Minimum Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Minimum Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
</span>
|
||||
<li id="topPrioItem" class="divider"><a id="topPrioLink"><img class="MyMenuIcon" src="theme/go-top" alt="QBT_TR(Top Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Top Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="increasePrioItem"><a id="increasePrioLink"><img class="MyMenuIcon" src="theme/go-up" alt="QBT_TR(Increase Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Increase Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="decreasePrioItem"><a id="decreasePrioLink"><img class="MyMenuIcon" src="theme/go-down" alt="QBT_TR(Decrease Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Decrease Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="bottomPrioItem"><a id="bottomPrioLink"><img class="MyMenuIcon" src="theme/go-bottom" alt="QBT_TR(Minimum Priority)QBT_TR[CONTEXT=MainWindow]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Minimum Priority)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li class="divider"><a id="recheckLink"><img class="MyMenuIcon" src="theme/document-edit-verify" alt="QBT_TR(Force Recheck)QBT_TR[CONTEXT=TransferListWidget]" width="16" height="16" onload="fixPNG(this)"/>QBT_TR(Force recheck)QBT_TR[CONTEXT=TransferListWidget]</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]</title>
|
||||
|
@ -33,7 +33,7 @@
|
|||
var hashesList = new URI().getData('hashes');
|
||||
if (!hashesList) {
|
||||
new Request({
|
||||
url: 'command/addCategory',
|
||||
url: 'api/v2/torrents/createCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
category: categoryName
|
||||
|
@ -46,7 +46,7 @@
|
|||
else
|
||||
{
|
||||
new Request({
|
||||
url: 'command/setCategory',
|
||||
url: 'api/v2/torrents/setCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashesList,
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]</title>
|
|
@ -17,7 +17,7 @@
|
|||
<label for="appendext_checkbox">QBT_TR(Append .!qB extension to incomplete files)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</span><br/><br/>
|
||||
QBT_TR(Automatically add torrents from:)QBT_TR[CONTEXT=OptionsDialog]<br/>
|
||||
<table border="1" id="watched_folders_tab">
|
||||
<table id="watched_folders_tab" style="border: 1px solid black;">
|
||||
<thead><tr><th>QBT_TR(Monitored Folder)QBT_TR[CONTEXT=ScanFoldersModel]</th><th>QBT_TR(Override Save Location)QBT_TR[CONTEXT=ScanFoldersModel]</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
<tfoot><tr>
|
||||
|
@ -70,7 +70,7 @@
|
|||
<legend><input type="checkbox" id="autorun_checkbox" onclick="updateAutoRun();"/>
|
||||
<label for="autorun_checkbox">QBT_TR(Run external program on torrent completion)QBT_TR[CONTEXT=OptionsDialog]</label></legend>
|
||||
<input type="text" id="autorunProg_txt" style="width: 400px;"/><br/>
|
||||
<i>QBT_TR(Supported parameters (case sensitive):)QBT_TR[CONTEXT=OptionsDialog]
|
||||
<div style="font-style: italic;">QBT_TR(Supported parameters (case sensitive):)QBT_TR[CONTEXT=OptionsDialog]
|
||||
<ul>
|
||||
<li>QBT_TR(%N: Torrent name)QBT_TR[CONTEXT=OptionsDialog]</li>
|
||||
<li>QBT_TR(%L: Category)QBT_TR[CONTEXT=OptionsDialog]</li>
|
||||
|
@ -83,7 +83,7 @@
|
|||
<li>QBT_TR(%I: Info hash)QBT_TR[CONTEXT=OptionsDialog]</li>
|
||||
</ul>
|
||||
QBT_TR(Tip: Encapsulate parameter with quotation marks to avoid text being cut off at whitespace (e.g., "%N"))QBT_TR[CONTEXT=OptionsDialog]
|
||||
</i>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
@ -256,7 +256,7 @@
|
|||
<option value="8">QBT_TR(Saturday)QBT_TR[CONTEXT=HttpServer]</option>
|
||||
<option value="9">QBT_TR(Sunday)QBT_TR[CONTEXT=HttpServer]</option>
|
||||
</select>
|
||||
</br/>
|
||||
<br/>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
|
@ -472,7 +472,7 @@
|
|||
</div>
|
||||
|
||||
<br/>
|
||||
<center><input type="button" value="QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]" onclick="applyPreferences();"/></center>
|
||||
<div style="text-align: center;"><input type="button" value="QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]" onclick="applyPreferences();"/></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
// Downloads tab
|
||||
|
@ -826,7 +826,7 @@ time_padding = function(val) {
|
|||
}
|
||||
|
||||
loadPreferences = function() {
|
||||
var url = 'query/preferences';
|
||||
var url = 'api/v2/app/preferences';
|
||||
var request = new Request.JSON({
|
||||
url: url,
|
||||
method: 'get',
|
||||
|
@ -1374,18 +1374,19 @@ applyPreferences = function() {
|
|||
// Send it to qBT
|
||||
var json_str = JSON.encode(settings);
|
||||
|
||||
new Request({url: 'command/setPreferences',
|
||||
new Request({url: 'api/v2/app/setPreferences',
|
||||
method: 'post',
|
||||
data: {'json': json_str,
|
||||
},
|
||||
onFailure: function() {
|
||||
alert("QBT_TR(Unable to save program preferences, qBittorrent is probably unreachable.)QBT_TR[CONTEXT=HttpServer]");
|
||||
window.parent.closeWindows();
|
||||
},
|
||||
data: {
|
||||
'json': json_str,
|
||||
},
|
||||
onFailure: function() {
|
||||
alert("QBT_TR(Unable to save program preferences, qBittorrent is probably unreachable.)QBT_TR[CONTEXT=HttpServer]");
|
||||
window.parent.closeWindows();
|
||||
},
|
||||
onSuccess: function() {
|
||||
// Close window
|
||||
window.parent.location.reload();
|
||||
window.parent.closeWindows();
|
||||
// Close window
|
||||
window.parent.location.reload();
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
}).send();
|
||||
};
|
|
@ -52,7 +52,7 @@
|
|||
<table class="dynamicTable" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30%;">QBT_TR(URL)QBT_TR[CONTEXT=TrackerList] <img src="theme/list-add" id="addTrackersPlus"/></th>
|
||||
<th style="width: 30%;">QBT_TR(URL)QBT_TR[CONTEXT=TrackerList] <img src="theme/list-add" id="addTrackersPlus" alt="Add Trackers" /></th>
|
||||
<th style="width: 10%;">QBT_TR(Status)QBT_TR[CONTEXT=TrackerList]</th>
|
||||
<th style="width: 10%;">QBT_TR(Peers)QBT_TR[CONTEXT=TrackerList]</th>
|
||||
<th style="width: 50%;">QBT_TR(Message)QBT_TR[CONTEXT=TrackerList]</th>
|
||||
|
@ -64,7 +64,7 @@
|
|||
</div>
|
||||
|
||||
<div id="prop_peers" class="invisible">
|
||||
<div id="peers">
|
||||
<div>
|
||||
<div id="torrentPeersTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
|
||||
<table class="dynamicTable" style="position:relative;">
|
||||
<thead>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Rename)QBT_TR[CONTEXT=TransferListWidget]</title>
|
||||
|
@ -36,7 +36,7 @@
|
|||
var hash = new URI().getData('hash');
|
||||
if (hash) {
|
||||
new Request({
|
||||
url: 'command/rename',
|
||||
url: 'api/v2/torrents/rename',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash,
|
|
@ -273,7 +273,7 @@ window.addEvent('load', function () {
|
|||
|
||||
var syncMainDataTimer;
|
||||
var syncMainData = function () {
|
||||
var url = new URI('sync/maindata');
|
||||
var url = new URI('api/v2/sync/maindata');
|
||||
url.setData('rid', syncMainDataLastResponseId);
|
||||
var request = new Request.JSON({
|
||||
url : url,
|
||||
|
@ -407,12 +407,18 @@ window.addEvent('load', function () {
|
|||
torrentsTable.columns['priority'].force_hide = !queueing_enabled;
|
||||
torrentsTable.updateColumn('priority');
|
||||
if (queueing_enabled) {
|
||||
$('queueingLinks').removeClass('invisible');
|
||||
$('topPrioItem').removeClass('invisible');
|
||||
$('increasePrioItem').removeClass('invisible');
|
||||
$('decreasePrioItem').removeClass('invisible');
|
||||
$('bottomPrioItem').removeClass('invisible');
|
||||
$('queueingButtons').removeClass('invisible');
|
||||
$('queueingMenuItems').removeClass('invisible');
|
||||
}
|
||||
else {
|
||||
$('queueingLinks').addClass('invisible');
|
||||
$('topPrioItem').addClass('invisible');
|
||||
$('increasePrioItem').addClass('invisible');
|
||||
$('decreasePrioItem').addClass('invisible');
|
||||
$('bottomPrioItem').addClass('invisible');
|
||||
$('queueingButtons').addClass('invisible');
|
||||
$('queueingMenuItems').addClass('invisible');
|
||||
}
|
||||
|
@ -439,7 +445,7 @@ window.addEvent('load', function () {
|
|||
// Change icon immediately to give some feedback
|
||||
updateAltSpeedIcon(!alternativeSpeedLimits);
|
||||
|
||||
new Request({url: 'command/toggleAlternativeSpeedLimits',
|
||||
new Request({url: 'api/v2/transfer/toggleSpeedLimitsMode',
|
||||
method: 'post',
|
||||
onComplete: function() {
|
||||
alternativeSpeedLimits = !alternativeSpeedLimits;
|
||||
|
@ -659,7 +665,7 @@ var loadTorrentPeersData = function(){
|
|||
loadTorrentPeersTimer = loadTorrentPeersData.delay(syncMainDataTimerPeriod);
|
||||
return;
|
||||
}
|
||||
var url = new URI('sync/torrent_peers');
|
||||
var url = new URI('api/v2/sync/torrentPeers');
|
||||
url.setData('rid', syncTorrentPeersLastResponseId);
|
||||
url.setData('hash', current_hash);
|
||||
var request = new Request.JSON({
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
getSavePath = function() {
|
||||
var req = new Request({
|
||||
url: 'command/getSavePath',
|
||||
url: 'api/v2/app/defaultSavePath',
|
||||
method: 'get',
|
||||
noCache: true,
|
||||
onFailure: function() {
|
|
@ -142,7 +142,7 @@ initializeWindows = function() {
|
|||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'command/toggleSequentialDownload',
|
||||
url: 'api/v2/toggleSequentialDownload',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
|
@ -156,7 +156,7 @@ initializeWindows = function() {
|
|||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'command/toggleFirstLastPiecePrio',
|
||||
url: 'api/v2/toggleFirstLastPiecePrio',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
|
@ -170,7 +170,7 @@ initializeWindows = function() {
|
|||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'command/setSuperSeeding',
|
||||
url: 'api/v2/torrents/setSuperSeeding',
|
||||
method: 'post',
|
||||
data: {
|
||||
value: val,
|
||||
|
@ -185,7 +185,7 @@ initializeWindows = function() {
|
|||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'command/setForceStart',
|
||||
url: 'api/v2/torrents/setForceStart',
|
||||
method: 'post',
|
||||
data: {
|
||||
value: 'true',
|
||||
|
@ -274,15 +274,13 @@ initializeWindows = function() {
|
|||
pauseFN = function() {
|
||||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
hashes.each(function(hash, index) {
|
||||
new Request({
|
||||
url: 'command/pause',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
new Request({
|
||||
url: 'api/v2/torrents/pause',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
}).send();
|
||||
updateMainData();
|
||||
}
|
||||
};
|
||||
|
@ -290,15 +288,13 @@ initializeWindows = function() {
|
|||
startFN = function() {
|
||||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
hashes.each(function(hash, index) {
|
||||
new Request({
|
||||
url: 'command/resume',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
new Request({
|
||||
url: 'api/v2/torrents/resume',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
}).send();
|
||||
updateMainData();
|
||||
}
|
||||
};
|
||||
|
@ -313,7 +309,7 @@ initializeWindows = function() {
|
|||
enable = true;
|
||||
});
|
||||
new Request({
|
||||
url: 'command/setAutoTMM',
|
||||
url: 'api/v2/torrents/setAutoManagement',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
|
@ -329,10 +325,10 @@ initializeWindows = function() {
|
|||
if (hashes.length) {
|
||||
hashes.each(function(hash, index) {
|
||||
new Request({
|
||||
url: 'command/recheck',
|
||||
url: 'api/v2/torrents/recheck',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash
|
||||
hashes: hash
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
@ -409,7 +405,7 @@ initializeWindows = function() {
|
|||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'command/setCategory',
|
||||
url: 'api/v2/torrents/setCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
|
@ -439,7 +435,7 @@ initializeWindows = function() {
|
|||
removeCategoryFN = function (categoryHash) {
|
||||
var categoryName = category_list[categoryHash].name;
|
||||
new Request({
|
||||
url: 'command/removeCategories',
|
||||
url: 'api/v2/torrents/removeCategories',
|
||||
method: 'post',
|
||||
data: {
|
||||
categories: categoryName
|
||||
|
@ -455,7 +451,7 @@ initializeWindows = function() {
|
|||
categories.push(category_list[hash].name);
|
||||
}
|
||||
new Request({
|
||||
url: 'command/removeCategories',
|
||||
url: 'api/v2/torrents/removeCategories',
|
||||
method: 'post',
|
||||
data: {
|
||||
categories: categories.join('\n')
|
||||
|
@ -467,15 +463,13 @@ initializeWindows = function() {
|
|||
startTorrentsByCategoryFN = function (categoryHash) {
|
||||
var hashes = torrentsTable.getFilteredTorrentsHashes('all', categoryHash);
|
||||
if (hashes.length) {
|
||||
hashes.each(function (hash, index) {
|
||||
new Request({
|
||||
url: 'command/resume',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
new Request({
|
||||
url: 'api/v2/torrents/resume',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
}).send();
|
||||
updateMainData();
|
||||
}
|
||||
};
|
||||
|
@ -483,15 +477,13 @@ initializeWindows = function() {
|
|||
pauseTorrentsByCategoryFN = function (categoryHash) {
|
||||
var hashes = torrentsTable.getFilteredTorrentsHashes('all', categoryHash);
|
||||
if (hashes.length) {
|
||||
hashes.each(function (hash, index) {
|
||||
new Request({
|
||||
url: 'command/pause',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
new Request({
|
||||
url: 'api/v2/torrents/pause',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
}).send();
|
||||
updateMainData();
|
||||
}
|
||||
};
|
||||
|
@ -545,11 +537,15 @@ initializeWindows = function() {
|
|||
return torrentsTable.selectedRowsIds().join("\n");
|
||||
};
|
||||
|
||||
['pauseAll', 'resumeAll'].each(function(item) {
|
||||
addClickEvent(item, function(e) {
|
||||
['pause', 'resume'].each(function(item) {
|
||||
addClickEvent(item + 'All', function(e) {
|
||||
new Event(e).stop();
|
||||
new Request({
|
||||
url: 'command/' + item
|
||||
url: 'api/v2/torrents/' + item,
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: "all"
|
||||
}
|
||||
}).send();
|
||||
updateMainData();
|
||||
});
|
||||
|
@ -562,10 +558,10 @@ initializeWindows = function() {
|
|||
if (hashes.length) {
|
||||
hashes.each(function(hash, index) {
|
||||
new Request({
|
||||
url: 'command/' + item,
|
||||
url: 'api/v2/torrents/' + item,
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash
|
||||
hashes: hash
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
@ -574,7 +570,7 @@ initializeWindows = function() {
|
|||
});
|
||||
});
|
||||
|
||||
['decreasePrio', 'increasePrio', 'topPrio', 'bottomPrio'].each(function(item) {
|
||||
['decrease_prio', 'increase_prio', 'top_prio', 'bottom_prio'].each(function(item) {
|
||||
addClickEvent(item, function(e) {
|
||||
new Event(e).stop();
|
||||
setPriorityFN(item);
|
||||
|
@ -585,7 +581,7 @@ initializeWindows = function() {
|
|||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'command/' + cmd,
|
||||
url: 'api/v2/torrents/' + cmd,
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
|
@ -611,7 +607,7 @@ initializeWindows = function() {
|
|||
addClickEvent('logout', function(e) {
|
||||
new Event(e).stop();
|
||||
new Request({
|
||||
url: 'logout',
|
||||
url: 'api/v2/auth/logout',
|
||||
method: 'post',
|
||||
onSuccess: function() {
|
||||
window.location.reload();
|
||||
|
@ -623,7 +619,7 @@ initializeWindows = function() {
|
|||
new Event(e).stop();
|
||||
if (confirm('QBT_TR(Are you sure you want to quit qBittorrent?)QBT_TR[CONTEXT=MainWindow]')) {
|
||||
new Request({
|
||||
url: 'command/shutdown',
|
||||
url: 'api/v2/app/shutdown',
|
||||
onSuccess: function() {
|
||||
document.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>QBT_TR(qBittorrent has been shutdown.)QBT_TR[CONTEXT=HttpServer]</title><style type=\"text/css\">body { text-align: center; }</style></head><body><h1>QBT_TR(qBittorrent has been shutdown.)QBT_TR[CONTEXT=HttpServer]</h1></body></html>");
|
||||
stop();
|
527
src/webui/www/private/scripts/mootools-1.2-core-yc.js
Normal file
527
src/webui/www/private/scripts/mootools-1.2-core-yc.js
Normal file
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
---
|
||||
MooTools: the javascript framework
|
||||
|
||||
web build:
|
||||
- http://mootools.net/core/76bf47062d6c1983d66ce47ad66aa0e0
|
||||
|
||||
packager build:
|
||||
- packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff
|
||||
|
||||
copyrights:
|
||||
- [MooTools](http://mootools.net)
|
||||
|
||||
licenses:
|
||||
- [MIT License](http://mootools.net/license.txt)
|
||||
...
|
||||
*/
|
||||
|
||||
(function(){this.MooTools={version:"1.4.5",build:"ab8ea8824dc3b24b6666867a2c4ed58ebb762cf0"};var e=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){return i.$family();
|
||||
}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if(i.callee){return"arguments";
|
||||
}if("item" in i){return"collection";}}}return typeof i;};var u=this.instanceOf=function(w,i){if(w==null){return false;}var v=w.$constructor||w.constructor;
|
||||
while(v){if(v===i){return true;}v=v.parent;}if(!w.hasOwnProperty){return false;}return w instanceof i;};var f=this.Function;var r=true;for(var q in {toString:1}){r=null;
|
||||
}if(r){r=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];}f.prototype.overloadSetter=function(v){var i=this;
|
||||
return function(x,w){if(x==null){return this;}if(v||typeof x!="string"){for(var y in x){i.call(this,y,x[y]);}if(r){for(var z=r.length;z--;){y=r[z];if(x.hasOwnProperty(y)){i.call(this,y,x[y]);
|
||||
}}}}else{i.call(this,x,w);}return this;};};f.prototype.overloadGetter=function(v){var i=this;return function(x){var y,w;if(typeof x!="string"){y=x;}else{if(arguments.length>1){y=arguments;
|
||||
}else{if(v){y=[x];}}}if(y){w={};for(var z=0;z<y.length;z++){w[y[z]]=i.call(this,y[z]);}}else{w=i.call(this,x);}return w;};};f.prototype.extend=function(i,v){this[i]=v;
|
||||
}.overloadSetter();f.prototype.implement=function(i,v){this.prototype[i]=v;}.overloadSetter();var o=Array.prototype.slice;f.from=function(i){return(e(i)=="function")?i:function(){return i;
|
||||
};};Array.from=function(i){if(i==null){return[];}return(k.isEnumerable(i)&&typeof i!="string")?(e(i)=="array")?i:o.call(i):[i];};Number.from=function(v){var i=parseFloat(v);
|
||||
return isFinite(i)?i:null;};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;
|
||||
return this;}});var k=this.Type=function(x,w){if(x){var v=x.toLowerCase();var i=function(y){return(e(y)==v);};k["is"+x]=i;if(w!=null){w.prototype.$family=(function(){return v;
|
||||
}).hide();w.type=i;}}if(w==null){return null;}w.extend(this);w.$constructor=k;w.prototype.$constructor=w;return w;};var p=Object.prototype.toString;k.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&p.call(i)!="[object Function]");
|
||||
};var b={};var d=function(i){var v=e(i.prototype);return b[v]||(b[v]=[]);};var h=function(w,A){if(A&&A.$hidden){return;}var v=d(this);for(var x=0;x<v.length;
|
||||
x++){var z=v[x];if(e(z)=="type"){h.call(z,w,A);}else{z.call(this,w,A);}}var y=this.prototype[w];if(y==null||!y.$protected){this.prototype[w]=A;}if(this[w]==null&&e(A)=="function"){t.call(this,w,function(i){return A.apply(i,o.call(arguments,1));
|
||||
});}};var t=function(i,w){if(w&&w.$hidden){return;}var v=this[i];if(v==null||!v.$protected){this[i]=w;}};k.implement({implement:h.overloadSetter(),extend:t.overloadSetter(),alias:function(i,v){h.call(this,i,this.prototype[v]);
|
||||
}.overloadSetter(),mirror:function(i){d(this).push(i);return this;}});new k("Type",k);var c=function(v,A,y){var x=(A!=Object),E=A.prototype;if(x){A=new k(v,A);
|
||||
}for(var B=0,z=y.length;B<z;B++){var F=y[B],D=A[F],C=E[F];if(D){D.protect();}if(x&&C){A.implement(F,C.protect());}}if(x){var w=E.propertyIsEnumerable(y[0]);
|
||||
A.forEachMethod=function(J){if(!w){for(var I=0,G=y.length;I<G;I++){J.call(E,E[y[I]],y[I]);}}for(var H in E){J.call(E,E[H],H);}};}return c;};c("String",String,["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","trim","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
|
||||
Object.extend=t.overloadSetter();Date.extend("now",function(){return +(new Date);});new k("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
|
||||
}.hide();Number.extend("random",function(v,i){return Math.floor(Math.random()*(i-v+1)+v);});var l=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,w,x){for(var v in i){if(l.call(i,v)){w.call(x,i[v],v,i);
|
||||
}}});Object.each=Object.forEach;Array.implement({forEach:function(x,y){for(var w=0,v=this.length;w<v;w++){if(w in this){x.call(y,this[w],w,this);}}},each:function(i,v){Array.forEach(this,i,v);
|
||||
return this;}});var s=function(i){switch(e(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var v=this.length,w=new Array(v);
|
||||
while(v--){w[v]=s(this[v]);}return w;});var a=function(v,i,w){switch(e(w)){case"object":if(e(v[i])=="object"){Object.merge(v[i],w);}else{v[i]=Object.clone(w);
|
||||
}break;case"array":v[i]=w.clone();break;default:v[i]=w;}return v;};Object.extend({merge:function(C,y,x){if(e(y)=="string"){return a(C,y,x);}for(var B=1,w=arguments.length;
|
||||
B<w;B++){var z=arguments[B];for(var A in z){a(C,A,z[A]);}}return C;},clone:function(i){var w={};for(var v in i){w[v]=s(i[v]);}return w;},append:function(z){for(var y=1,w=arguments.length;
|
||||
y<w;y++){var v=arguments[y]||{};for(var x in v){z[x]=v[x];}}return z;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new k(i);
|
||||
});var j=Date.now();String.extend("uniqueID",function(){return(j++).toString(36);});var g=this.Hash=new k("Hash",function(i){if(e(i)=="hash"){i=Object.clone(i.getClean());
|
||||
}for(var v in i){this[v]=i[v];}return this;});g.implement({forEach:function(i,v){Object.forEach(this,i,v);},getClean:function(){var v={};for(var i in this){if(this.hasOwnProperty(i)){v[i]=this[i];
|
||||
}}return v;},getLength:function(){var v=0;for(var i in this){if(this.hasOwnProperty(i)){v++;}}return v;}});g.alias("each","forEach");Object.type=k.isObject;
|
||||
var n=this.Native=function(i){return new k(i.name,i.initialize);};n.type=k.type;n.implement=function(x,v){for(var w=0;w<x.length;w++){x[w].implement(v);
|
||||
}return n;};var m=Array.type;Array.type=function(i){return u(i,Array)||m(i);};this.$A=function(i){return Array.from(i).slice();};this.$arguments=function(v){return function(){return arguments[v];
|
||||
};};this.$chk=function(i){return !!(i||i===0);};this.$clear=function(i){clearTimeout(i);clearInterval(i);return null;};this.$defined=function(i){return(i!=null);
|
||||
};this.$each=function(w,v,x){var i=e(w);((i=="arguments"||i=="collection"||i=="array"||i=="elements")?Array:Object).each(w,v,x);};this.$empty=function(){};
|
||||
this.$extend=function(v,i){return Object.append(v,i);};this.$H=function(i){return new g(i);};this.$merge=function(){var i=Array.slice(arguments);i.unshift({});
|
||||
return Object.merge.apply(null,i);};this.$lambda=f.from;this.$mixin=Object.merge;this.$random=Number.random;this.$splat=Array.from;this.$time=Date.now;
|
||||
this.$type=function(i){var v=e(i);if(v=="elements"){return"array";}return(v=="null")?false:v;};this.$unlink=function(i){switch(e(i)){case"object":return Object.clone(i);
|
||||
case"array":return Array.clone(i);case"hash":return new g(i);default:return i;}};})();Array.implement({every:function(c,d){for(var b=0,a=this.length>>>0;
|
||||
b<a;b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
|
||||
if(d.call(f,e,b,this)){c.push(e);}}}return c;},indexOf:function(c,d){var b=this.length>>>0;for(var a=(d<0)?Math.max(0,b+d):d||0;a<b;a++){if(this[a]===c){return a;
|
||||
}}return -1;},map:function(c,e){var d=this.length>>>0,b=Array(d);for(var a=0;a<d;a++){if(a in this){b[a]=c.call(e,this[a],a,this);}}return b;},some:function(c,d){for(var b=0,a=this.length>>>0;
|
||||
b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);
|
||||
return this.map(function(c){return c[a].apply(c,b);});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];
|
||||
}return d;},link:function(c){var a={};for(var e=0,b=this.length;e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;
|
||||
},append:function(a){this.push.apply(this,a);return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
|
||||
},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
|
||||
},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
|
||||
for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
|
||||
}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
|
||||
}var a=this.map(function(c){if(c.length==1){c+=c;}return c.toInt(16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;}if(this.length==4&&this[3]==0&&!d){return"transparent";
|
||||
}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");}});Array.alias("extend","append");
|
||||
var $pick=function(){return Array.from(arguments).pick();};String.implement({test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
|
||||
},contains:function(a,b){return(b)?(b+this+b).indexOf(b+a+b)>-1:String(this).indexOf(a)>-1;},trim:function(){return String(this).replace(/^\s+|\s+$/g,"");
|
||||
},clean:function(){return String(this).replace(/\s+/g," ").trim();},camelCase:function(){return String(this).replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();
|
||||
});},hyphenate:function(){return String(this).replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return String(this).replace(/\b[a-z]/g,function(a){return a.toUpperCase();
|
||||
});},escapeRegExp:function(){return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);
|
||||
},hexToRgb:function(b){var a=String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=String(this).match(/\d{1,3}/g);
|
||||
return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return String(this).replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
|
||||
}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
|
||||
return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
|
||||
}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
|
||||
};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
|
||||
b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
|
||||
},bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
|
||||
g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;},pass:function(b,c){var a=this;
|
||||
if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
|
||||
},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});delete Function.prototype.bind;Function.implement({create:function(b){var a=this;
|
||||
b=b||{};return function(d){var c=b.arguments;c=(c!=null)?Array.from(c):Array.slice(arguments,(b.event)?1:0);if(b.event){c=[d||window.event].extend(c);}var e=function(){return a.apply(b.bind||null,c);
|
||||
};if(b.delay){return setTimeout(e,b.delay);}if(b.periodical){return setInterval(e,b.periodical);}if(b.attempt){return Function.attempt(e);}return e();};
|
||||
},bind:function(c,b){var a=this;if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},bindWithEvent:function(c,b){var a=this;
|
||||
if(b!=null){b=Array.from(b);}return function(d){return a.apply(c,(b==null)?arguments:[d].concat(b));};},run:function(a,b){return this.apply(b,Array.from(a));
|
||||
}});if(Object.create==Function.prototype.create){Object.create=null;}var $try=Function.attempt;(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};
|
||||
for(var e=0,b=g.length;e<b;e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);
|
||||
}}return d;},filter:function(b,e,g){var d={};for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
|
||||
}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
|
||||
}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
|
||||
}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
|
||||
}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
|
||||
break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(a){return Object.keyOf(this,a);
|
||||
},hasValue:function(a){return Object.contains(this,a);},extend:function(a){Hash.each(a||{},function(c,b){Hash.set(this,b,c);},this);return this;},combine:function(a){Hash.each(a||{},function(c,b){Hash.include(this,b,c);
|
||||
},this);return this;},erase:function(a){if(this.hasOwnProperty(a)){delete this[a];}return this;},get:function(a){return(this.hasOwnProperty(a))?this[a]:null;
|
||||
},set:function(a,b){if(!this[a]||this.hasOwnProperty(a)){this[a]=b;}return this;},empty:function(){Hash.each(this,function(b,a){delete this[a];},this);
|
||||
return this;},include:function(a,b){if(this[a]==null){this[a]=b;}return this;},map:function(a,b){return new Hash(Object.map(this,a,b));},filter:function(a,b){return new Hash(Object.filter(this,a,b));
|
||||
},every:function(a,b){return Object.every(this,a,b);},some:function(a,b){return Object.some(this,a,b);},getKeys:function(){return Object.keys(this);},getValues:function(){return Object.values(this);
|
||||
},toQueryString:function(a){return Object.toQueryString(this,a);}});Hash.extend=Object.append;Hash.alias({indexOf:"keyOf",contains:"hasValue"});(function(){var k=this.document;
|
||||
var h=k.window=this;var a=navigator.userAgent.toLowerCase(),b=navigator.platform.toLowerCase(),i=a.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0],f=i[1]=="ie"&&k.documentMode;
|
||||
var o=this.Browser={extend:Function.prototype.extend,name:(i[1]=="version")?i[3]:i[1],version:f||parseFloat((i[1]=="opera"&&i[4])?i[4]:i[2]),Platform:{name:a.match(/ip(?:ad|od|hone)/)?"ios":(a.match(/(?:webos|android)/)||b.match(/mac|win|linux/)||["other"])[0]},Features:{xpath:!!(k.evaluate),air:!!(h.runtime),query:!!(k.querySelector),json:!!(h.JSON)},Plugins:{}};
|
||||
o[o.name]=true;o[o.name+parseInt(o.version,10)]=true;o.Platform[o.Platform.name]=true;o.Request=(function(){var q=function(){return new XMLHttpRequest();
|
||||
};var p=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");};return Function.attempt(function(){q();
|
||||
return q;},function(){p();return p;},function(){e();return e;});})();o.Features.xhr=!!(o.Request);var j=(Function.attempt(function(){return navigator.plugins["Shockwave Flash"].description;
|
||||
},function(){return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");})||"0 r0").match(/\d+/g);o.Plugins.Flash={version:Number(j[0]||"0."+j[1])||0,build:Number(j[2])||0};
|
||||
o.exec=function(p){if(!p){return p;}if(h.execScript){h.execScript(p);}else{var e=k.createElement("script");e.setAttribute("type","text/javascript");e.text=p;
|
||||
k.head.appendChild(e);k.head.removeChild(e);}return p;};String.implement("stripScripts",function(p){var e="";var q=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(r,s){e+=s+"\n";
|
||||
return"";});if(p===true){o.exec(e);}else{if(typeOf(p)=="function"){p(e,q);}}return q;});o.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
|
||||
this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,p){h[e]=p;});this.Document=k.$constructor=new Type("Document",function(){});
|
||||
k.$family=Function.from("document").hide();Document.mirror(function(e,p){k[e]=p;});k.html=k.documentElement;if(!k.head){k.head=k.getElementsByTagName("head")[0];
|
||||
}if(k.execCommand){try{k.execCommand("BackgroundImageCache",false,true);}catch(g){}}if(this.attachEvent&&!this.addEventListener){var c=function(){this.detachEvent("onunload",c);
|
||||
k.head=k.html=k.window=null;};this.attachEvent("onunload",c);}var m=Array.from;try{m(k.html.childNodes);}catch(g){Array.from=function(p){if(typeof p!="string"&&Type.isEnumerable(p)&&typeOf(p)!="array"){var e=p.length,q=new Array(e);
|
||||
while(e--){q[e]=p[e];}return q;}return m(p);};var l=Array.prototype,n=l.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var p=l[e];
|
||||
Array[e]=function(q){return p.apply(Array.from(q),n.call(arguments,1));};});}if(o.Platform.ios){o.Platform.ipod=true;}o.Engine={};var d=function(p,e){o.Engine.name=p;
|
||||
o.Engine[p+e]=true;o.Engine.version=e;};if(o.ie){o.Engine.trident=true;switch(o.version){case 6:d("trident",4);break;case 7:d("trident",5);break;case 8:d("trident",6);
|
||||
}}if(o.firefox){o.Engine.gecko=true;if(o.version>=3){d("gecko",19);}else{d("gecko",18);}}if(o.safari||o.chrome){o.Engine.webkit=true;switch(o.version){case 2:d("webkit",419);
|
||||
break;case 3:d("webkit",420);break;case 4:d("webkit",525);}}if(o.opera){o.Engine.presto=true;if(o.version>=9.6){d("presto",960);}else{if(o.version>=9.5){d("presto",950);
|
||||
}else{d("presto",925);}}}if(o.name=="unknown"){switch((a.match(/(?:webkit|khtml|gecko)/)||[])[0]){case"webkit":case"khtml":o.Engine.webkit=true;break;case"gecko":o.Engine.gecko=true;
|
||||
}}this.$exec=o.exec;})();(function(){var b={};var a=this.DOMEvent=new Type("DOMEvent",function(c,g){if(!g){g=window;}c=c||g.event;if(c.$extended){return c;
|
||||
}this.event=c;this.$extended=true;this.shift=c.shiftKey;this.control=c.ctrlKey;this.alt=c.altKey;this.meta=c.metaKey;var i=this.type=c.type;var h=c.target||c.srcElement;
|
||||
while(h&&h.nodeType==3){h=h.parentNode;}this.target=document.id(h);if(i.indexOf("key")==0){var d=this.code=(c.which||c.keyCode);this.key=b[d]||Object.keyOf(Event.Keys,d);
|
||||
if(i=="keydown"){if(d>111&&d<124){this.key="f"+(d-111);}else{if(d>95&&d<106){this.key=d-96;}}}if(this.key==null){this.key=String.fromCharCode(d).toLowerCase();
|
||||
}}else{if(i=="click"||i=="dblclick"||i=="contextmenu"||i=="DOMMouseScroll"||i.indexOf("mouse")==0){var j=g.document;j=(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;
|
||||
this.page={x:(c.pageX!=null)?c.pageX:c.clientX+j.scrollLeft,y:(c.pageY!=null)?c.pageY:c.clientY+j.scrollTop};this.client={x:(c.pageX!=null)?c.pageX-g.pageXOffset:c.clientX,y:(c.pageY!=null)?c.pageY-g.pageYOffset:c.clientY};
|
||||
if(i=="DOMMouseScroll"||i=="mousewheel"){this.wheel=(c.wheelDelta)?c.wheelDelta/120:-(c.detail||0)/3;}this.rightClick=(c.which==3||c.button==2);if(i=="mouseover"||i=="mouseout"){var k=c.relatedTarget||c[(i=="mouseover"?"from":"to")+"Element"];
|
||||
while(k&&k.nodeType==3){k=k.parentNode;}this.relatedTarget=document.id(k);}}else{if(i.indexOf("touch")==0||i.indexOf("gesture")==0){this.rotation=c.rotation;
|
||||
this.scale=c.scale;this.targetTouches=c.targetTouches;this.changedTouches=c.changedTouches;var f=this.touches=c.touches;if(f&&f[0]){var e=f[0];this.page={x:e.pageX,y:e.pageY};
|
||||
this.client={x:e.clientX,y:e.clientY};}}}}if(!this.client){this.client={};}if(!this.page){this.page={};}});a.implement({stop:function(){return this.preventDefault().stopPropagation();
|
||||
},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
|
||||
}else{this.event.returnValue=false;}return this;}});a.defineKey=function(d,c){b[d]=c;return this;};a.defineKeys=a.defineKey.overloadSetter(true);a.defineKeys({"38":"up","40":"down","37":"left","39":"right","27":"esc","32":"space","8":"backspace","9":"tab","46":"delete","13":"enter"});
|
||||
})();var Event=DOMEvent;Event.Keys={};Event.Keys=new Hash(Event.Keys);(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};
|
||||
}var g=function(){e(this);if(g.$prototyping){return this;}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;
|
||||
return i;}.extend(this).implement(h);g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
|
||||
}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
|
||||
};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
|
||||
break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
|
||||
}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
|
||||
return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
|
||||
}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
|
||||
return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
|
||||
for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
|
||||
return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
|
||||
return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
|
||||
if(c==$empty){return this;}this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);
|
||||
}return this;},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
|
||||
}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
|
||||
},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
|
||||
}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
|
||||
if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
|
||||
(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
|
||||
var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
|
||||
return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
|
||||
}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
|
||||
o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
|
||||
};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
|
||||
function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
|
||||
if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
|
||||
}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
|
||||
}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
|
||||
}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
|
||||
break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
|
||||
case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
|
||||
};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
|
||||
};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var k={},m={},d=Object.prototype.toString;
|
||||
k.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};k.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(d.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
|
||||
};k.setDocument=function(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
|
||||
}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
|
||||
s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
|
||||
var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
|
||||
s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
|
||||
try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
|
||||
s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
|
||||
c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
|
||||
}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
|
||||
}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
|
||||
s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
|
||||
}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matchesSelector||A.mozMatchesSelector||A.webkitMatchesSelector;
|
||||
if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
|
||||
s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
|
||||
if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
|
||||
};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
|
||||
};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
|
||||
}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
|
||||
}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
|
||||
}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
|
||||
}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
|
||||
}:null;A=null;for(r in s){this[r]=s[r];}};var f=/^([#.]?)((?:[\w-]+|\*))$/,h=/\[.+[*$^]=(?:""|'')?\]/,g={};k.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);
|
||||
if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);
|
||||
}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(f);simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;
|
||||
}E=U.getElementsByTagName(v);if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;
|
||||
}A=U.getElementById(v);if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
|
||||
}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
|
||||
if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+e.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
|
||||
for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
|
||||
}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||g[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&h.test(z))||(!y&&z.indexOf(",")>-1)||e.disableQSA){break querySelector;
|
||||
}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
|
||||
}else{E=U.querySelectorAll(S);}}catch(Q){g[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
|
||||
A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
|
||||
}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
|
||||
return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
|
||||
if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
|
||||
if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
|
||||
this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
|
||||
}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
|
||||
}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};k.uidx=1;k.uidk="slick-uniqueid";k.getUIDXML=function(n){var c=n.getAttribute(this.uidk);
|
||||
if(!c){c=this.uidx++;n.setAttribute(this.uidk,c);}return c;};k.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};k.sort=function(c){if(!this.documentSorter){return c;
|
||||
}c.sort(this.documentSorter);return c;};k.cacheNTH={};k.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;k.parseNTHArgument=function(q){var o=q.match(this.matchNTH);
|
||||
if(!o){return false;}var p=o[2]||false;var n=o[1]||1;if(n=="-"){n=-1;}var c=+o[3]||0;o=(p=="n")?{a:n,b:c}:(p=="odd")?{a:2,b:1}:(p=="even")?{a:2,b:0}:{a:0,b:n};
|
||||
return(this.cacheNTH[q]=o);};k.createNTHPseudo=function(p,n,c,o){return function(s,q){var u=this.getUID(s);if(!this[c][u]){var A=s.parentNode;if(!A){return false;
|
||||
}var r=A[p],t=1;if(o){var z=s.nodeName;do{if(r.nodeName!=z){continue;}this[c][this.getUID(r)]=t++;}while((r=r[n]));}else{do{if(r.nodeType!=1){continue;
|
||||
}this[c][this.getUID(r)]=t++;}while((r=r[n]));}}q=q||"n";var v=this.cacheNTH[q]||this.parseNTHArgument(q);if(!v){return false;}var y=v.a,x=v.b,w=this[c][u];
|
||||
if(y==0){return x==w;}if(y>0){if(w<x){return false;}}else{if(x<w){return false;}}return((w-x)%y)==0;};};k.pushArray=function(p,c,r,o,n,q){if(this.matchSelector(p,c,r,o,n,q)){this.found.push(p);
|
||||
}};k.pushUID=function(q,c,s,p,n,r){var o=this.getUID(q);if(!this.uniques[o]&&this.matchSelector(q,c,s,p,n,r)){this.uniques[o]=true;this.found.push(q);}};
|
||||
k.matchNode=function(n,o){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(n,o.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
|
||||
}catch(u){}}var t=this.Slick.parse(o);if(!t){return true;}var r=t.expressions,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var p=currentExpression[0];
|
||||
if(this.matchSelector(n,(this.isXMLDocument)?p.tag:p.tag.toUpperCase(),p.id,p.classes,p.attributes,p.pseudos)){return true;}s++;}}if(s==t.length){return false;
|
||||
}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===n){return true;}}return false;};k.matchPseudo=function(q,c,p){var n="pseudo:"+c;if(this[n]){return this[n](q,p);
|
||||
}var o=this.getAttribute(q,c);return(p)?p==o:!!o;};k.matchSelector=function(o,v,c,p,q,s){if(v){var t=(this.isXMLDocument)?o.nodeName:o.nodeName.toUpperCase();
|
||||
if(v=="*"){if(t<"@"){return false;}}else{if(t!=v){return false;}}}if(c&&o.getAttribute("id")!=c){return false;}var r,n,u;if(p){for(r=p.length;r--;){u=this.getAttribute(o,"class");
|
||||
if(!(u&&p[r].regexp.test(u))){return false;}}}if(q){for(r=q.length;r--;){n=q[r];if(n.operator?!n.test(this.getAttribute(o,n.key)):!this.hasAttribute(o,n.key)){return false;
|
||||
}}}if(s){for(r=s.length;r--;){n=s[r];if(!this.matchPseudo(o,n.key,n.value)){return false;}}}return true;};var j={" ":function(q,w,n,r,s,u,p){var t,v,o;
|
||||
if(this.isHTMLDocument){getById:if(n){v=this.document.getElementById(n);if((!v&&q.all)||(this.idGetsName&&v&&v.getAttributeNode("id").nodeValue!=n)){o=q.all[n];
|
||||
if(!o){return;}if(!o[0]){o=[o];}for(t=0;v=o[t++];){var c=v.getAttributeNode("id");if(c&&c.nodeValue==n){this.push(v,w,null,r,s,u);break;}}return;}if(!v){if(this.contains(this.root,q)){return;
|
||||
}else{break getById;}}else{if(this.document!==q&&!this.contains(q,v)){return;}}this.push(v,w,null,r,s,u);return;}getByClass:if(r&&q.getElementsByClassName&&!this.brokenGEBCN){o=q.getElementsByClassName(p.join(" "));
|
||||
if(!(o&&o.length)){break getByClass;}for(t=0;v=o[t++];){this.push(v,w,n,null,s,u);}return;}}getByTag:{o=q.getElementsByTagName(w);if(!(o&&o.length)){break getByTag;
|
||||
}if(!this.brokenStarGEBTN){w=null;}for(t=0;v=o[t++];){this.push(v,w,n,r,s,u);}}},">":function(p,c,r,o,n,q){if((p=p.firstChild)){do{if(p.nodeType==1){this.push(p,c,r,o,n,q);
|
||||
}}while((p=p.nextSibling));}},"+":function(p,c,r,o,n,q){while((p=p.nextSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);break;}}},"^":function(p,c,r,o,n,q){p=p.firstChild;
|
||||
if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:+"](p,c,r,o,n,q);}}},"~":function(q,c,s,p,n,r){while((q=q.nextSibling)){if(q.nodeType!=1){continue;
|
||||
}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}},"++":function(p,c,r,o,n,q){this["combinator:+"](p,c,r,o,n,q);
|
||||
this["combinator:!+"](p,c,r,o,n,q);},"~~":function(p,c,r,o,n,q){this["combinator:~"](p,c,r,o,n,q);this["combinator:!~"](p,c,r,o,n,q);},"!":function(p,c,r,o,n,q){while((p=p.parentNode)){if(p!==this.document){this.push(p,c,r,o,n,q);
|
||||
}}},"!>":function(p,c,r,o,n,q){p=p.parentNode;if(p!==this.document){this.push(p,c,r,o,n,q);}},"!+":function(p,c,r,o,n,q){while((p=p.previousSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);
|
||||
break;}}},"!^":function(p,c,r,o,n,q){p=p.lastChild;if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:!+"](p,c,r,o,n,q);}}},"!~":function(q,c,s,p,n,r){while((q=q.previousSibling)){if(q.nodeType!=1){continue;
|
||||
}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}}};for(var i in j){k["combinator:"+i]=j[i];}var l={empty:function(c){var n=c.firstChild;
|
||||
return !(n&&n.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,n){return !this.matchNode(c,n);},contains:function(c,n){return(c.innerText||c.textContent||"").indexOf(n)>-1;
|
||||
},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
|
||||
}}return true;},"only-child":function(o){var n=o;while((n=n.previousSibling)){if(n.nodeType==1){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeType==1){return false;
|
||||
}}return true;},"nth-child":k.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":k.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":k.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":k.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(n,c){return this["pseudo:nth-child"](n,""+(c+1));
|
||||
},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var n=c.nodeName;
|
||||
while((c=c.previousSibling)){if(c.nodeName==n){return false;}}return true;},"last-of-type":function(c){var n=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==n){return false;
|
||||
}}return true;},"only-of-type":function(o){var n=o,p=o.nodeName;while((n=n.previousSibling)){if(n.nodeName==p){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeName==p){return false;
|
||||
}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
|
||||
},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var b in l){k["pseudo:"+b]=l[b];}var a=k.attributeGetters={"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");
|
||||
},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");
|
||||
},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;},type:function(){return this.getAttribute("type");
|
||||
},maxlength:function(){var c=this.getAttributeNode("maxLength");return(c&&c.specified)?c.nodeValue:null;}};a.MAXLENGTH=a.maxLength=a.maxlength;var e=k.Slick=(this.Slick||{});
|
||||
e.version="1.1.7";e.search=function(n,o,c){return k.search(n,o,c);};e.find=function(c,n){return k.search(c,n,null,true);};e.contains=function(c,n){k.setDocument(c);
|
||||
return k.contains(c,n);};e.getAttribute=function(n,c){k.setDocument(n);return k.getAttribute(n,c);};e.hasAttribute=function(n,c){k.setDocument(n);return k.hasAttribute(n,c);
|
||||
};e.match=function(n,c){if(!(n&&c)){return false;}if(!c||c===n){return true;}k.setDocument(n);return k.matchNode(n,c);};e.defineAttributeGetter=function(c,n){k.attributeGetters[c]=n;
|
||||
return this;};e.lookupAttributeGetter=function(c){return k.attributeGetters[c];};e.definePseudo=function(c,n){k["pseudo:"+c]=function(p,o){return n.call(p,o);
|
||||
};return this;};e.lookupPseudo=function(c){var n=k["pseudo:"+c];if(n){return function(o){return n.call(this,o);};}return null;};e.override=function(n,c){k.override(n,c);
|
||||
return this;};e.isXML=k.isXML;e.uidOf=function(c){return k.getUIDHTML(c);};if(!this.Slick){this.Slick=e;}}).apply((typeof exports!="undefined")?exports:this);
|
||||
var Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];
|
||||
b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var a,f=0,c=d.length;f<c;f++){a=d[f];if(g[a.key]!=null){continue;
|
||||
}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
|
||||
}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
|
||||
};})(Element.prototype.fireEvent);}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;}var b={};b[a]=function(){var h=[],e=arguments,j=true;
|
||||
for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");}return(j)?new Elements(h):h;};Elements.implement(b);
|
||||
});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$constructor":Element,"$family":Function.from("element").hide()};Element.mirror(function(a,b){Element.Prototype[a]=b;
|
||||
});}Element.Constructors={};Element.Constructors=new Hash;var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
|
||||
}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
|
||||
b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
|
||||
for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
|
||||
}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
|
||||
b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
|
||||
c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
|
||||
for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
|
||||
b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});Elements.alias("extend","append");
|
||||
(function(){var f=Array.prototype.splice,a={"0":0,"1":1,length:2};f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);
|
||||
while(g>=this.length){delete this[g--];}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;
|
||||
try{d=(document.createElement("<input name=x>").name=="x");}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&").replace(/"/g,""");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
|
||||
}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
|
||||
}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
|
||||
},getWindow:function(){return this.window;},id:(function(){var e={string:function(E,D,l){E=Slick.find(l,"#"+E.replace(/(\W)/g,"\\$1"));return(E)?e.element(E,D):null;
|
||||
},element:function(D,E){Slick.uidOf(D);if(!E&&!D.$family&&!(/^(?:object|embed)$/i).test(D.tagName)){var l=D.fireEvent;D._fireEvent=function(F,G){return l(F,G);
|
||||
};Object.append(D,Element.Prototype);}return D;},object:function(D,E,l){if(D.toElement){return e.element(D.toElement(l),E);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
|
||||
};return function(D,F,E){if(D&&D.$family&&D.uniqueNumber){return D;}var l=typeOf(D);return(e[l])?e[l](D,F,E||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
|
||||
});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
|
||||
},getElement:function(e){return document.id(Slick.find(this,e));}});var m={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(m);
|
||||
}if(!document.createElement("div").contains){Element.implement(m);}Element.implement("hasChild",function(e){return this!==e&&this.contains(e);});(function(l,E,e){this.Selectors={};
|
||||
var F=this.Selectors.Pseudo=new Hash();var D=function(){for(var G in F){if(F.hasOwnProperty(G)){Slick.definePseudo(G,F[G]);delete F[G];}}};Slick.search=function(H,I,G){D();
|
||||
return l.call(this,H,I,G);};Slick.find=function(G,H){D();return E.call(this,G,H);};Slick.match=function(H,G){D();return e.call(this,H,G);};})(Slick.search,Slick.find,Slick.match);
|
||||
var r=function(E,D){if(!E){return D;}E=Object.clone(Slick.parse(E));var l=E.expressions;for(var e=l.length;e--;){l[e][0].combinator=D;}return E;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(D){return this.getElement(r(D,e));
|
||||
});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(D){return this.getElements(r(D,e));
|
||||
});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,r(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,r(e,">")).getLast());
|
||||
},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
|
||||
},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){var H=new Elements;if(arguments.length==1&&typeof e=="string"){return Slick.search(this.document,e,H);
|
||||
}var E=Array.flatten(arguments);for(var F=0,D=E.length;F<D;F++){var G=E[F];switch(typeOf(G)){case"element":H.push(G);break;case"string":Slick.search(this.document,G,H);
|
||||
}}return H;});}if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
|
||||
}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var w={before:function(l,e){var D=e.parentNode;if(D){D.insertBefore(l,e);
|
||||
}},after:function(l,e){var D=e.parentNode;if(D){D.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
|
||||
}};w.inside=w.bottom;Object.each(w,function(l,D){D=D.capitalize();var e={};e["inject"+D]=function(E){l(this,document.id(E,true));return this;};e["grab"+D]=function(E){l(document.id(E,true),this);
|
||||
return this;};Element.implement(e);});var j={},d={};var k={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){k[e.toLowerCase()]=e;
|
||||
});k.html="innerHTML";k.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(k,function(l,e){d[e]=function(D,E){D[l]=E;
|
||||
};j[e]=function(D){return D[l];};});var x=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
|
||||
var h={};Array.forEach(x,function(e){var l=e.toLowerCase();h[l]=e;d[l]=function(D,E){D[e]=!!E;};j[l]=function(D){return !!D[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
|
||||
},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
|
||||
},value:function(e,l){e.value=(l!=null)?l:"";}});j["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
|
||||
try{f.type="button";}catch(z){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var p=document.createElement("input");p.value="t";
|
||||
p.type="submit";if(p.value!="t"){d.type=function(l,e){var D=l.value;l.type=e;l.value=D;};}p=null;var q=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
|
||||
})(document.createElement("div"));Element.implement({setProperty:function(l,D){var E=d[l.toLowerCase()];if(E){E(this,D);}else{if(q){var e=this.retrieve("$attributeWhiteList",{});
|
||||
}if(D==null){this.removeAttribute(l);if(q){delete e[l];}}else{this.setAttribute(l,""+D);if(q){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);
|
||||
}return this;},getProperty:function(F){var D=j[F.toLowerCase()];if(D){return D(this);}if(q){var l=this.getAttributeNode(F),E=this.retrieve("$attributeWhiteList",{});
|
||||
if(!l){return null;}if(l.expando&&!E[F]){var G=this.outerHTML;if(G.substr(0,G.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(F)<0){return null;}E[F]=true;}}var e=Slick.getAttribute(this,F);
|
||||
return(!e&&!Slick.hasAttribute(this,F))?null:e;},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);
|
||||
},removeProperty:function(e){return this.setProperty(e,null);},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(D,l){var e=Element.Properties[D];
|
||||
(e&&e.set)?e.set.call(this,l):this.setProperty(D,l);}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);
|
||||
}.overloadGetter(),erase:function(l){var e=Element.Properties[l];(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:function(e){return this.className.clean().contains(e," ");
|
||||
},addClass:function(e){if(!this.hasClass(e)){this.className=(this.className+" "+e).clean();}return this;},removeClass:function(e){this.className=this.className.replace(new RegExp("(^|\\s)"+e+"(?:\\s|$)"),"$1");
|
||||
return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var E=this,e,G=Array.flatten(arguments),F=G.length;
|
||||
if(F>1){E=e=document.createDocumentFragment();}for(var D=0;D<F;D++){var l=document.id(G[D],true);if(l){E.appendChild(l);}}if(e){this.appendChild(e);}return this;
|
||||
},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);},grab:function(l,e){w[e||"bottom"](document.id(l,true),this);return this;
|
||||
},inject:function(l,e){w[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);e.parentNode.replaceChild(this,e);
|
||||
return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;return new Elements(Array.from(this.options).filter(function(e){return e.selected;
|
||||
}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(D){var l=D.type;if(!D.name||D.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;
|
||||
}var E=(D.get("tag")=="select")?D.getSelected().map(function(F){return document.id(F).get("value");}):((l=="radio"||l=="checkbox")&&!D.checked)?null:D.get("value");
|
||||
Array.from(E).each(function(F){if(typeof F!="undefined"){e.push(encodeURIComponent(D.name)+"="+encodeURIComponent(F));}});});return e.join("&");}});var i={},A={};
|
||||
var B=function(e){return(A[e]||(A[e]={}));};var v=function(l){var e=l.uniqueNumber;if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();
|
||||
}if(e!=null){delete i[e];delete A[e];}return l;};var C={input:"checked",option:"selected",textarea:"value"};Element.implement({destroy:function(){var e=v(this).getElementsByTagName("*");
|
||||
Array.each(e,v);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;
|
||||
},clone:function(G,E){G=G!==false;var L=this.cloneNode(G),D=[L],F=[this],J;if(G){D.append(Array.from(L.getElementsByTagName("*")));F.append(Array.from(this.getElementsByTagName("*")));
|
||||
}for(J=D.length;J--;){var H=D[J],K=F[J];if(!E){H.removeAttribute("id");}if(H.clearAttributes){H.clearAttributes();H.mergeAttributes(K);H.removeAttribute("uniqueNumber");
|
||||
if(H.options){var O=H.options,e=K.options;for(var I=O.length;I--;){O[I].selected=e[I].selected;}}}var l=C[K.tagName.toLowerCase()];if(l&&K[l]){H[l]=K[l];
|
||||
}}if(Browser.ie){var M=L.getElementsByTagName("object"),N=this.getElementsByTagName("object");for(J=M.length;J--;){M[J].outerHTML=N[J].outerHTML;}}return document.id(L);
|
||||
}});[Element,Window,Document].invoke("implement",{addListener:function(E,D){if(E=="unload"){var e=D,l=this;D=function(){l.removeListener("unload",D);e();
|
||||
};}else{i[Slick.uidOf(this)]=this;}if(this.addEventListener){this.addEventListener(E,D,!!arguments[2]);}else{this.attachEvent("on"+E,D);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
|
||||
}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var E=B(Slick.uidOf(this)),D=E[l];if(e!=null&&D==null){D=E[l]=e;}return D!=null?D:null;
|
||||
},store:function(l,e){var D=B(Slick.uidOf(this));D[l]=e;return this;},eliminate:function(e){var l=B(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){window.addListener("unload",function(){Object.each(i,v);
|
||||
if(window.CollectGarbage){CollectGarbage();}});}Element.Properties={};Element.Properties=new Hash;Element.Properties.style={set:function(e){this.style.cssText=e;
|
||||
},get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();
|
||||
}};Element.Properties.html={set:function(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
|
||||
}};var t=document.createElement("div");t.innerHTML="<nav></nav>";var a=(t.childNodes.length==1);if(!a){var s="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),u=s.length;
|
||||
while(u--){b.createElement(s[u]);}}t=null;var g=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
|
||||
});var c=document.createElement("tr"),o="<td></td>";c.innerHTML=o;var y=(c.innerHTML==o);c=null;if(!g||!y||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
|
||||
e.thead=e.tfoot=e.tbody;return function(D){var E=e[this.get("tag")];if(!E&&!a){E=[0,"",""];}if(!E){return l.call(this,D);}var H=E[0],G=document.createElement("div"),F=G;
|
||||
if(!a){b.appendChild(G);}G.innerHTML=[E[1],D,E[2]].flatten().join("");while(H--){F=F.firstChild;}this.empty().adopt(F.childNodes);if(!a){b.removeChild(G);
|
||||
}G=null;};})(Element.Properties.html.set);}var n=document.createElement("form");n.innerHTML="<select><option>s</option></select>";if(n.firstChild.value!="s"){Element.Properties.value={set:function(G){var l=this.get("tag");
|
||||
if(l!="select"){return this.setProperty("value",G);}var D=this.getElements("option");for(var E=0;E<D.length;E++){var F=D[E],e=F.getAttributeNode("value"),H=(e&&e.specified)?F.value:F.get("text");
|
||||
if(H==G){return F.selected=true;}}},get:function(){var D=this,l=D.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(D=D.getSelected()[0])){return"";
|
||||
}var e=D.getAttributeNode("value");return(e&&e.specified)?D.value:D.get("text");}};}n=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
|
||||
},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var i=document.html;var d=document.createElement("div");
|
||||
d.style.color="red";d.style.color=null;var c=d.style.color=="red";d=null;Element.Properties.styles={set:function(k){this.setStyles(k);}};var h=(i.style.opacity!=null),e=(i.style.filter!=null),j=/alpha\(opacity=([\d.]+)\)/i;
|
||||
var a=function(l,k){l.store("$opacity",k);l.style.visibility=k>0||k==null?"visible":"hidden";};var f=(h?function(l,k){l.style.opacity=k;}:(e?function(l,k){var n=l.style;
|
||||
if(!l.currentStyle||!l.currentStyle.hasLayout){n.zoom=1;}if(k==null||k==1){k="";}else{k="alpha(opacity="+(k*100).limit(0,100).round()+")";}var m=n.filter||l.getComputedStyle("filter")||"";
|
||||
n.filter=j.test(m)?m.replace(j,k):m+k;if(!n.filter){n.removeAttribute("filter");}}:a));var g=(h?function(l){var k=l.style.opacity||l.getComputedStyle("opacity");
|
||||
return(k=="")?1:k.toFloat();}:(e?function(l){var m=(l.style.filter||l.getComputedStyle("filter")),k;if(m){k=m.match(j);}return(k==null||m==null)?1:(k[1]/100);
|
||||
}:function(l){var k=l.retrieve("$opacity");if(k==null){k=(l.style.visibility=="hidden"?0:1);}return k;}));var b=(i.style.cssFloat==null)?"styleFloat":"cssFloat";
|
||||
Element.implement({getComputedStyle:function(m){if(this.currentStyle){return this.currentStyle[m.camelCase()];}var l=Element.getDocument(this).defaultView,k=l?l.getComputedStyle(this,null):null;
|
||||
return(k)?k.getPropertyValue((m==b)?"float":m.hyphenate()):null;},setStyle:function(l,k){if(l=="opacity"){if(k!=null){k=parseFloat(k);}f(this,k);return this;
|
||||
}l=(l=="float"?b:l).camelCase();if(typeOf(k)!="string"){var m=(Element.Styles[l]||"@").split(" ");k=Array.from(k).map(function(o,n){if(!m[n]){return"";
|
||||
}return(typeOf(o)=="number")?m[n].replace("@",Math.round(o)):o;}).join(" ");}else{if(k==String(Number(k))){k=Math.round(k);}}this.style[l]=k;if((k==""||k==null)&&c&&this.style.removeAttribute){this.style.removeAttribute(l);
|
||||
}return this;},getStyle:function(q){if(q=="opacity"){return g(this);}q=(q=="float"?b:q).camelCase();var k=this.style[q];if(!k||q=="zIndex"){k=[];for(var p in Element.ShortStyles){if(q!=p){continue;
|
||||
}for(var o in Element.ShortStyles[p]){k.push(this.getStyle(o));}return k.join(" ");}k=this.getComputedStyle(q);}if(k){k=String(k);var m=k.match(/rgba?\([\d\s,]+\)/);
|
||||
if(m){k=k.replace(m[0],m[0].rgbToHex());}}if(Browser.opera||Browser.ie){if((/^(height|width)$/).test(q)&&!(/px$/.test(k))){var l=(q=="width")?["left","right"]:["top","bottom"],n=0;
|
||||
l.each(function(r){n+=this.getStyle("border-"+r+"-width").toInt()+this.getStyle("padding-"+r).toInt();},this);return this["offset"+q.capitalize()]-n+"px";
|
||||
}if(Browser.ie&&(/^border(.+)Width|margin|padding/).test(q)&&isNaN(parseFloat(k))){return"0px";}}return k;},setStyles:function(l){for(var k in l){this.setStyle(k,l[k]);
|
||||
}return this;},getStyles:function(){var k={};Array.flatten(arguments).each(function(l){k[l]=this.getStyle(l);},this);return k;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
|
||||
Element.implement({setOpacity:function(k){f(this,k);return this;},getOpacity:function(){return g(this);}});Element.Properties.opacity={set:function(k){f(this,k);
|
||||
a(this,k);},get:function(){return g(this);}};Element.Styles=new Hash(Element.Styles);Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};
|
||||
["Top","Right","Bottom","Left"].each(function(q){var p=Element.ShortStyles;var l=Element.Styles;["margin","padding"].each(function(r){var s=r+q;p[r][s]=l[s]="@px";
|
||||
});var o="border"+q;p.border[o]=l[o]="@px @ rgb(@, @, @)";var n=o+"Width",k=o+"Style",m=o+"Color";p[o]={};p.borderWidth[n]=p[o][n]=l[n]="@px";p.borderStyle[k]=p[o][k]=l[k]="@";
|
||||
p.borderColor[m]=p[o][m]=l[m]="rgb(@, @, @)";});})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});
|
||||
if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h,f);
|
||||
}if(b.condition){d=function(k){if(b.condition.call(this,k,f)){return h.call(this,k);}return true;};}if(b.base){g=Function.from(b.base).call(this,f);}}var e=function(){return h.call(j);
|
||||
};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new DOMEvent(k,j.getWindow());if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);
|
||||
}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);
|
||||
if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];if(f){if(f.onRemove){f.onRemove.call(this,d,e);}if(f.base){e=Function.from(f.base).call(this,e);
|
||||
}}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
|
||||
},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);}return this;}var c=this.retrieve("events");if(!c){return this;
|
||||
}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);},this);delete c[b];
|
||||
}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
|
||||
}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
|
||||
}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,paste:2,input:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};
|
||||
Element.Events={mousewheel:{base:(Browser.firefox)?"DOMMouseScroll":"mousewheel"}};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
|
||||
}else{var a=function(b){var c=b.relatedTarget;if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));
|
||||
};Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}if(!window.addEventListener){Element.NativeEvents.propertychange=2;
|
||||
Element.Events.change={base:function(){var b=this.type;return(this.get("tag")=="input"&&(b=="radio"||b=="checkbox"))?"propertychange":"change";},condition:function(b){return this.type!="radio"||(b.event.propertyName=="checked"&&this.checked);
|
||||
}};}Element.Events=new Hash(Element.Events);})();(function(){var c=!!window.addEventListener;Element.NativeEvents.focusin=Element.NativeEvents.focusout=2;
|
||||
var k=function(l,m,n,o,p){while(p&&p!=l){if(m(p,o)){return n.call(p,o,p);}p=document.id(p.parentNode);}};var a={mouseenter:{base:"mouseover"},mouseleave:{base:"mouseout"},focus:{base:"focus"+(c?"":"in"),capture:true},blur:{base:c?"blur":"focusout",capture:true}};
|
||||
var b="$delegation:";var i=function(l){return{base:"focusin",remove:function(m,o){var p=m.retrieve(b+l+"listeners",{})[o];if(p&&p.forms){for(var n=p.forms.length;
|
||||
n--;){p.forms[n].removeEvent(l,p.fns[n]);}}},listen:function(x,r,v,n,t,s){var o=(t.get("tag")=="form")?t:n.target.getParent("form");if(!o){return;}var u=x.retrieve(b+l+"listeners",{}),p=u[s]||{forms:[],fns:[]},m=p.forms,w=p.fns;
|
||||
if(m.indexOf(o)!=-1){return;}m.push(o);var q=function(y){k(x,r,v,y,t);};o.addEvent(l,q);w.push(q);u[s]=p;x.store(b+l+"listeners",u);}};};var d=function(l){return{base:"focusin",listen:function(m,n,p,q,r){var o={blur:function(){this.removeEvents(o);
|
||||
}};o[l]=function(s){k(m,n,p,s,r);};q.target.addEvents(o);}};};if(!c){Object.append(a,{submit:i("submit"),reset:i("reset"),change:d("change"),select:d("select")});
|
||||
}var h=Element.prototype,f=h.addEvent,j=h.removeEvent;var e=function(l,m){return function(r,q,n){if(r.indexOf(":relay")==-1){return l.call(this,r,q,n);
|
||||
}var o=Slick.parse(r).expressions[0][0];if(o.pseudos[0].key!="relay"){return l.call(this,r,q,n);}var p=o.tag;o.pseudos.slice(1).each(function(s){p+=":"+s.key+(s.value?"("+s.value+")":"");
|
||||
});l.call(this,r,q);return m.call(this,p,o.pseudos[0].value,q);};};var g={addEvent:function(v,q,x){var t=this.retrieve("$delegates",{}),r=t[v];if(r){for(var y in r){if(r[y].fn==x&&r[y].match==q){return this;
|
||||
}}}var p=v,u=q,o=x,n=a[v]||{};v=n.base||p;q=function(B){return Slick.match(B,u);};var w=Element.Events[p];if(w&&w.condition){var l=q,m=w.condition;q=function(C,B){return l(C,B)&&m.call(C,B,v);
|
||||
};}var z=this,s=String.uniqueID();var A=n.listen?function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){n.listen(z,q,x,B,C,s);}}:function(B,C){if(!C&&B&&B.target){C=B.target;
|
||||
}if(C){k(z,q,x,B,C);}};if(!r){r={};}r[s]={match:u,fn:o,delegator:A};t[p]=r;return f.call(this,v,A,n.capture);},removeEvent:function(r,n,t,u){var q=this.retrieve("$delegates",{}),p=q[r];
|
||||
if(!p){return this;}if(u){var m=r,w=p[u].delegator,l=a[r]||{};r=l.base||m;if(l.remove){l.remove(this,u);}delete p[u];q[m]=p;return j.call(this,r,w);}var o,v;
|
||||
if(t){for(o in p){v=p[o];if(v.match==n&&v.fn==t){return g.removeEvent.call(this,r,n,t,o);}}}else{for(o in p){v=p[o];if(v.match==n){g.removeEvent.call(this,r,n,v.fn,o);
|
||||
}}}return this;}};[Element,Window,Document].invoke("implement",{addEvent:e(f,g.addEvent),removeEvent:e(j,g.removeEvent)});})();(function(){var h=document.createElement("div"),e=document.createElement("div");
|
||||
h.style.height="0";h.appendChild(e);var d=(e.offsetParent===h);h=e=null;var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);
|
||||
};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();
|
||||
}return{x:this.offsetWidth,y:this.offsetHeight};},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};
|
||||
},getScroll:function(){if(a(this)){return this.getWindow().getScroll();}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};
|
||||
while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
|
||||
}var n=(k(m,"position")=="static")?i:l;while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
|
||||
}try{return m.offsetParent;}catch(n){}return null;},getOffsets:function(){if(this.getBoundingClientRect&&!Browser.Platform.ios){var r=this.getBoundingClientRect(),o=document.id(this.getDocument().documentElement),q=o.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
|
||||
return{x:r.left.toInt()+t.x+((s)?0:q.x)-o.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-o.clientTop};}var n=this,m={x:0,y:0};if(a(this)){return m;}while(n&&!a(n)){m.x+=n.offsetLeft;
|
||||
m.y+=n.offsetTop;if(Browser.firefox){if(!c(n)){m.x+=b(n);m.y+=g(n);}var p=n.parentNode;if(p&&k(p,"overflow")!="visible"){m.x+=b(p);m.y+=g(p);}}else{if(n!=this&&Browser.safari){m.x+=b(n);
|
||||
m.y+=g(n);}}n=n.offsetParent;}if(Browser.firefox&&!c(this)){m.x-=b(this);m.y-=g(this);}return m;},getPosition:function(p){var q=this.getOffsets(),n=this.getScrolls();
|
||||
var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();
|
||||
}var m=this.getPosition(o),n=this.getSize();var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
|
||||
},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
|
||||
},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
|
||||
return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
|
||||
return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
|
||||
}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
|
||||
}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
|
||||
},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
|
||||
},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
|
||||
},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
|
||||
this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
|
||||
this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
|
||||
}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
|
||||
},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
|
||||
return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
|
||||
this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
|
||||
this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
|
||||
d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
|
||||
}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
|
||||
this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
|
||||
},resume:function(){if((this.frame<this.frames)&&!this.isRunning()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];
|
||||
return g&&g.contains(this);}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};var e={},c={};var a=function(){var h=Date.now();
|
||||
for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);
|
||||
}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(b,e,a){a=Array.from(a);
|
||||
var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;if(c&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);
|
||||
if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);
|
||||
b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);
|
||||
return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};
|
||||
}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
|
||||
});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
|
||||
});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var c={},b=new RegExp("^"+a.escapeRegExp()+"$");
|
||||
Array.each(document.styleSheets,function(f,e){var d=f.href;if(d&&d.contains("://")&&!d.contains(document.domain)){return;}var g=f.rules||f.cssRules;Array.each(g,function(k,h){if(!k.style){return;
|
||||
}var j=(k.selectorText)?k.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();}):null;if(!j||!b.test(j)){return;}Object.each(Element.Styles,function(l,i){if(!k.style[i]||Element.ShortStyles[i]){return;
|
||||
}l=String(k.style[i]);c[i]=((/^rgb/).test(l))?l.rgbToHex():l;});});});return Fx.CSS.Cache[a]=c;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
|
||||
}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
|
||||
});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
|
||||
},serve:function(a){return a;}}};Fx.CSS.Parsers=new Hash(Fx.CSS.Parsers);Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);
|
||||
this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);
|
||||
return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);
|
||||
return this.parent(a.from,a.to);}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");
|
||||
if(!a){a=new Fx.Tween(this,{link:"cancel"});this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(a,c,b);
|
||||
return this;},fade:function(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";
|
||||
c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);
|
||||
g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];
|
||||
if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");this.callChain();
|
||||
});}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;
|
||||
}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
|
||||
b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
|
||||
},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
|
||||
for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
|
||||
for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
|
||||
return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
|
||||
return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
|
||||
a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
|
||||
var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
|
||||
}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions=new Hash(Fx.Transitions);Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);
|
||||
}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));
|
||||
},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;
|
||||
1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);
|
||||
}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);
|
||||
var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
|
||||
this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
|
||||
this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
|
||||
}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
|
||||
}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
|
||||
}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
|
||||
},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
|
||||
},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
|
||||
return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
|
||||
}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
|
||||
}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
|
||||
o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
|
||||
break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
|
||||
j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
|
||||
}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.contains("?")?"&":"?")+String.uniqueID();
|
||||
}if(j&&e=="get"){f+=(f.contains("?")?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
|
||||
}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
|
||||
}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
|
||||
}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}else{if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
|
||||
}}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
|
||||
if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
|
||||
if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
|
||||
return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
|
||||
this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
|
||||
Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(f){var e=this.options,c=this.response;
|
||||
c.html=f.stripScripts(function(h){c.javascript=h;});var d=c.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(d){c.html=d[1];}var b=new Element("div").set("html",c.html);
|
||||
c.tree=b.childNodes;c.elements=b.getElements(e.filter||"*");if(e.filter){c.tree=c.elements;}if(e.update){var g=document.id(e.update).empty();if(e.filter){g.adopt(c.elements);
|
||||
}else{g.set("html",c.html);}}else{if(e.append){var a=document.id(e.append);if(e.filter){c.elements.reverse().inject(a);}else{a.adopt(b.getChildren());}}}if(e.evalScripts){Browser.exec(c.javascript);
|
||||
}this.onSuccess(c.tree,c.elements,c.html,c.javascript);}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;
|
||||
},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});this.store("load",a);}return a;
|
||||
}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));return this;}});if(typeof JSON=="undefined"){this.JSON={};
|
||||
}JSON=new Hash({stringify:JSON.stringify,parse:JSON.parse});(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};
|
||||
var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
|
||||
return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
|
||||
}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
|
||||
Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
|
||||
case"null":return"null";}return null;};JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure||JSON.secure){if(JSON.parse){return JSON.parse(string);
|
||||
}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");}}return eval("("+string+")");
|
||||
};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
|
||||
},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
|
||||
}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
|
||||
this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
|
||||
}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
|
||||
}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
|
||||
return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
|
||||
Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
|
||||
};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
|
||||
k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
|
||||
if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
|
||||
c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
|
||||
}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
|
||||
}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document);(function(){var Swiff=this.Swiff=new Class({Implements:Options,options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"window",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object;
|
||||
},initialize:function(path,options){this.instance="Swiff_"+String.uniqueID();this.setOptions(options);options=this.options;var id=this.id=options.id||this.instance;
|
||||
var container=document.id(options.container);Swiff.CallBacks[this.instance]={};var params=options.params,vars=options.vars,callBacks=options.callBacks;
|
||||
var properties=Object.append({height:options.height,width:options.width},options.properties);var self=this;for(var callBack in callBacks){Swiff.CallBacks[this.instance][callBack]=(function(option){return function(){return option.apply(self.object,arguments);
|
||||
};})(callBacks[callBack]);vars[callBack]="Swiff.CallBacks."+this.instance+"."+callBack;}params.flashVars=Object.toQueryString(vars);if(Browser.ie){properties.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
|
||||
params.movie=path;}else{properties.type="application/x-shockwave-flash";}properties.data=path;var build='<object id="'+id+'"';for(var property in properties){build+=" "+property+'="'+properties[property]+'"';
|
||||
}build+=">";for(var param in params){if(params[param]){build+='<param name="'+param+'" value="'+params[param]+'" />';}}build+="</object>";this.object=((container)?container.empty():new Element("div")).set("html",build).firstChild;
|
||||
},replaces:function(element){element=document.id(element,true);element.parentNode.replaceChild(this.toElement(),element);return this;},inject:function(element){document.id(element,true).appendChild(this.toElement());
|
||||
return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].append(arguments));}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction('<invoke name="'+fn+'" returntype="javascript">'+__flash__argumentsToXML(arguments,2)+"</invoke>");
|
||||
return eval(rs);};})();
|
|
@ -21,7 +21,7 @@ MochaUI.extend({
|
|||
// Get global upload limit
|
||||
var maximum = 500;
|
||||
var req = new Request({
|
||||
url: 'command/getGlobalUpLimit',
|
||||
url: 'api/v2/transfer/uploadLimit',
|
||||
method: 'post',
|
||||
data: {},
|
||||
onSuccess: function(data) {
|
||||
|
@ -70,7 +70,7 @@ MochaUI.extend({
|
|||
}
|
||||
else {
|
||||
var req = new Request.JSON({
|
||||
url: 'command/getTorrentsUpLimit',
|
||||
url: 'api/v2/torrents/uploadLimit',
|
||||
noCache : true,
|
||||
method: 'post',
|
||||
data: {
|
||||
|
@ -125,7 +125,7 @@ MochaUI.extend({
|
|||
// Get global upload limit
|
||||
var maximum = 500;
|
||||
var req = new Request({
|
||||
url: 'command/getGlobalDlLimit',
|
||||
url: 'api/v2/transfer/downloadLimit',
|
||||
method: 'post',
|
||||
data: {},
|
||||
onSuccess: function(data) {
|
||||
|
@ -174,7 +174,7 @@ MochaUI.extend({
|
|||
}
|
||||
else {
|
||||
var req = new Request.JSON({
|
||||
url: 'command/getTorrentsDlLimit',
|
||||
url: 'api/v2/torrents/downloadLimit',
|
||||
noCache : true,
|
||||
method: 'post',
|
||||
data: {
|
|
@ -94,7 +94,7 @@ var allCBUnchecked = function() {
|
|||
var setFilePriority = function(id, priority) {
|
||||
if (current_hash === "") return;
|
||||
new Request({
|
||||
url: 'command/setFilePrio',
|
||||
url: 'api/v2/torrents/filePrio',
|
||||
method: 'post',
|
||||
data: {
|
||||
'hash': current_hash,
|
||||
|
@ -289,7 +289,7 @@ var loadTorrentFilesData = function() {
|
|||
fTable.removeAllRows();
|
||||
current_hash = new_hash;
|
||||
}
|
||||
var url = 'query/propertiesFiles/' + current_hash;
|
||||
var url = new URI('api/v2/torrents/files?hash=' + current_hash);
|
||||
var request = new Request.JSON({
|
||||
url: url,
|
||||
noCache: true,
|
|
@ -41,7 +41,7 @@ var loadTorrentData = function() {
|
|||
}
|
||||
// Display hash
|
||||
$('torrent_hash').set('html', current_hash);
|
||||
var url = 'query/propertiesGeneral/' + current_hash;
|
||||
var url = new URI('api/v2/torrents/properties?hash=' + current_hash);
|
||||
var request = new Request.JSON({
|
||||
url: url,
|
||||
noCache: true,
|
|
@ -70,7 +70,7 @@ var loadTrackersData = function() {
|
|||
tTable.removeAllRows();
|
||||
current_hash = new_hash;
|
||||
}
|
||||
var url = 'query/propertiesTrackers/' + current_hash;
|
||||
var url = new URI('api/v2/torrents/trackers?hash=' + current_hash);
|
||||
var request = new Request.JSON({
|
||||
url: url,
|
||||
noCache: true,
|
|
@ -70,7 +70,7 @@ var loadWebSeedsData = function() {
|
|||
wsTable.removeAllRows();
|
||||
current_hash = new_hash;
|
||||
}
|
||||
var url = 'query/propertiesWebSeeds/' + current_hash;
|
||||
var url = new URI('api/v2/torrents/webseeds?hash=' + current_hash);
|
||||
var request = new Request.JSON({
|
||||
url: url,
|
||||
noCache: true,
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Set location)QBT_TR[CONTEXT=HttpServer]</title>
|
||||
|
@ -29,7 +29,7 @@
|
|||
|
||||
var hashesList = new URI().getData('hashes');
|
||||
new Request({
|
||||
url: 'command/setLocation',
|
||||
url: 'api/v2/torrents/setLocation',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashesList,
|
|
@ -44,16 +44,16 @@
|
|||
renameFN();
|
||||
},
|
||||
prioTop : function (element, ref) {
|
||||
setPriorityFN('topPrio');
|
||||
setPriorityFN('top_prio');
|
||||
},
|
||||
prioUp : function (element, ref) {
|
||||
setPriorityFN('increasePrio');
|
||||
setPriorityFN('increase_prio');
|
||||
},
|
||||
prioDown : function (element, ref) {
|
||||
setPriorityFN('decreasePrio');
|
||||
setPriorityFN('decrease_prio');
|
||||
},
|
||||
prioBottom : function (element, ref) {
|
||||
setPriorityFN('bottomPrio');
|
||||
setPriorityFN('bottom_prio');
|
||||
},
|
||||
|
||||
DownloadLimit : function (element, ref) {
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]</title>
|
||||
|
@ -10,12 +10,10 @@
|
|||
</head>
|
||||
<body>
|
||||
<iframe id="upload_frame" name="upload_frame" class="invisible" src="javascript:false;"></iframe>
|
||||
<form action="command/upload" enctype="multipart/form-data" method="post" id="uploadForm" style="text-align: center;" target="upload_frame">
|
||||
<p>
|
||||
<div style="margin-top: 25px; display: inline-block; border: 1px solid lightgrey; border-radius: 4px;">
|
||||
<input type="file" id="fileselect" name="fileselect[]" multiple="multiple"/>
|
||||
</div>
|
||||
</p>
|
||||
<form action="api/v2/torrents/add" enctype="multipart/form-data" method="post" id="uploadForm" style="text-align: center;" target="upload_frame">
|
||||
<div style="margin-top: 25px; display: inline-block; border: 1px solid lightgrey; border-radius: 4px;">
|
||||
<input type="file" id="fileselect" name="fileselect[]" multiple="multiple"/>
|
||||
</div>
|
||||
<fieldset class="settings" style="border: 0; text-align: left;">
|
||||
<div class="formRow" style="margin-top: 12px;">
|
||||
<label for="savepath" class="leftLabelLarge">QBT_TR(Save files to location:)QBT_TR[CONTEXT=HttpServer]</label>
|
||||
|
@ -23,7 +21,7 @@
|
|||
</div>
|
||||
<div class="formRow">
|
||||
<label for="rename" class="leftLabelLarge">QBT_TR(Rename torrent)QBT_TR[CONTEXT=HttpServer]</label>
|
||||
<input type="text" name="rename" style="width: 16em;"/>
|
||||
<input type="text" id="rename" name="rename" style="width: 16em;"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="category" class="leftLabelLarge">QBT_TR(Category:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
|
@ -36,27 +34,27 @@
|
|||
</div>
|
||||
<div class="formRow">
|
||||
<label for="skip_checking" class="leftLabelLarge">QBT_TR(Skip hash check)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
<input type="checkbox" name="skip_checking" value="true"/>
|
||||
<input type="checkbox" id="skip_checking" name="skip_checking" value="true"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="root_folder" class="leftLabelLarge">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
<input type="checkbox" name="root_folder" value="true" checked="checked"/>
|
||||
<input type="checkbox" id="root_folder" name="root_folder" value="true" checked="checked"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="sequentialDownload" class="leftLabelLarge">QBT_TR(Download in sequential order)QBT_TR[CONTEXT=TransferListWidget]</label>
|
||||
<input type="checkbox" name="sequentialDownload" value="true"/>
|
||||
<input type="checkbox" id="sequentialDownload" name="sequentialDownload" value="true"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="firstLastPiecePrio" class="leftLabelLarge">QBT_TR(Download first and last pieces first)QBT_TR[CONTEXT=TransferListWidget]</label>
|
||||
<input type="checkbox" name="firstLastPiecePrio" value="true"/>
|
||||
<input type="checkbox" id="firstLastPiecePrio" name="firstLastPiecePrio" value="true"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="dlLimit" class="leftLabelLarge">QBT_TR(Limit download rate)QBT_TR[CONTEXT=HttpServer]</label>
|
||||
<input type="text" name="dlLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
<input type="text" id="dlLimit" name="dlLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
</div>
|
||||
<div class="formRow">
|
||||
<label for="upLimit" class="leftLabelLarge">QBT_TR(Limit upload rate)QBT_TR[CONTEXT=HttpServer]</label>
|
||||
<input type="text" name="upLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
<input type="text" id="upLimit" name="upLimit" style="width: 16em;" placeholder="Bytes/s"/>
|
||||
</div>
|
||||
<div id="submitbutton" style="margin-top: 30px; text-align: center;">
|
||||
<button type="submit" style="font-size: 1em;">QBT_TR(Upload Torrents)QBT_TR[CONTEXT=HttpServer]</button>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>QBT_TR(Torrent Upload Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]</title>
|
||||
|
@ -25,7 +25,7 @@
|
|||
var limit = $("uplimitUpdatevalue").value.toInt() * 1024;
|
||||
if (hashes[0] == "global") {
|
||||
new Request({
|
||||
url: 'command/setGlobalUpLimit',
|
||||
url: 'api/v2/transfer/setUploadLimit',
|
||||
method: 'post',
|
||||
data: {
|
||||
'limit': limit
|
||||
|
@ -38,7 +38,7 @@
|
|||
}
|
||||
else {
|
||||
new Request({
|
||||
url: 'command/setTorrentsUpLimit',
|
||||
url: 'api/v2/torrents/set_upload_limit',
|
||||
method: 'post',
|
||||
data: {
|
||||
'hashes': hashes.join('|'),
|
|
@ -1,8 +1,9 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" dir="ltr">
|
||||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>qBittorrent QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]</title>
|
||||
<link rel="icon" type="image/png" href="images/skin/qbittorrent16.png" />
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||
<script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"></script>
|
||||
<script type="text/javascript">
|
||||
|
@ -20,7 +21,7 @@
|
|||
|
||||
function submitLoginForm() {
|
||||
new Request({
|
||||
url: 'login',
|
||||
url: 'api/v2/auth/login',
|
||||
method: 'post',
|
||||
data: $('loginform').toQueryString(),
|
||||
onComplete: function() {
|
||||
|
@ -74,7 +75,7 @@
|
|||
<img src="images/skin/qbittorrent-tray.svg" alt="qBittorrent logo" style="height: 11em;"/>
|
||||
</div>
|
||||
<div id="formplace" class="col">
|
||||
<form id="loginform" action="">
|
||||
<form id="loginform">
|
||||
<div class="row"><label for="username">QBT_TR(Username)QBT_TR[CONTEXT=HttpServer]</label><br /><input type="text" id="username" name="username" /></div>
|
||||
<div class="row"><label for="password">QBT_TR(Password)QBT_TR[CONTEXT=HttpServer]</label><br /><input type="password" id="password" name="password" /></div>
|
||||
<div class="row"><input type="submit" id="login" value="QBT_TR(Login)QBT_TR[CONTEXT=HttpServer]" /></div>
|
Loading…
Reference in a new issue