ogbrowser/src/mainwindow.cpp

574 lines
16 KiB
C++

#include "mainwindow.h"
#include <QtWebEngineWidgets>
#include <QStringList>
#include <QWebEngineView>
#include <QDockWidget>
#include <QtDebug>
#include <QWebEnginePage>
#include <QProcess>
#include <QTextEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QDateTime>
#include <QProgressBar>
#include <QTabWidget>
#include <QLineEdit>
#include <QNetworkReply>
#include <QSslError>
#include <QTimer>
#include <QRegularExpression>
#include <QStringList>
#include <QString>
#include <libintl.h>
#include "qtermwidget.h"
#include "digitalclock.h"
#include "ogurlhandler.h"
#define BUFFERSIZE 2048
#define REGEXP_STRING "^\\[(\\d+)\\]"
#define CURRENT_TIME() QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss")
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),m_web(new QWebEngineView()),m_output(new QTextEdit()),
m_logfile(0),m_logstream(0),m_numberTerminal(0)
{
// Graphic
setWindowTitle(tr("OpenGnsys Browser"));
setCentralWidget(m_web);
readEnvironmentValues();
m_is_admin = qgetenv("ogactiveadmin") == "true";
m_kiosk_mode = qgetenv("OGKIOSKMODE") == "true";
// Open the log file for append
if(m_env.contains("OGLOGFILE") && m_env["OGLOGFILE"]!="")
{
QFile* m_logfile=new QFile(m_env["OGLOGFILE"]);
if(!m_logfile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append))
{
delete m_logfile;
print(tr(gettext("El fichero de log no ha podido ser abierto: "))+m_env["OGLOGFILE"]+".");
}
else
{
m_logstream=new QTextStream(m_logfile);
}
}
// Output
m_output->setReadOnly(true);
m_output->setFontPointSize(16);
// Button Dock
QDockWidget* dock=new QDockWidget();
dock->setAllowedAreas(Qt::BottomDockWidgetArea);
QWidget* dummy=new QWidget();
dummy->setMaximumHeight(0);
dock->setTitleBarWidget(dummy);
// TabWidget
m_tabs=new QTabWidget(dock);
QPushButton *button=new QPushButton(tr(gettext("&Nueva Terminal")));
button->setFocusPolicy(Qt::TabFocus);
m_tabs->setCornerWidget(button);
m_tabs->setFocusPolicy(Qt::NoFocus);
m_tabs->addTab(m_output,tr(gettext("Salida")));
slotCreateTerminal();
// Assign tabs to dock
dock->setWidget(m_tabs);
// Assign tabs dock to the mainwindow if admin mode is active
if(isAdmin())
addDockWidget(Qt::BottomDockWidgetArea,dock);
// Top Dock
dock=new QDockWidget();
dock->setAllowedAreas(Qt::TopDockWidgetArea);
QWidget* dummy2=new QWidget();
dummy2->setMaximumHeight(0);
dock->setTitleBarWidget(dummy2);
// WebBar
m_webBar=new QLineEdit(dock);
// WebBar to dock
dock->setWidget(m_webBar);
// Assign top dock to the mainwindow if admin mode is active
if(isAdmin())
addDockWidget(Qt::TopDockWidgetArea,dock);
// Status bar
QStatusBar* st=statusBar();
st->setSizeGripEnabled(false);
// OpenGnsys logo (or alternate text)
m_logo=new QLabel();
QPixmap logo;
if(logo.load("/opt/opengnsys/lib/pictures/oglogo.png"))
m_logo->setPixmap(logo);
else
m_logo->setText("OG");
m_logo->setToolTip(tr(gettext("Proyecto OpenGnsys"))+"\nhttps://opengnsys.es");
// Progress bar
m_progressBar=new QProgressBar(this);
m_progressBar->setRange(0,100);
// Connection speed
QString speed=readSpeed();
m_speedInfo=new QLabel(speed);
m_speedInfo->setAlignment(Qt::AlignCenter);
if(m_env.contains("DEFAULTSPEED") && m_env["DEFAULTSPEED"]!="")
if(speed.compare(m_env["DEFAULTSPEED"])!=0)
m_speedInfo->setStyleSheet("background-color: darkred; color: white; font-weight: bold;");
// Clock
m_clock=new DigitalClock(this);
connect(m_web,SIGNAL(loadStarted()),this,SLOT(slotWebLoadStarted()));
connect(m_web,SIGNAL(loadFinished(bool)),this,SLOT(slotWebLoadFinished(bool)));
connect(m_web,SIGNAL(loadProgress(int)),this,SLOT(slotWebLoadProgress(int)));
connect(m_web,SIGNAL(urlChanged(const QUrl&)),this,SLOT(slotUrlChanged(const QUrl&)));
// Ignore SSL errors.
// Qindel:
//connect(m_web->page()->networkAccessManager(),
// SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)), this,
// SLOT(slotSslErrors(QNetworkReply*)));
// Dock signals
connect(button,SIGNAL(clicked()),this,SLOT(slotCreateTerminal()));
// All schemes need registering first, then their handlers.
registerScheme("command");
registerScheme("command+output");
registerScheme("command+confirm");
registerScheme("command+confirm+output");
registerScheme("command+output+confirm");
registerHandler("command", false, false);
registerHandler("command+output", false, true);
registerHandler("command+confirm", true, false);
registerHandler("command+confirm+output", true, true);
registerHandler("command+output+confirm", true, true);
QStringList arguments=QCoreApplication::arguments();
m_webBar->setText(arguments.at(1));
m_web->load(QUrl(arguments.at(1)));
showMaximized();
showFullScreen();
}
void MainWindow::closeEvent(QCloseEvent *event) {
if (isKioskMode()) {
qInfo() << "Modo quiosco activado, ignorando intento de cerrar ventana";
event->ignore();
}
}
MainWindow::~MainWindow()
{
if(m_logfile)
{
m_logfile->close();
delete m_logfile;
}
if(m_logstream)
delete m_logstream;
}
void MainWindow::registerScheme(const QString &name) {
QWebEngineUrlScheme scheme(name.toLatin1());
scheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
scheme.setDefaultPort(0);
scheme.setFlags(QWebEngineUrlScheme::LocalScheme);
QWebEngineUrlScheme::registerScheme(scheme);
}
void MainWindow::registerHandler(const QString &commandName, bool confirm, bool returnOutput) {
OGBrowserUrlHandlerCommand *handler = new OGBrowserUrlHandlerCommand(this);
connect(handler, &OGBrowserUrlHandlerCommand::command, this, &MainWindow::commandQueued);
handler->setAskConfirmation(confirm);
handler->setReturnOutput(returnOutput);
QWebEngineProfile::defaultProfile()->installUrlSchemeHandler(commandName.toLatin1(), handler);
}
void MainWindow::commandQueued(const QString &command, bool confirm, bool returnOutput) {
//PendingCommand cmd;
qInfo() << "Queued command:" << command;
if (confirm) {
QMessageBox msgBox;
msgBox.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint);
msgBox.setWindowTitle(tr(gettext("AVISO")));
msgBox.setIcon(QMessageBox::Question);
msgBox.setTextFormat(Qt::RichText);
msgBox.setText(tr(gettext("La siguiente acci&oacute;n puede modificar datos o tardar varios minutos. El equipo no podr&aacute; ser utilizado durante su ejecuci&oacute;n.")));
QPushButton *execButton = msgBox.addButton(tr(gettext("Ejecutar")), QMessageBox::ActionRole);
msgBox.addButton(tr(gettext("Cancelar")), QMessageBox::RejectRole);
msgBox.setDefaultButton(execButton);
msgBox.exec();
if (msgBox.clickedButton() != execButton) {
qInfo() << "User rejected running the command";
return;
}
}
if (returnOutput && !isAdmin()) {
int w=MainWindow::width(), h=MainWindow::height();
m_output->setWindowFlags(Qt::Window);
m_output->move(100, 100);
m_output->setFixedSize(w*0.8-100, h*0.8-100);
m_output->show();
}
m_command.command = command;
m_command.confirm = confirm;
m_command.returnOutput = returnOutput;
QStringList list=command.split(" ",Qt::SkipEmptyParts);
QString program=list.takeFirst();
m_command.process = new QProcess(this);
m_command.process->setReadChannel(QProcess::StandardOutput);
m_command.process->setEnvironment(QProcess::systemEnvironment());
// Process signals
connect(m_command.process, &QProcess::started,this, &MainWindow::slotProcessStarted);
connect(m_command.process, &QProcess::finished,this,&MainWindow::slotProcessFinished);
connect(m_command.process, &QProcess::errorOccurred, this,&MainWindow::slotProcessError);
connect(m_command.process, &QProcess::readyReadStandardOutput,this,&MainWindow::slotProcessOutput);
connect(m_command.process, &QProcess::readyReadStandardError,this,&MainWindow::slotProcessErrorOutput);
if(isAdmin()) {
m_output->setTextColor(QColor(Qt::darkGreen));
print(tr(gettext("Lanzando el comando: "))+command);
m_output->setTextColor(QColor(Qt::black));
} else {
write(tr(gettext("Lanzando el comando: "))+command);
}
m_command.process->start(program,list);
startProgressBar();
}
void MainWindow::slotWebLoadStarted()
{
startProgressBar();
m_progressBar->setFormat(gettext("%p% Cargando"));
}
void MainWindow::slotWebLoadProgress(int progress)
{
m_progressBar->setValue(progress);
}
void MainWindow::slotWebLoadFinished(bool ok)
{
// If any error ocurred, show a pop up
// Sometimes when the url hasn't got a dot, i.e /var/www/pageweb,
// the return value is always true so we check the bytes received too
qWarning() << "Load finished. URL: " << m_web->url() << "; ok = " << ok;
finishProgressBar();
}
void MainWindow::slotUrlChanged(const QUrl &url)
{
m_webBar->setText(url.toString());
}
void MainWindow::slotSslErrors(QNetworkReply* reply)
{
reply->ignoreSslErrors();
}
void MainWindow::slotProcessStarted()
{
startProgressBar();
}
void MainWindow::slotProcessOutput()
{
m_command.process->setReadChannel(QProcess::StandardOutput);
char buf[BUFFERSIZE];
while((m_command.process->readLine(buf,BUFFERSIZE) > 0))
{
QString s(buf);
qInfo() << "OUT: " << buf;
if(isAdmin())
{
m_output->insertPlainText(tr("Proc. stdout: "));
}
print(s);
captureOutputForStatusBar(s);
}
}
void MainWindow::slotProcessErrorOutput()
{
// QProcess *process = qobject_cast<QProcess*>(sender());
// QVector<PendingCommand>::iterator it=std::find(m_commands.begin(), m_commands.end(), []())
m_command.process->setReadChannel(QProcess::StandardError);
char buf[BUFFERSIZE];
while((m_command.process->readLine(buf,BUFFERSIZE) > 0))
{
QString s(buf);
qInfo() << "ERR: " << buf;
if(isAdmin())
{
m_output->insertPlainText(tr("Proc. stderr: "));
}
m_output->setTextColor(QColor(Qt::darkBlue));
print(s);
m_output->setTextColor(QColor(Qt::black));
}
}
void MainWindow::slotProcessFinished(int code, QProcess::ExitStatus status)
{
qInfo() << "Finished: " << m_command.command << "with status" << status;
if(isAdmin())
{
// Admin user: show process status
if(status==QProcess::NormalExit)
{
if(code > 0)
{
m_output->setTextColor(QColor(Qt::darkRed));
}
print("\n"+tr(gettext("Fin del proceso. Valor de retorno: "))+QString::number(code));
}
else
{
m_output->setTextColor(QColor(Qt::darkRed));
print("\n"+tr(gettext("El proceso ha fallado inesperadamente. Salida: ")+code));
}
m_output->setTextColor(QColor(Qt::black));
}
else
{
// Non-admin user: show instruction to close the popup window
write(tr(gettext("Fin del proceso. Valor de retorno: "))+QString::number(code));
m_output->setFontUnderline(true);
print("\n\n"+tr(gettext("AVISO: Pulsar el botón superior derecho para cerrar"))+" [X]");
m_output->setFontUnderline(false);
}
// On error, show a message box
if(code > 0 && ! m_output->isActiveWindow())
{
showErrorMessage(gettext("Código de salida: ")+QString::number(code));
}
finishProgressBar();
}
void MainWindow::slotProcessError(QProcess::ProcessError error)
{
qCritical() << "Error: " << m_command.command << "with status" << error;
QString errorMsg;
switch(error)
{
case QProcess::FailedToStart:
errorMsg=tr(gettext("Imposible lanzar el proceso."));
break;
case QProcess::WriteError:
errorMsg=tr(gettext("Error de escritura en el proceso."));
break;
case QProcess::ReadError:
errorMsg=tr(gettext("Error de lectura del proceso."));
break;
// No capturo crashed porque la pillo por finished
case QProcess::Crashed:
case QProcess::Timedout:
break;
case QProcess::UnknownError:
default:
errorMsg=tr(gettext("Error desconocido."));
break;
}
// Print error and show message box with timeout.
if(!errorMsg.isNull()) {
m_output->setTextColor(QColor(Qt::darkRed));
print(errorMsg);
m_output->setTextColor(QColor(Qt::black));
showErrorMessage(errorMsg);
}
finishProgressBar();
}
void MainWindow::slotCreateTerminal()
{
QTermWidget* console = new QTermWidget(1,this);
QFont font = QApplication::font();
font.setFamily("DejaVu Sans Mono");
font.setPointSize(12);
console->setTerminalFont(font);
console->setFocusPolicy(Qt::StrongFocus);
console->setScrollBarPosition(QTermWidget::ScrollBarRight);
++m_numberTerminal;
connect(console,SIGNAL(finished()),this,SLOT(slotDeleteTerminal()));
QString name=tr("Term ")+QString::number(m_numberTerminal);
m_tabs->addTab(console,name);
}
void MainWindow::slotDeleteTerminal()
{
QWidget *widget = qobject_cast<QWidget *>(sender());
Q_ASSERT(widget);
m_tabs->removeTab(m_tabs->indexOf(widget));
delete widget;
}
int MainWindow::readEnvironmentValues()
{
// The return value
int ret=true;
// Get all environment variables
QStringList environmentlist=QProcess::systemEnvironment();
// This is the list of the important variables
QStringList variablelist=QString(ENVIRONMENT).split(",");
// This is an auxiliar variable
QStringList stringlist;
foreach (QString str,variablelist)
{
// Look for the variable in the environment
stringlist=environmentlist.filter(str+"=");
if(stringlist.isEmpty())
{
m_env[str]="";
ret=false;
}
else
{
// Get the first element and get the value part
m_env[str]=(stringlist.first().split("="))[1];
}
}
return ret;
}
// Write a string to the log file
void MainWindow::write(QString s)
{
if(! s.endsWith("\n"))
s+="\n";
if(m_logstream)
{
*m_logstream<<CURRENT_TIME()<<": browser: "<<s;
m_logstream->flush();
}
}
// Print and log a string
void MainWindow::print(QString s)
{
if(! s.endsWith("\n"))
s+="\n";
write(s);
if(m_output)
m_output->insertPlainText(s);
}
// Show message in status bar
void MainWindow::captureOutputForStatusBar(QString output)
{
// Modify the status bar
output=output.trimmed();
// Get percentage (string starts with "[Number]")
QRegularExpression regexp(REGEXP_STRING);
QRegularExpressionMatch match = regexp.match(output);
if(match.hasMatch())
{
int pass=match.captured(1).toInt();
output.replace(regexp,"");
m_progressBar->setValue(pass);
m_progressBar->setFormat("%p%"+output);
}
}
// Init status bar
void MainWindow::startProgressBar()
{
QStatusBar* st=statusBar();
st->clearMessage();
st->addWidget(m_logo);
st->addWidget(m_progressBar,90);
st->addWidget(m_speedInfo,5);
st->addWidget(m_clock,5);
m_progressBar->show();
m_clock->show();
m_web->setEnabled(false);
}
// Reset status bar
void MainWindow::finishProgressBar()
{
m_progressBar->reset();
m_web->setEnabled(true);
}
// Returns communication speed
QString MainWindow::readSpeed() {
if(m_env.contains("OGLOGFILE"))
{
QString infoFile=m_env["OGLOGFILE"].replace(".log", ".info.html");
//QString command="grep -hoe \"[0-9]*Mb/s\" "+infoFile+" 2>/dev/null";
QProcess process;
process.start("grep", QStringList({"-hoe", "[0-9]*Mb/s", infoFile}));
process.waitForFinished();
QString speed(process.readAllStandardOutput());
return speed.simplified();
}
else
{
return QString("");
}
}
// Show an error box with timeout
void MainWindow::showErrorMessage(QString text)
{
QMessageBox* msgBox=new QMessageBox();
msgBox->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint);
msgBox->setWindowTitle(gettext("ERROR"));
msgBox->setIcon(QMessageBox::Warning);
msgBox->setText(text);
msgBox->show();
QTimer::singleShot(5000, msgBox, SLOT(close()));
}