mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-26 03:06:37 +03:00
Redesign Web API
Normalize Web API method names. Allow to use alternative Web UI. Switch Web API version to standard form (i.e. "2.0"). Improve Web UI translation code. Retranslate changed files. Add Web API for RSS subsystem.
This commit is contained in:
parent
0fc1ad664f
commit
27d8dbf13b
90 changed files with 4817 additions and 3038 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
|
||||
|
@ -85,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
|
||||
|
|
|
@ -21,6 +21,7 @@ HEADERS += \
|
|||
$$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 \
|
||||
|
@ -86,6 +87,7 @@ SOURCES += \
|
|||
$$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 \
|
||||
|
|
|
@ -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)
|
||||
|
|
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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
data.replace(QLatin1String("${LANG}"), locale.left(2));
|
||||
data.replace(QLatin1String("${VERSION}"), QBT_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
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 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,21 @@
|
|||
* 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 <QString>
|
||||
#include "apicontroller.h"
|
||||
|
||||
class prefjson
|
||||
class LogController : public APIController
|
||||
{
|
||||
private:
|
||||
prefjson();
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(LogController)
|
||||
|
||||
public:
|
||||
static QByteArray getPreferences();
|
||||
static void setPreferences(const QString& json);
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void mainAction();
|
||||
void peersAction();
|
||||
};
|
||||
|
||||
#endif // PREFJSON_H
|
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);
|
||||
}
|
|
@ -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 SyncController : public APIController
|
||||
{
|
||||
QVariantMap syncMainDataLastResponse;
|
||||
QVariantMap syncMainDataLastAcceptedResponse;
|
||||
QVariantMap syncTorrentPeersLastResponse;
|
||||
QVariantMap syncTorrentPeersLastAcceptedResponse;
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(SyncController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void maindataAction();
|
||||
void torrentPeersAction();
|
||||
};
|
||||
|
||||
#endif // WEBSESSIONDATA
|
||||
|
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>
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
</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">
|
||||
<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>
|
|
@ -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('|'),
|
|
@ -4,6 +4,7 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-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" />-->
|
||||
|
|
|
@ -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,
|
|
@ -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();
|
||||
};
|
|
@ -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,
|
||||
|
@ -445,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;
|
||||
|
@ -665,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,
|
|
@ -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) {
|
|
@ -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>
|
|
@ -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('|'),
|
|
@ -3,6 +3,7 @@
|
|||
<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() {
|
Loading…
Reference in a new issue