mirror of
https://github.com/ElvishArtisan/rivendell.git
synced 2025-08-16 08:34:12 +02:00
2021-07-14 Fred Gleason <fredg@paravelsystems.com>
* Restored the ability to post to RSS feeds in rdcatch(1). Signed-off-by: Fred Gleason <fredg@paravelsystems.com>
This commit is contained in:
parent
7596303890
commit
ad9e466df9
@ -22047,3 +22047,5 @@
|
|||||||
* Added a 'timeengine_test' test harness.
|
* Added a 'timeengine_test' test harness.
|
||||||
2021-07-14 Fred Gleason <fredg@paravelsystems.com>
|
2021-07-14 Fred Gleason <fredg@paravelsystems.com>
|
||||||
* Removed the 'RDTimeEvent' class.
|
* Removed the 'RDTimeEvent' class.
|
||||||
|
2021-07-14 Fred Gleason <fredg@paravelsystems.com>
|
||||||
|
* Restored the ability to post to RSS feeds in rdcatch(1).
|
||||||
|
@ -44,6 +44,8 @@
|
|||||||
#include <rddebug.h>
|
#include <rddebug.h>
|
||||||
#include <rddownload.h>
|
#include <rddownload.h>
|
||||||
#include <rdescape_string.h>
|
#include <rdescape_string.h>
|
||||||
|
#include <rdfeed.h>
|
||||||
|
#include <rdpodcast.h>
|
||||||
#include <rdsettings.h>
|
#include <rdsettings.h>
|
||||||
#include <rdtempdirectory.h>
|
#include <rdtempdirectory.h>
|
||||||
#include <rdupload.h>
|
#include <rdupload.h>
|
||||||
@ -290,20 +292,6 @@ void MainObject::RunUpload(CatchEvent *evt)
|
|||||||
(const char *)evt->cutName().toUtf8(),
|
(const char *)evt->cutName().toUtf8(),
|
||||||
(const char *)evt->tempName().toUtf8(),evt->id());
|
(const char *)evt->tempName().toUtf8(),evt->id());
|
||||||
|
|
||||||
//
|
|
||||||
// Load Podcast Parameters
|
|
||||||
//
|
|
||||||
if(evt->feedId()>0) {
|
|
||||||
QFile *file=new QFile(evt->tempName());
|
|
||||||
evt->setPodcastLength(file->size());
|
|
||||||
delete file;
|
|
||||||
RDWaveFile *wave=new RDWaveFile(evt->tempName());
|
|
||||||
if(wave->openWave()) {
|
|
||||||
evt->setPodcastTime(wave->getExtTimeLength());
|
|
||||||
}
|
|
||||||
delete wave;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Execute Upload
|
// Execute Upload
|
||||||
//
|
//
|
||||||
@ -311,67 +299,98 @@ void MainObject::RunUpload(CatchEvent *evt)
|
|||||||
(const char *)evt->tempName().toUtf8(),
|
(const char *)evt->tempName().toUtf8(),
|
||||||
(const char *)evt->resolvedUrl().toUtf8(),
|
(const char *)evt->resolvedUrl().toUtf8(),
|
||||||
evt->id());
|
evt->id());
|
||||||
RDUpload *conv=new RDUpload(rda->config(),this);
|
if(evt->feedId()<=0) { // Standard upload
|
||||||
conv->setSourceFile(evt->tempName());
|
RDUpload *conv=new RDUpload(rda->config(),this);
|
||||||
conv->setDestinationUrl(evt->resolvedUrl());
|
conv->setSourceFile(evt->tempName());
|
||||||
QString url_username=evt->urlUsername();
|
conv->setDestinationUrl(evt->resolvedUrl());
|
||||||
QString url_password=evt->urlPassword();
|
QString url_username=evt->urlUsername();
|
||||||
if(url_username.isEmpty()&&
|
QString url_password=evt->urlPassword();
|
||||||
(QUrl(evt->resolvedUrl()).scheme().toLower()=="ftp")) {
|
if(url_username.isEmpty()&&
|
||||||
url_username=RD_ANON_FTP_USERNAME;
|
(QUrl(evt->resolvedUrl()).scheme().toLower()=="ftp")) {
|
||||||
url_password=QString(RD_ANON_FTP_PASSWORD)+"-"+VERSION;
|
url_username=RD_ANON_FTP_USERNAME;
|
||||||
|
url_password=QString(RD_ANON_FTP_PASSWORD)+"-"+VERSION;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// FIXME: Finish implementing ssh(1) identity keys!
|
||||||
|
//
|
||||||
|
switch((conv_err=conv->runUpload(url_username,url_password,"",false,
|
||||||
|
rda->config()->logXloadDebugData()))) {
|
||||||
|
case RDUpload::ErrorOk:
|
||||||
|
catch_connect->setExitCode(evt->id(),RDRecording::Ok,tr("Ok"));
|
||||||
|
qApp->processEvents();
|
||||||
|
rda->syslog(LOG_INFO,"finished upload of %s to %s, id=%d",
|
||||||
|
(const char *)evt->tempName().toUtf8(),
|
||||||
|
(const char *)evt->resolvedUrl().toUtf8(),
|
||||||
|
evt->id());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RDUpload::ErrorInternal:
|
||||||
|
catch_connect->setExitCode(evt->id(),RDRecording::InternalError,
|
||||||
|
RDUpload::errorText(conv_err));
|
||||||
|
qApp->processEvents();
|
||||||
|
rda->syslog(LOG_WARNING,"upload of %s returned an error: \"%s\", id=%d",
|
||||||
|
(const char *)evt->tempName().toUtf8(),
|
||||||
|
(const char *)RDUpload::errorText(conv_err).toUtf8(),
|
||||||
|
evt->id());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
catch_connect->setExitCode(evt->id(),RDRecording::ServerError,
|
||||||
|
RDUpload::errorText(conv_err));
|
||||||
|
qApp->processEvents();
|
||||||
|
rda->syslog(LOG_WARNING,"upload of %s returned an error: \"%s\", id=%d",
|
||||||
|
(const char *)evt->tempName().toUtf8(),
|
||||||
|
(const char *)RDUpload::errorText(conv_err).toUtf8(),
|
||||||
|
evt->id());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delete conv;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Clean Up
|
||||||
|
//
|
||||||
|
if(evt->deleteTempFile()) {
|
||||||
|
unlink(evt->tempName().toUtf8());
|
||||||
|
rda->
|
||||||
|
syslog(LOG_INFO,"deleted file %s",evt->tempName().toUtf8().constData());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RDCheckExitCode("batch.cpp chown",chown(evt->tempName().toUtf8(),
|
||||||
|
rda->config()->uid(),
|
||||||
|
rda->config()->gid()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//
|
else { // Podcast upload
|
||||||
// FIXME: Finish implementing ssh(1) identity keys!
|
unsigned cast_id;
|
||||||
//
|
RDFeed::Error feed_err=RDFeed::ErrorOk;
|
||||||
switch((conv_err=conv->runUpload(url_username,url_password,"",false,
|
|
||||||
rda->config()->logXloadDebugData()))) {
|
RDFeed *feed=new RDFeed(evt->feedId(),rda->config(),this);
|
||||||
case RDUpload::ErrorOk:
|
rda->syslog(LOG_INFO,"starting post of %s to feed \"%s\", id=%d",
|
||||||
|
evt->tempName().toUtf8().constData(),
|
||||||
|
feed->keyName().toUtf8().constData(),
|
||||||
|
evt->id());
|
||||||
|
if((cast_id=feed->postFile(evt->tempName(),&feed_err))==0) {
|
||||||
|
rda->syslog(LOG_WARNING,"post of %s to feed \"%s\" failed [%s], id=%d",
|
||||||
|
evt->tempName().toUtf8().constData(),
|
||||||
|
feed->keyName().toUtf8().constData(),
|
||||||
|
RDFeed::errorString(feed_err).toUtf8().constData(),
|
||||||
|
evt->id());
|
||||||
|
catch_connect->setExitCode(evt->id(),RDRecording::ServerError,
|
||||||
|
RDFeed::errorString(feed_err));
|
||||||
|
delete feed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rda->syslog(LOG_INFO,"post of %s to cast id %u successful, id=%d",
|
||||||
|
evt->tempName().toUtf8().constData(),
|
||||||
|
cast_id,
|
||||||
|
evt->id());
|
||||||
|
RDCart *cart=new RDCart(RDCut::cartNumber(evt->cutName()));
|
||||||
|
RDPodcast *cast=new RDPodcast(rda->config(),cast_id);
|
||||||
|
cast->setItemTitle(cart->title());
|
||||||
catch_connect->setExitCode(evt->id(),RDRecording::Ok,tr("Ok"));
|
catch_connect->setExitCode(evt->id(),RDRecording::Ok,tr("Ok"));
|
||||||
qApp->processEvents();
|
delete cast;
|
||||||
rda->syslog(LOG_INFO,"finished upload of %s to %s, id=%d",
|
delete cart;
|
||||||
(const char *)evt->tempName().toUtf8(),
|
delete feed;
|
||||||
(const char *)evt->resolvedUrl().toUtf8(),
|
|
||||||
evt->id());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RDUpload::ErrorInternal:
|
|
||||||
catch_connect->setExitCode(evt->id(),RDRecording::InternalError,
|
|
||||||
RDUpload::errorText(conv_err));
|
|
||||||
qApp->processEvents();
|
|
||||||
rda->syslog(LOG_WARNING,"upload of %s returned an error: \"%s\", id=%d",
|
|
||||||
(const char *)evt->tempName().toUtf8(),
|
|
||||||
(const char *)RDUpload::errorText(conv_err).toUtf8(),
|
|
||||||
evt->id());
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
catch_connect->setExitCode(evt->id(),RDRecording::ServerError,
|
|
||||||
RDUpload::errorText(conv_err));
|
|
||||||
qApp->processEvents();
|
|
||||||
rda->syslog(LOG_WARNING,"upload of %s returned an error: \"%s\", id=%d",
|
|
||||||
(const char *)evt->tempName().toUtf8(),
|
|
||||||
(const char *)RDUpload::errorText(conv_err).toUtf8(),
|
|
||||||
evt->id());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
delete conv;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Clean Up
|
|
||||||
//
|
|
||||||
if(evt->feedId()>0) {
|
|
||||||
CheckInPodcast(evt);
|
|
||||||
}
|
|
||||||
if(evt->deleteTempFile()) {
|
|
||||||
unlink(evt->tempName().toUtf8());
|
|
||||||
rda->syslog(LOG_INFO,"deleted file %s",
|
|
||||||
(const char *)evt->tempName().toUtf8());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
RDCheckExitCode("batch.cpp chown",chown(evt->tempName().toUtf8(),
|
|
||||||
rda->config()->uid(),
|
|
||||||
rda->config()->gid()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +349,6 @@ MainObject::MainObject(QObject *parent)
|
|||||||
// Time Engine
|
// Time Engine
|
||||||
//
|
//
|
||||||
catch_engine=new RDTimeEngine(this);
|
catch_engine=new RDTimeEngine(this);
|
||||||
catch_engine->setTimeOffset(rda->station()->timeOffset());
|
|
||||||
connect(catch_engine,SIGNAL(timeout(int)),this,SLOT(engineData(int)));
|
connect(catch_engine,SIGNAL(timeout(int)),this,SLOT(engineData(int)));
|
||||||
LoadEngine();
|
LoadEngine();
|
||||||
|
|
||||||
@ -572,7 +571,6 @@ void MainObject::offsetTimerData(int id)
|
|||||||
|
|
||||||
void MainObject::engineData(int id)
|
void MainObject::engineData(int id)
|
||||||
{
|
{
|
||||||
// LogLine(QString().sprintf("engineData(%d)",id));
|
|
||||||
QString sql;
|
QString sql;
|
||||||
RDSqlQuery *q;
|
RDSqlQuery *q;
|
||||||
RDStation *rdstation;
|
RDStation *rdstation;
|
||||||
@ -583,9 +581,6 @@ void MainObject::engineData(int id)
|
|||||||
//
|
//
|
||||||
QDate date=QDate::currentDate();
|
QDate date=QDate::currentDate();
|
||||||
QTime current_time=QTime::currentTime();
|
QTime current_time=QTime::currentTime();
|
||||||
if((current_time.msecsTo(QTime(23,59,59))+1000)<catch_engine->timeOffset()) {
|
|
||||||
date=date.addDays(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Ignore inactive or non-existent events
|
// Ignore inactive or non-existent events
|
||||||
@ -810,10 +805,10 @@ void MainObject::engineData(int id)
|
|||||||
delete q;
|
delete q;
|
||||||
catch_events[event].
|
catch_events[event].
|
||||||
setResolvedUrl(RDDateTimeDecode(catch_events[event].url(),
|
setResolvedUrl(RDDateTimeDecode(catch_events[event].url(),
|
||||||
QDateTime(date.addDays(catch_events[event].eventdateOffset()),
|
QDateTime(date.addDays(catch_events[event].eventdateOffset()),
|
||||||
current_time),rda->station(),RDConfiguration()));
|
current_time),rda->station(),RDConfiguration()));
|
||||||
StartDownloadEvent(event);
|
StartDownloadEvent(event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RDRecording::Upload:
|
case RDRecording::Upload:
|
||||||
if(!RDCut::exists(catch_events[event].cutName())) {
|
if(!RDCut::exists(catch_events[event].cutName())) {
|
||||||
@ -1664,11 +1659,6 @@ void MainObject::DispatchCommand(ServerConnection *conn)
|
|||||||
LoadDeckList();
|
LoadDeckList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cmds.at(0)=="RO") { // Reload Time Offset
|
|
||||||
EchoArgs(conn->id(),'+');
|
|
||||||
catch_engine->setTimeOffset(rda->station()->timeOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
if((cmds.at(0)=="RE")&&(cmds.size()==2)) { // Request Status
|
if((cmds.at(0)=="RE")&&(cmds.size()==2)) { // Request Status
|
||||||
chan=cmds.at(1).toInt(&ok);
|
chan=cmds.at(1).toInt(&ok);
|
||||||
if(!ok) {
|
if(!ok) {
|
||||||
@ -1959,6 +1949,12 @@ void MainObject::LoadEvent(RDSqlQuery *q,CatchEvent *e,bool add)
|
|||||||
e->setChannels(q->value(20).toInt());
|
e->setChannels(q->value(20).toInt());
|
||||||
e->setSampleRate(q->value(21).toUInt());
|
e->setSampleRate(q->value(21).toUInt());
|
||||||
e->setBitrate(q->value(22).toInt());
|
e->setBitrate(q->value(22).toInt());
|
||||||
|
e->setUrl(q->value(37).toString());
|
||||||
|
e->setUrlUsername(q->value(38).toString());
|
||||||
|
e->setUrlPassword(q->value(39).toString());
|
||||||
|
e->setQuality(q->value(40).toInt());
|
||||||
|
e->setNormalizeLevel(q->value(41).toInt());
|
||||||
|
e->setFeedId(q->value(45).toUInt());
|
||||||
e->setMacroCart(q->value(23).toInt());
|
e->setMacroCart(q->value(23).toInt());
|
||||||
e->setSwitchInput(q->value(24).toInt());
|
e->setSwitchInput(q->value(24).toInt());
|
||||||
e->setSwitchOutput(q->value(25).toInt());
|
e->setSwitchOutput(q->value(25).toInt());
|
||||||
@ -1974,15 +1970,9 @@ void MainObject::LoadEvent(RDSqlQuery *q,CatchEvent *e,bool add)
|
|||||||
e->setEndLength(q->value(34).toInt());
|
e->setEndLength(q->value(34).toInt());
|
||||||
e->setEndMatrix(q->value(35).toInt());
|
e->setEndMatrix(q->value(35).toInt());
|
||||||
e->setEndLine(q->value(36).toInt());
|
e->setEndLine(q->value(36).toInt());
|
||||||
e->setUrl(q->value(37).toString());
|
|
||||||
e->setUrlUsername(q->value(38).toString());
|
|
||||||
e->setUrlPassword(q->value(39).toString());
|
|
||||||
e->setQuality(q->value(40).toInt());
|
|
||||||
e->setNormalizeLevel(q->value(41).toInt());
|
|
||||||
e->setAllowMultipleRecordings(RDBool(q->value(42).toString()));
|
e->setAllowMultipleRecordings(RDBool(q->value(42).toString()));
|
||||||
e->setMaxGpiRecordLength(q->value(43).toUInt());
|
e->setMaxGpiRecordLength(q->value(43).toUInt());
|
||||||
e->setDescription(q->value(44).toString());
|
e->setDescription(q->value(44).toString());
|
||||||
e->setFeedId(q->value(45).toUInt());
|
|
||||||
e->setEventdateOffset(q->value(46).toInt());
|
e->setEventdateOffset(q->value(46).toInt());
|
||||||
e->setEnableMetadata(RDBool(q->value(47).toString()));
|
e->setEnableMetadata(RDBool(q->value(47).toString()));
|
||||||
|
|
||||||
@ -2347,70 +2337,6 @@ void MainObject::CheckInRecording(QString cutname,CatchEvent *evt,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MainObject::CheckInPodcast(CatchEvent *e) const
|
|
||||||
{
|
|
||||||
QString sql;
|
|
||||||
RDSqlQuery *q;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Purge Stale Casts
|
|
||||||
//
|
|
||||||
sql=QString("delete from `PODCASTS` where ")+
|
|
||||||
QString().sprintf("(`FEED_ID`=%d)&&",e->feedId())+
|
|
||||||
"(`AUDIO_FILENAME`='"+RDEscapeString(RDGetBasePart(e->resolvedUrl()))+"')";
|
|
||||||
RDSqlQuery::apply(sql);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Get Channel Parameters
|
|
||||||
//
|
|
||||||
sql=QString("select ")+
|
|
||||||
"`ENABLE_AUTOPOST`,"+ // 00
|
|
||||||
"`CHANNEL_TITLE`,"+ // 01
|
|
||||||
"`CHANNEL_DESCRIPTION`,"+ // 02
|
|
||||||
"`CHANNEL_CATEGORY`,"+ // 03
|
|
||||||
"`CHANNEL_LINK`,"+ // 04
|
|
||||||
"`MAX_SHELF_LIFE` "+ // 05
|
|
||||||
"from `FEEDS` where "+
|
|
||||||
QString().sprintf("`ID`=%u",e->feedId());
|
|
||||||
q=new RDSqlQuery(sql);
|
|
||||||
if(!q->first()) {
|
|
||||||
delete q;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Add the Cast Entry
|
|
||||||
//
|
|
||||||
RDPodcast::Status status=RDPodcast::StatusPending;
|
|
||||||
if(q->value(0).toString().toLower()=="y") {
|
|
||||||
status=RDPodcast::StatusActive;
|
|
||||||
}
|
|
||||||
sql=QString("insert into `PODCASTS` set ")+
|
|
||||||
QString().sprintf("`FEED_ID`=%u,",e->feedId())+
|
|
||||||
QString().sprintf("`STATUS`=%u,",status)+
|
|
||||||
"`ITEM_TITLE`='"+RDEscapeString(q->value(1).toString())+"',"+
|
|
||||||
"`ITEM_DESCRIPTION`='"+RDEscapeString(q->value(2).toString())+"',"+
|
|
||||||
"`ITEM_CATEGORY`='"+RDEscapeString(q->value(3).toString())+"',"+
|
|
||||||
"`ITEM_LINK`='"+RDEscapeString(q->value(4).toString())+"',"+
|
|
||||||
"`AUDIO_FILENAME`='"+RDEscapeString(RDGetBasePart(e->resolvedUrl()))+"',"+
|
|
||||||
QString().sprintf("`AUDIO_LENGTH`=%u,",e->podcastLength())+
|
|
||||||
QString().sprintf("`AUDIO_TIME`=%u,",e->podcastTime())+
|
|
||||||
QString().sprintf("`SHELF_LIFE`=%u,",q->value(5).toUInt())+
|
|
||||||
"`EFFECTIVE_DATETIME`=now(),"+
|
|
||||||
"`ORIGIN_DATETIME`=now()";
|
|
||||||
delete q;
|
|
||||||
RDSqlQuery::apply(sql);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Update the Build Date
|
|
||||||
//
|
|
||||||
sql=QString("update `FEEDS` set ")+
|
|
||||||
"`LAST_BUILD_DATETIME`=now() where "+
|
|
||||||
QString().sprintf("`ID`=%u",e->feedId());
|
|
||||||
RDSqlQuery::apply(sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RDRecording::ExitCode MainObject::ReadExitCode(int event)
|
RDRecording::ExitCode MainObject::ReadExitCode(int event)
|
||||||
{
|
{
|
||||||
RDRecording::ExitCode code=RDRecording::InternalError;
|
RDRecording::ExitCode code=RDRecording::InternalError;
|
||||||
|
@ -24,17 +24,7 @@
|
|||||||
#define XLOAD_UPDATE_INTERVAL 1000
|
#define XLOAD_UPDATE_INTERVAL 1000
|
||||||
#define RDCATCHD_USAGE "[-d][--event-id=<id>]\n\nOptions:\n\n-d\n Set 'debug' mode, causing rdcatchd(8) to stay in the foreground\n and print debugging info on standard output.\n\n--event-id=<id>\n Execute event <id> and then exit.\n\n"
|
#define RDCATCHD_USAGE "[-d][--event-id=<id>]\n\nOptions:\n\n-d\n Set 'debug' mode, causing rdcatchd(8) to stay in the foreground\n and print debugging info on standard output.\n\n--event-id=<id>\n Execute event <id> and then exit.\n\n"
|
||||||
|
|
||||||
#include <vector>
|
#include <QTcpServer>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qstring.h>
|
|
||||||
#include <qtcpserver.h>
|
|
||||||
#include <qsignalmapper.h>
|
|
||||||
#include <qtimer.h>
|
|
||||||
#include <qhostaddress.h>
|
|
||||||
#include <qsignalmapper.h>
|
|
||||||
|
|
||||||
#include <rd.h>
|
#include <rd.h>
|
||||||
#include <rdcart.h>
|
#include <rdcart.h>
|
||||||
@ -178,7 +168,6 @@ class MainObject : public QObject
|
|||||||
void LoadHeartbeat();
|
void LoadHeartbeat();
|
||||||
void CheckInRecording(QString cutname,CatchEvent *evt,unsigned msecs,
|
void CheckInRecording(QString cutname,CatchEvent *evt,unsigned msecs,
|
||||||
unsigned threshold);
|
unsigned threshold);
|
||||||
void CheckInPodcast(CatchEvent *e) const;
|
|
||||||
RDRecording::ExitCode ReadExitCode(int event);
|
RDRecording::ExitCode ReadExitCode(int event);
|
||||||
void WriteExitCode(int event,RDRecording::ExitCode code,
|
void WriteExitCode(int event,RDRecording::ExitCode code,
|
||||||
const QString &err_text="");
|
const QString &err_text="");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user