fileUpload #30
@ -93,6 +93,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
|||||||
QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived);
|
QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived);
|
||||||
QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed);
|
QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed);
|
||||||
|
|
||||||
|
QObject::connect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
|
||||||
|
QObject::connect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
|
||||||
|
|
||||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||||
path += "/" + name;
|
path += "/" + name;
|
||||||
QDir dir(path);
|
QDir dir(path);
|
||||||
@ -141,6 +144,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
|||||||
|
|
||||||
Account::~Account()
|
Account::~Account()
|
||||||
{
|
{
|
||||||
|
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
|
||||||
|
QObject::disconnect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
|
||||||
|
|
||||||
for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
|
for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
|
||||||
delete itr->second;
|
delete itr->second;
|
||||||
}
|
}
|
||||||
@ -1640,3 +1646,11 @@ void Core::Account::onFileUploaded(const QString& messageId, const QString& url)
|
|||||||
pendingMessages.erase(itr);
|
pendingMessages.erase(itr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Account::onFileUploadError(const QString& messageId, const QString& errMsg)
|
||||||
|
{
|
||||||
|
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
|
||||||
|
if (itr != pendingMessages.end()) {
|
||||||
|
pendingMessages.erase(itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -196,7 +196,7 @@ private slots:
|
|||||||
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
||||||
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
||||||
void onFileUploaded(const QString& messageId, const QString& url);
|
void onFileUploaded(const QString& messageId, const QString& url);
|
||||||
void onFileUploadError(const QString& messageId, const QString& path);
|
void onFileUploadError(const QString& messageId, const QString& errMsg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addedAccount(const QString &bareJid);
|
void addedAccount(const QString &bareJid);
|
||||||
|
@ -437,16 +437,22 @@ void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QStr
|
|||||||
try {
|
try {
|
||||||
QString ePath = files.getRecord(url);
|
QString ePath = files.getRecord(url);
|
||||||
if (ePath == path) {
|
if (ePath == path) {
|
||||||
emit fileLocalPathResponse(messageId, path);
|
|
||||||
} else {
|
|
||||||
files.changeRecord(url, path);
|
|
||||||
}
|
|
||||||
QFileInfo info(path);
|
QFileInfo info(path);
|
||||||
if (info.exists() && info.isFile()) {
|
if (info.exists() && info.isFile()) {
|
||||||
emit fileLocalPathResponse(messageId, path);
|
emit fileLocalPathResponse(messageId, path);
|
||||||
} else {
|
} else {
|
||||||
files.removeRecord(url);
|
files.removeRecord(url);
|
||||||
startDownload(messageId, url);
|
startUpload(messageId, url, path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QFileInfo info(path);
|
||||||
|
if (info.exists() && info.isFile()) {
|
||||||
|
files.changeRecord(url, path);
|
||||||
|
emit fileLocalPathResponse(messageId, path);
|
||||||
|
} else {
|
||||||
|
files.removeRecord(url);
|
||||||
|
startUpload(messageId, url, path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Archive::NotFound e) {
|
} catch (Archive::NotFound e) {
|
||||||
startUpload(messageId, url, path);
|
startUpload(messageId, url, path);
|
||||||
@ -474,6 +480,9 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
|
|||||||
{
|
{
|
||||||
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), 0});
|
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), 0});
|
||||||
QNetworkRequest req(put);
|
QNetworkRequest req(put);
|
||||||
|
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
|
||||||
|
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
|
||||||
|
}
|
||||||
QFile* file = new QFile(path);
|
QFile* file = new QFile(path);
|
||||||
if (file->open(QIODevice::ReadOnly)) {
|
if (file->open(QIODevice::ReadOnly)) {
|
||||||
upl->reply = manager->put(req, file);
|
upl->reply = manager->put(req, file);
|
||||||
|
2
main.cpp
2
main.cpp
@ -131,6 +131,8 @@ int main(int argc, char *argv[])
|
|||||||
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
|
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
|
||||||
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress);
|
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress);
|
||||||
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError);
|
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError);
|
||||||
|
QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::uploadFileProgress);
|
||||||
|
QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::uploadFileError);
|
||||||
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
|
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
|
||||||
|
|
||||||
coreThread->start();
|
coreThread->start();
|
||||||
|
@ -100,6 +100,8 @@ public slots:
|
|||||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||||
void downloadFileError(const QString& messageId, const QString& error);
|
void downloadFileError(const QString& messageId, const QString& error);
|
||||||
void downloadFileProgress(const QString& messageId, qreal value);
|
void downloadFileProgress(const QString& messageId, qreal value);
|
||||||
|
void uploadFileError(const QString& messageId, const QString& error);
|
||||||
|
void uploadFileProgress(const QString& messageId, qreal value);
|
||||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -38,12 +38,10 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
|
|||||||
file(0),
|
file(0),
|
||||||
progress(0),
|
progress(0),
|
||||||
fileComment(new QLabel()),
|
fileComment(new QLabel()),
|
||||||
errorText(""),
|
|
||||||
hasDownloadButton(false),
|
hasDownloadButton(false),
|
||||||
hasProgress(false),
|
hasProgress(false),
|
||||||
hasFile(false),
|
hasFile(false),
|
||||||
commentAdded(false),
|
commentAdded(false)
|
||||||
errorDownloadingFile(false)
|
|
||||||
{
|
{
|
||||||
body->setBackgroundRole(QPalette::AlternateBase);
|
body->setBackgroundRole(QPalette::AlternateBase);
|
||||||
body->setAutoFillBackground(true);
|
body->setAutoFillBackground(true);
|
||||||
@ -101,12 +99,17 @@ QString Message::getId() const
|
|||||||
return msg.getId();
|
return msg.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Message::getFileUrl() const
|
||||||
|
{
|
||||||
|
return msg.getOutOfBandUrl();
|
||||||
|
}
|
||||||
|
|
||||||
void Message::setSender(const QString& p_sender)
|
void Message::setSender(const QString& p_sender)
|
||||||
{
|
{
|
||||||
sender->setText(p_sender);
|
sender->setText(p_sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::addDownloadDialog()
|
void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& comment)
|
||||||
{
|
{
|
||||||
hideFile();
|
hideFile();
|
||||||
hideProgress();
|
hideProgress();
|
||||||
@ -116,16 +119,14 @@ void Message::addDownloadDialog()
|
|||||||
text->setText("");
|
text->setText("");
|
||||||
text->hide();
|
text->hide();
|
||||||
}
|
}
|
||||||
downloadButton = new QPushButton(QIcon::fromTheme("download"), tr("Download"));
|
downloadButton = new QPushButton(icon, buttonText);
|
||||||
downloadButton->setToolTip("<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
|
downloadButton->setToolTip("<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
|
||||||
if (errorDownloadingFile) {
|
if (comment.size() != 0) {
|
||||||
fileComment->setWordWrap(true);
|
fileComment->setWordWrap(true);
|
||||||
fileComment->setText(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", errorText.toLatin1())));
|
fileComment->setText(comment);
|
||||||
} else {
|
|
||||||
fileComment->setText(tr("%1 is offering you to download a file").arg(sender->text()));
|
|
||||||
}
|
|
||||||
fileComment->show();
|
fileComment->show();
|
||||||
connect(downloadButton, &QPushButton::clicked, this, &Message::onDownload);
|
}
|
||||||
|
connect(downloadButton, &QPushButton::clicked, this, &Message::downloadFile);
|
||||||
bodyLayout->insertWidget(2, fileComment);
|
bodyLayout->insertWidget(2, fileComment);
|
||||||
bodyLayout->insertWidget(3, downloadButton);
|
bodyLayout->insertWidget(3, downloadButton);
|
||||||
hasDownloadButton = true;
|
hasDownloadButton = true;
|
||||||
@ -133,12 +134,7 @@ void Message::addDownloadDialog()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::onDownload()
|
void Message::setProgress(qreal value, const QString& label)
|
||||||
{
|
|
||||||
emit downloadFile(msg.getId(), msg.getOutOfBandUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::setProgress(qreal value)
|
|
||||||
{
|
{
|
||||||
hideFile();
|
hideFile();
|
||||||
hideDownload();
|
hideDownload();
|
||||||
@ -150,7 +146,9 @@ void Message::setProgress(qreal value)
|
|||||||
}
|
}
|
||||||
progress = new QProgressBar();
|
progress = new QProgressBar();
|
||||||
progress->setRange(0, 100);
|
progress->setRange(0, 100);
|
||||||
fileComment->setText("Downloading...");
|
if (label.size() != 0) {
|
||||||
|
fileComment->setText(label);
|
||||||
|
}
|
||||||
fileComment->show();
|
fileComment->show();
|
||||||
bodyLayout->insertWidget(2, progress);
|
bodyLayout->insertWidget(2, progress);
|
||||||
bodyLayout->insertWidget(3, fileComment);
|
bodyLayout->insertWidget(3, fileComment);
|
||||||
@ -214,7 +212,6 @@ void Message::hideDownload()
|
|||||||
downloadButton->deleteLater();
|
downloadButton->deleteLater();
|
||||||
downloadButton = 0;
|
downloadButton = 0;
|
||||||
hasDownloadButton = false;
|
hasDownloadButton = false;
|
||||||
errorDownloadingFile = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,10 +232,3 @@ void Message::hideProgress()
|
|||||||
hasProgress = false;;
|
hasProgress = false;;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::showError(const QString& error)
|
|
||||||
{
|
|
||||||
errorDownloadingFile = true;
|
|
||||||
errorText = error;
|
|
||||||
addDownloadDialog();
|
|
||||||
}
|
|
||||||
|
@ -30,9 +30,9 @@
|
|||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "../../global.h"
|
#include "global.h"
|
||||||
#include "../utils/resizer.h"
|
#include "resizer.h"
|
||||||
#include "../utils/image.h"
|
#include "image.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo write docs
|
* @todo write docs
|
||||||
@ -46,14 +46,14 @@ public:
|
|||||||
|
|
||||||
void setSender(const QString& sender);
|
void setSender(const QString& sender);
|
||||||
QString getId() const;
|
QString getId() const;
|
||||||
|
QString getFileUrl() const;
|
||||||
|
|
||||||
void addDownloadDialog();
|
void addButton(const QIcon& icon, const QString& buttonText, const QString& comment = "");
|
||||||
void showFile(const QString& path);
|
void showFile(const QString& path);
|
||||||
void showError(const QString& error);
|
void setProgress(qreal value, const QString& label = "");
|
||||||
void setProgress(qreal value);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void downloadFile(const QString& messageId, const QString& url);
|
void downloadFile();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Shared::Message msg;
|
Shared::Message msg;
|
||||||
@ -67,15 +67,10 @@ private:
|
|||||||
QLabel* file;
|
QLabel* file;
|
||||||
QProgressBar* progress;
|
QProgressBar* progress;
|
||||||
QLabel* fileComment;
|
QLabel* fileComment;
|
||||||
QString errorText;
|
|
||||||
bool hasDownloadButton;
|
bool hasDownloadButton;
|
||||||
bool hasProgress;
|
bool hasProgress;
|
||||||
bool hasFile;
|
bool hasFile;
|
||||||
bool commentAdded;
|
bool commentAdded;
|
||||||
bool errorDownloadingFile;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onDownload();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void hideDownload();
|
void hideDownload();
|
||||||
|
@ -26,10 +26,13 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
|
|||||||
messageOrder(),
|
messageOrder(),
|
||||||
myMessages(),
|
myMessages(),
|
||||||
palMessages(),
|
palMessages(),
|
||||||
|
uploadPaths(),
|
||||||
layout(new QVBoxLayout(this)),
|
layout(new QVBoxLayout(this)),
|
||||||
myName(),
|
myName(),
|
||||||
palNames(),
|
palNames(),
|
||||||
views(),
|
views(),
|
||||||
|
uploading(),
|
||||||
|
downloading(),
|
||||||
room(p_room),
|
room(p_room),
|
||||||
busyShown(false),
|
busyShown(false),
|
||||||
progress()
|
progress()
|
||||||
@ -125,14 +128,27 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
|
|||||||
layout->insertLayout(index, message);
|
layout->insertLayout(index, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.hasOutOfBandUrl()) {\
|
if (msg.hasOutOfBandUrl()) {
|
||||||
emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl());
|
emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl());
|
||||||
connect(message, &Message::downloadFile, this, &MessageLine::downloadFile);
|
connect(message, &Message::downloadFile, this, &MessageLine::onDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageLine::onDownload()
|
||||||
|
{
|
||||||
|
Message* msg = static_cast<Message*>(sender());
|
||||||
|
QString messageId = msg->getId();
|
||||||
|
Index::const_iterator itr = downloading.find(messageId);
|
||||||
|
if (itr == downloading.end()) {
|
||||||
|
downloading.insert(std::make_pair(messageId, msg));
|
||||||
|
emit downloadFile(messageId, msg->getFileUrl());
|
||||||
|
} else {
|
||||||
|
qDebug() << "An attempt to initiate download for already downloading file" << msg->getFileUrl() << ", skipping";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MessageLine::setMyName(const QString& name)
|
void MessageLine::setMyName(const QString& name)
|
||||||
{
|
{
|
||||||
myName = name;
|
myName = name;
|
||||||
@ -192,13 +208,18 @@ void MessageLine::hideBusyIndicator()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLine::responseDownloadProgress(const QString& messageId, qreal progress)
|
void MessageLine::fileProgress(const QString& messageId, qreal progress)
|
||||||
{
|
{
|
||||||
Index::const_iterator itr = messageIndex.find(messageId);
|
Index::const_iterator itr = downloading.find(messageId);
|
||||||
if (itr == messageIndex.end()) {
|
if (itr == downloading.end()) {
|
||||||
|
Index::const_iterator itr = uploading.find(messageId);
|
||||||
|
if (itr == uploading.end()) {
|
||||||
|
//TODO may be some logging, that's not normal
|
||||||
} else {
|
} else {
|
||||||
itr->second->setProgress(progress);
|
itr->second->setProgress(progress, tr("Uploading..."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itr->second->setProgress(progress, tr("Downloading..."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,18 +232,31 @@ void MessageLine::responseLocalFile(const QString& messageId, const QString& pat
|
|||||||
if (path.size() > 0) {
|
if (path.size() > 0) {
|
||||||
itr->second->showFile(path);
|
itr->second->showFile(path);
|
||||||
} else {
|
} else {
|
||||||
itr->second->addDownloadDialog();
|
itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), tr("Push the button to daownload the file"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLine::downloadError(const QString& messageId, const QString& error)
|
void MessageLine::fileError(const QString& messageId, const QString& error)
|
||||||
{
|
{
|
||||||
Index::const_iterator itr = messageIndex.find(messageId);
|
Index::const_iterator itr = downloading.find(messageId);
|
||||||
if (itr == messageIndex.end()) {
|
if (itr == downloading.end()) {
|
||||||
|
Index::const_iterator itr = uploading.find(messageId);
|
||||||
|
if (itr == uploading.end()) {
|
||||||
|
//TODO may be some logging, that's not normal
|
||||||
} else {
|
} else {
|
||||||
itr->second->showError(error);
|
//itr->second->showError(error);
|
||||||
|
//itr->second->addDownloadDialog();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itr->second->addButton(QIcon::fromTheme("download"), tr("Download"),
|
||||||
|
tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
|
||||||
|
{
|
||||||
|
message(msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -50,17 +50,22 @@ public:
|
|||||||
void showBusyIndicator();
|
void showBusyIndicator();
|
||||||
void hideBusyIndicator();
|
void hideBusyIndicator();
|
||||||
void responseLocalFile(const QString& messageId, const QString& path);
|
void responseLocalFile(const QString& messageId, const QString& path);
|
||||||
void downloadError(const QString& messageId, const QString& error);
|
void fileError(const QString& messageId, const QString& error);
|
||||||
void responseDownloadProgress(const QString& messageId, qreal progress);
|
void fileProgress(const QString& messageId, qreal progress);
|
||||||
|
void appendMessageWithUpload(const Shared::Message& message, const QString& path);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void resize(int amount);
|
void resize(int amount);
|
||||||
void downloadFile(const QString& messageId, const QString& url);
|
void downloadFile(const QString& messageId, const QString& url);
|
||||||
|
void uploadFile(const Shared::Message& msg, const QString& path);
|
||||||
void requestLocalFile(const QString& messageId, const QString& url);
|
void requestLocalFile(const QString& messageId, const QString& url);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent * event) override;
|
void resizeEvent(QResizeEvent * event) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDownload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Comparator {
|
struct Comparator {
|
||||||
bool operator()(const Shared::Message& a, const Shared::Message& b) const {
|
bool operator()(const Shared::Message& a, const Shared::Message& b) const {
|
||||||
@ -76,11 +81,14 @@ private:
|
|||||||
Order messageOrder;
|
Order messageOrder;
|
||||||
Index myMessages;
|
Index myMessages;
|
||||||
std::map<QString, Index> palMessages;
|
std::map<QString, Index> palMessages;
|
||||||
|
std::map<QString, QString> uploadPaths;
|
||||||
QVBoxLayout* layout;
|
QVBoxLayout* layout;
|
||||||
|
|
||||||
QString myName;
|
QString myName;
|
||||||
std::map<QString, QString> palNames;
|
std::map<QString, QString> palNames;
|
||||||
std::deque<QHBoxLayout*> views;
|
std::deque<QHBoxLayout*> views;
|
||||||
|
Index uploading;
|
||||||
|
Index downloading;
|
||||||
bool room;
|
bool room;
|
||||||
bool busyShown;
|
bool busyShown;
|
||||||
Progress progress;
|
Progress progress;
|
||||||
|
Loading…
Reference in New Issue
Block a user