Clean up email notification plugin and prepare for improvement
This commit is contained in:
parent
e6347ba0a9
commit
4b11f1b481
@ -36,11 +36,6 @@
|
||||
|
||||
\chapter Supported services
|
||||
|
||||
\section2 Google Mail
|
||||
With the Google Mail Notification you can send a mail with your gmail address to a recipient. The
|
||||
username is your mail address (e.g. "chuck.norris@gmail.com"). The recipient will receive the notification
|
||||
from your gmail account.
|
||||
|
||||
\section2 Yahoo Mail
|
||||
The Yahoo Mail Notification you can send a mail with your yahoo address to a recipient. The username
|
||||
is your mail address (e.g. "chuck.norris@yahoo.com"). The recipient will receive the notification
|
||||
@ -80,48 +75,6 @@ DevicePluginMailNotification::~DevicePluginMailNotification()
|
||||
|
||||
DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Device *device)
|
||||
{
|
||||
// Google mail
|
||||
if(device->deviceClassId() == googleMailDeviceClassId) {
|
||||
SmtpClient *smtpClient = new SmtpClient(this);
|
||||
smtpClient->setHost("smtp.gmail.com");
|
||||
smtpClient->setPort(465);
|
||||
smtpClient->setUser(device->paramValue(googleMailDeviceUserParamTypeId).toString());
|
||||
// TODO: use cryptography to save password not as plain text
|
||||
smtpClient->setPassword(device->paramValue(googleMailDevicePasswordParamTypeId).toString());
|
||||
smtpClient->setAuthMethod(SmtpClient::AuthMethodLogin);
|
||||
smtpClient->setEncryptionType(SmtpClient::EncryptionTypeSSL);
|
||||
smtpClient->setSender(device->paramValue(googleMailDeviceUserParamTypeId).toString());
|
||||
smtpClient->setRecipient(device->paramValue(googleMailDeviceRecipientParamTypeId).toString());
|
||||
|
||||
connect(smtpClient, &SmtpClient::testLoginFinished, this, &DevicePluginMailNotification::testLoginFinished);
|
||||
connect(smtpClient, &SmtpClient::sendMailFinished, this, &DevicePluginMailNotification::sendMailFinished);
|
||||
m_clients.insert(smtpClient,device);
|
||||
|
||||
smtpClient->testLogin();
|
||||
|
||||
return DeviceManager::DeviceSetupStatusAsync;
|
||||
}
|
||||
// Yahoo mail
|
||||
if(device->deviceClassId() == yahooMailDeviceClassId) {
|
||||
SmtpClient *smtpClient = new SmtpClient(this);
|
||||
smtpClient->setHost("smtp.mail.yahoo.com");
|
||||
smtpClient->setPort(465);
|
||||
smtpClient->setUser(device->paramValue(yahooMailDeviceUserParamTypeId).toString());
|
||||
// TODO: use cryptography to save password not as plain text
|
||||
smtpClient->setPassword(device->paramValue(yahooMailDevicePasswordParamTypeId).toString());
|
||||
smtpClient->setAuthMethod(SmtpClient::AuthMethodLogin);
|
||||
smtpClient->setEncryptionType(SmtpClient::EncryptionTypeSSL);
|
||||
smtpClient->setSender(device->paramValue(yahooMailDeviceUserParamTypeId).toString());
|
||||
smtpClient->setRecipient(device->paramValue(yahooMailDeviceRecipientParamTypeId).toString());
|
||||
|
||||
connect(smtpClient, &SmtpClient::testLoginFinished, this, &DevicePluginMailNotification::testLoginFinished);
|
||||
connect(smtpClient, &SmtpClient::sendMailFinished, this, &DevicePluginMailNotification::sendMailFinished);
|
||||
m_clients.insert(smtpClient,device);
|
||||
|
||||
smtpClient->testLogin();
|
||||
|
||||
return DeviceManager::DeviceSetupStatusAsync;
|
||||
}
|
||||
// Custom mail
|
||||
if(device->deviceClassId() == customMailDeviceClassId) {
|
||||
SmtpClient *smtpClient = new SmtpClient(this);
|
||||
@ -133,9 +86,9 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic
|
||||
smtpClient->setPassword(device->paramValue(customMailDeviceCustomPasswordParamTypeId).toString());
|
||||
|
||||
if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "PLAIN") {
|
||||
smtpClient->setAuthMethod(SmtpClient::AuthMethodPlain);
|
||||
smtpClient->setAuthenticationMethod(SmtpClient::AuthenticationMethodPlain);
|
||||
} else if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "LOGIN") {
|
||||
smtpClient->setAuthMethod(SmtpClient::AuthMethodLogin);
|
||||
smtpClient->setAuthenticationMethod(SmtpClient::AuthenticationMethodLogin);
|
||||
} else {
|
||||
return DeviceManager::DeviceSetupStatusFailure;
|
||||
}
|
||||
@ -166,12 +119,22 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic
|
||||
|
||||
DeviceManager::DeviceError DevicePluginMailNotification::executeAction(Device *device, const Action &action)
|
||||
{
|
||||
if(action.actionTypeId() == googleMailSendMailActionTypeId) {
|
||||
SmtpClient *smtpClient = m_clients.key(device);
|
||||
smtpClient->sendMail(action.param(googleMailSendMailActionSubjectParamTypeId).value().toString(), action.param(googleMailSendMailActionBodyParamTypeId).value().toString(), action.id());
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
if (device->deviceClassId() == customMailDeviceClassId) {
|
||||
if(action.actionTypeId() == customMailNotifyActionTypeId) {
|
||||
SmtpClient *smtpClient = m_clients.key(device);
|
||||
if (!smtpClient) {
|
||||
qCWarning(dcMailNotification()) << "Could not find SMTP client for " << device;
|
||||
return DeviceManager::DeviceErrorHardwareNotAvailable;
|
||||
}
|
||||
|
||||
smtpClient->sendMail(action.param(customMailNotifyActionTitleParamTypeId).value().toString(),
|
||||
action.param(customMailNotifyActionBodyParamTypeId).value().toString(),
|
||||
action.id());
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
}
|
||||
return DeviceManager::DeviceErrorActionTypeNotFound;
|
||||
}
|
||||
return DeviceManager::DeviceErrorActionTypeNotFound;
|
||||
return DeviceManager::DeviceErrorDeviceClassNotFound;
|
||||
}
|
||||
|
||||
void DevicePluginMailNotification::deviceRemoved(Device *device)
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
"id": "f4844c97-7ca6-4349-904e-ff9749a9fe74",
|
||||
"name": "customMail",
|
||||
"displayName": "Custom mail",
|
||||
"createMethods": ["user"],
|
||||
"createMethods": [ "user" ],
|
||||
"interfaces": [ "notifications" ],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "af30ce7b-fb6b-42f0-889d-20b32f8b8fa4",
|
||||
@ -75,130 +76,12 @@
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "054613b0-3666-4dad-9252-e0ebca187edc",
|
||||
"name": "send",
|
||||
"name": "notify",
|
||||
"displayName": "send mail",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006",
|
||||
"name": "subject",
|
||||
"displayName": "subject",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine"
|
||||
},
|
||||
{
|
||||
"id": "aeb6e79e-9862-43e1-9873-cbdce549344a",
|
||||
"name": "body",
|
||||
"displayName": "body",
|
||||
"type": "QString",
|
||||
"inputType": "TextArea"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "google",
|
||||
"displayName": "Google",
|
||||
"id": "d02399c5-8e79-40be-8cf6-e00b55bbfe7c",
|
||||
"deviceClasses": [
|
||||
{
|
||||
"id": "3869884a-1592-4b8f-84a7-994be18ff555",
|
||||
"name": "googleMail",
|
||||
"displayName": "Google mail",
|
||||
"createMethods": ["user"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "0a4ea3ff-ea76-4a5f-adbb-b8f79faa2156",
|
||||
"name": "user",
|
||||
"displayName": "user",
|
||||
"type": "QString",
|
||||
"inputType": "Mail"
|
||||
},
|
||||
{
|
||||
"id": "f63f6486-5340-472a-b37c-be828111725c",
|
||||
"name": "password",
|
||||
"displayName": "password",
|
||||
"type": "QString",
|
||||
"inputType": "Password"
|
||||
},
|
||||
{
|
||||
"id": "76697fe2-3393-4ed0-a15d-9d041502cfd3",
|
||||
"name": "recipient",
|
||||
"displayName": "recipient",
|
||||
"type": "QString",
|
||||
"inputType": "Mail"
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "054613b0-3666-4dad-9252-e0ebca187edc",
|
||||
"name": "sendMail",
|
||||
"displayName": "send mail",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006",
|
||||
"name": "subject",
|
||||
"displayName": "subject",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine"
|
||||
},
|
||||
{
|
||||
"id": "aeb6e79e-9862-43e1-9873-cbdce549344a",
|
||||
"name": "body",
|
||||
"displayName": "body",
|
||||
"type": "QString",
|
||||
"inputType": "TextArea"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "yahoo",
|
||||
"displayName": "Yahoo",
|
||||
"id": "5eb3e1e9-25a7-4740-806e-5cfc836e4bf5",
|
||||
"deviceClasses": [
|
||||
{
|
||||
"id": "59409e8f-0c83-414f-abd5-bbbf2758acba",
|
||||
"name": "yahooMail",
|
||||
"displayName": "Yahoo mail",
|
||||
"createMethods": ["user"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "0a4ea3ff-ea76-4a5f-adbb-b8f79faa2156",
|
||||
"name": "user",
|
||||
"displayName": "user",
|
||||
"type": "QString",
|
||||
"inputType": "Mail"
|
||||
},
|
||||
{
|
||||
"id": "f63f6486-5340-472a-b37c-be828111725c",
|
||||
"name": "password",
|
||||
"displayName": "password",
|
||||
"type": "QString",
|
||||
"inputType": "Password"
|
||||
},
|
||||
{
|
||||
"id": "76697fe2-3393-4ed0-a15d-9d041502cfd3",
|
||||
"name": "recipient",
|
||||
"displayName": "recipient",
|
||||
"type": "QString",
|
||||
"inputType": "Mail"
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "054613b0-3666-4dad-9252-e0ebca187edc",
|
||||
"name": "send",
|
||||
"displayName": "send mail",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006",
|
||||
"name": "subject",
|
||||
"name": "title",
|
||||
"displayName": "subject",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine"
|
||||
|
||||
@ -27,30 +27,23 @@ SmtpClient::SmtpClient(QObject *parent):
|
||||
QObject(parent)
|
||||
{
|
||||
m_socket = new QSslSocket(this);
|
||||
m_state = InitState;
|
||||
m_testLogin = false;
|
||||
|
||||
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
|
||||
// error because QSslSocket has also a method called error...also QAbstractSocket or QTcpSocket don't work...
|
||||
//connect(m_socket, &QSslSocket::error, this, &SmtpClient::socketError);
|
||||
connect(m_socket, &QSslSocket::connected, this, &SmtpClient::connected);
|
||||
connect(m_socket, &QSslSocket::readyRead, this, &SmtpClient::readData);
|
||||
connect(m_socket, &QSslSocket::disconnected, this, &SmtpClient::disconnected);
|
||||
|
||||
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
|
||||
}
|
||||
|
||||
void SmtpClient::connectToHost()
|
||||
{
|
||||
switch (m_encryptionType) {
|
||||
case EncryptionTypeNone:
|
||||
case EncryptionTypeTLS:
|
||||
m_socket->connectToHost(m_host, m_port);
|
||||
break;
|
||||
case EncryptionTypeSSL:
|
||||
m_socket->connectToHostEncrypted(m_host, m_port);
|
||||
break;
|
||||
case EncryptionTypeTLS:
|
||||
m_socket->connectToHost(m_host,m_port);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -80,59 +73,68 @@ void SmtpClient::readData()
|
||||
responseLine = m_socket->readLine();
|
||||
response.append(responseLine);
|
||||
}
|
||||
responseLine.truncate( 3 );
|
||||
responseLine.truncate(3);
|
||||
|
||||
qCDebug(dcMailNotification()) << "<--" << response << "|" << responseLine;
|
||||
|
||||
switch (m_state) {
|
||||
case InitState:
|
||||
if(responseLine == "220") {
|
||||
if (responseLine == "220") {
|
||||
send("EHLO localhost");
|
||||
if(m_encryptionType == EncryptionTypeNone) {
|
||||
|
||||
if (m_encryptionType == EncryptionTypeNone) {
|
||||
m_state = AuthentificationState;
|
||||
break;
|
||||
}
|
||||
if(m_encryptionType == EncryptionTypeSSL) {
|
||||
|
||||
if (m_encryptionType == EncryptionTypeSSL) {
|
||||
m_state = HandShakeState;
|
||||
break;
|
||||
}
|
||||
if(m_encryptionType == EncryptionTypeTLS) {
|
||||
|
||||
if (m_encryptionType == EncryptionTypeTLS) {
|
||||
m_state = StartTlsState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HandShakeState:
|
||||
if(responseLine == "250") {
|
||||
if(!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) {
|
||||
if (responseLine == "250") {
|
||||
if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) {
|
||||
m_socket->startClientEncryption();
|
||||
}
|
||||
send("EHLO localhost");
|
||||
m_state = AuthentificationState;
|
||||
}
|
||||
if(responseLine == "220") {
|
||||
if(!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) {
|
||||
|
||||
if (responseLine == "220") {
|
||||
if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) {
|
||||
m_socket->startClientEncryption();
|
||||
}
|
||||
|
||||
send("EHLO localhost");
|
||||
m_state = AuthentificationState;
|
||||
}
|
||||
|
||||
break;
|
||||
case StartTlsState:
|
||||
if(responseLine == "250") {
|
||||
if (responseLine == "250") {
|
||||
send("STARTTLS");
|
||||
m_state = HandShakeState;
|
||||
}
|
||||
|
||||
break;
|
||||
case AuthentificationState:
|
||||
if(responseLine == "250") {
|
||||
if(m_authMethod == AuthMethodLogin) {
|
||||
if (responseLine == "250") {
|
||||
if (m_authenticationMethod == AuthenticationMethodLogin) {
|
||||
send("AUTH LOGIN");
|
||||
m_state = UserState;
|
||||
break;
|
||||
}
|
||||
if(m_authMethod == AuthMethodPlain) {
|
||||
if (m_authenticationMethod == AuthenticationMethodPlain) {
|
||||
send("AUTH PLAIN " + QByteArray().append((char) 0).append(m_user).append((char) 0).append(m_password).toBase64());
|
||||
// if we just want to test the Login, we are almost done here
|
||||
if(!m_testLogin) {
|
||||
if (!m_testLogin) {
|
||||
m_state = MailState;
|
||||
} else {
|
||||
m_state = TestLoginFinishedState;
|
||||
@ -142,16 +144,16 @@ void SmtpClient::readData()
|
||||
}
|
||||
break;
|
||||
case UserState:
|
||||
if(responseLine == "334") {
|
||||
if (responseLine == "334") {
|
||||
send(QByteArray().append(m_user).toBase64());
|
||||
m_state = PasswordState;
|
||||
}
|
||||
break;
|
||||
case PasswordState:
|
||||
if(responseLine == "334") {
|
||||
if (responseLine == "334") {
|
||||
send(QByteArray().append(m_password).toBase64());
|
||||
// if we just want to test the Login, we are almost done here
|
||||
if(!m_testLogin) {
|
||||
if (!m_testLogin) {
|
||||
m_state = MailState;
|
||||
} else {
|
||||
m_state = TestLoginFinishedState;
|
||||
@ -160,7 +162,7 @@ void SmtpClient::readData()
|
||||
}
|
||||
break;
|
||||
case TestLoginFinishedState:
|
||||
if(responseLine == "235") {
|
||||
if (responseLine == "235") {
|
||||
emit testLoginFinished(true);
|
||||
} else {
|
||||
emit testLoginFinished(false);
|
||||
@ -169,38 +171,38 @@ void SmtpClient::readData()
|
||||
m_testLogin = false;
|
||||
break;
|
||||
case MailState:
|
||||
if(responseLine == "235") {
|
||||
if (responseLine == "235") {
|
||||
send("MAIL FROM:<" + m_sender + ">");
|
||||
m_state = RcptState;
|
||||
}
|
||||
break;
|
||||
case RcptState:
|
||||
if(responseLine == "250") {
|
||||
if (responseLine == "250") {
|
||||
send("RCPT TO:<" + m_rcpt + ">");
|
||||
m_state = DataState;
|
||||
}
|
||||
break;
|
||||
case DataState:
|
||||
if(responseLine == "250") {
|
||||
if (responseLine == "250") {
|
||||
send("DATA");
|
||||
m_state = BodyState;
|
||||
}
|
||||
break;
|
||||
case BodyState:
|
||||
if(responseLine == "354") {
|
||||
if (responseLine == "354") {
|
||||
send(m_message + "\r\n.\r\n");
|
||||
m_state = QuitState;
|
||||
}
|
||||
break;
|
||||
case QuitState:
|
||||
if(responseLine == "250") {
|
||||
if (responseLine == "250") {
|
||||
emit sendMailFinished(true, m_actionId);
|
||||
send("QUIT");
|
||||
m_state = CloseState;
|
||||
}
|
||||
break;
|
||||
case CloseState:
|
||||
if(responseLine == "221") {
|
||||
if (responseLine == "221") {
|
||||
m_socket->close();
|
||||
}
|
||||
// some mail server does not recognize the QUIT command...so close the connection either way
|
||||
@ -208,7 +210,7 @@ void SmtpClient::readData()
|
||||
break;
|
||||
default:
|
||||
// unexpecterd response code received...
|
||||
if(m_testLogin) {
|
||||
if (m_testLogin) {
|
||||
emit testLoginFinished(false);
|
||||
m_testLogin = false;
|
||||
m_socket->close();
|
||||
@ -256,9 +258,9 @@ void SmtpClient::setEncryptionType(const SmtpClient::EncryptionType &encryptionT
|
||||
m_encryptionType = encryptionType;
|
||||
}
|
||||
|
||||
void SmtpClient::setAuthMethod(const SmtpClient::AuthMethod &authMethod)
|
||||
void SmtpClient::setAuthenticationMethod(const SmtpClient::AuthenticationMethod &authenticationMethod)
|
||||
{
|
||||
m_authMethod = authMethod;
|
||||
m_authenticationMethod = authenticationMethod;
|
||||
}
|
||||
|
||||
void SmtpClient::setUser(const QString &user)
|
||||
@ -281,13 +283,14 @@ void SmtpClient::setRecipient(const QString &rcpt)
|
||||
m_rcpt = rcpt;
|
||||
}
|
||||
|
||||
void SmtpClient::socketError(QAbstractSocket::SocketError error)
|
||||
void SmtpClient::onSocketError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
qCWarning(dcMailNotification) << "Mail socket -> " << error << m_socket->errorString();
|
||||
qCWarning(dcMailNotification) << "Mail socket error" << error << m_socket->errorString();
|
||||
}
|
||||
|
||||
void SmtpClient::send(const QString &data)
|
||||
{
|
||||
qCDebug(dcMailNotification()) << "-->" << qUtf8Printable(data.toUtf8());
|
||||
m_socket->write(data.toUtf8() + "\r\n");
|
||||
m_socket->flush();
|
||||
}
|
||||
|
||||
@ -35,10 +35,11 @@ class SmtpClient : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum AuthMethod{
|
||||
AuthMethodPlain,
|
||||
AuthMethodLogin
|
||||
enum AuthenticationMethod{
|
||||
AuthenticationMethodPlain,
|
||||
AuthenticationMethodLogin
|
||||
};
|
||||
Q_ENUM(AuthenticationMethod)
|
||||
|
||||
enum SendState{
|
||||
InitState,
|
||||
@ -55,12 +56,14 @@ public:
|
||||
QuitState,
|
||||
CloseState
|
||||
};
|
||||
Q_ENUM(SendState)
|
||||
|
||||
enum EncryptionType{
|
||||
EncryptionTypeNone,
|
||||
EncryptionTypeSSL,
|
||||
EncryptionTypeTLS
|
||||
};
|
||||
Q_ENUM(EncryptionType)
|
||||
|
||||
explicit SmtpClient(QObject *parent = 0);
|
||||
|
||||
@ -71,22 +74,22 @@ public:
|
||||
void setHost(const QString &host);
|
||||
void setPort(const int &port);
|
||||
void setEncryptionType(const EncryptionType &encryptionType);
|
||||
void setAuthMethod(const AuthMethod &authMethod);
|
||||
void setAuthenticationMethod(const AuthenticationMethod &authenticationMethod);
|
||||
void setUser(const QString &user);
|
||||
void setPassword(const QString &password);
|
||||
void setSender(const QString &sender);
|
||||
void setRecipient(const QString &rcpt);
|
||||
|
||||
|
||||
private:
|
||||
QSslSocket *m_socket;
|
||||
SendState m_state;
|
||||
QString m_host;
|
||||
int m_port;
|
||||
QSslSocket *m_socket = nullptr;
|
||||
SendState m_state = InitState;
|
||||
QString m_host = "127.0.0.1";
|
||||
int m_port = 25;
|
||||
|
||||
QString m_user;
|
||||
QString m_password;
|
||||
QString m_sender;
|
||||
AuthMethod m_authMethod;
|
||||
AuthenticationMethod m_authenticationMethod;
|
||||
EncryptionType m_encryptionType;
|
||||
QString m_rcpt;
|
||||
QString m_subject;
|
||||
@ -94,14 +97,14 @@ private:
|
||||
QString m_message;
|
||||
ActionId m_actionId;
|
||||
|
||||
bool m_testLogin;
|
||||
bool m_testLogin = false;
|
||||
|
||||
signals:
|
||||
void sendMailFinished(const bool &success, const ActionId &actionId);
|
||||
void testLoginFinished(const bool &success);
|
||||
|
||||
private slots:
|
||||
void socketError(QAbstractSocket::SocketError error);
|
||||
void onSocketError(QAbstractSocket::SocketError error);
|
||||
void connected();
|
||||
void disconnected();
|
||||
void readData();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user