rsyncui/mainwindow.cpp

1684 lines
49 KiB
C++

#include "mainwindow.h"
#include <QtGlobal>
//#include <QComboBox>
#include <QTextBlock>
#include "ui_configuration.h"
#include "version.h"
#include "tools.h"
#include "password.h"
#include <kcombobox.h>
#include <signal.h>
#include <QTextStream>
using namespace std;
bool display = false;
extern Ui::Configuration config;
extern bool testRsyncReturn(QProcess *);
extern QApplication a;
QMap<int, QString> rsyncErrorStrings {
{0, QTranslator::tr("Success. The rsync command completed successfully without any errors.")},
{1, QTranslator::tr("Syntax or usage error. There was a problem with the syntax of the rsync command or with the options specified.")},
{2, QTranslator::tr("Protocol incompatibility. There was a problem with the protocol version or negotiation between the rsync client and server.")},
{3, QTranslator::tr("Errors selecting input/output files, dirs. There was a problem with the source or destination file or directory specified in the rsync command.")},
{4, QTranslator::tr("Requested action not supported: An attempt was made to use an unsupported action or option.")},
{5, QTranslator::tr("Error starting client-server protocol. There was an error starting the client-server protocol.")},
{6, QTranslator::tr("Daemon unable to append to log-file. The rsync daemon was unable to write to its log file.")},
{10, QTranslator::tr("Error in socket I/O. There was an error with the socket input/output.")},
{11, QTranslator::tr("Error in file I/O. There was an error reading or writing to a file.")},
{12, QTranslator::tr("Error in rsync protocol data stream. There was an error in the rsync protocol data stream.")},
{13, QTranslator::tr("Errors with program diagnostics. There was an error generating program diagnostics.")},
{14, QTranslator::tr("Error in IPC code. There was an error in the inter-process communication (IPC) code.")},
{20, QTranslator::tr("Received SIGUSR1 or SIGINT. The rsync process was interrupted by a signal.")},
{21, QTranslator::tr("Some error returned by waitpid(). An error occurred while waiting for a child process to complete.")},
{22, QTranslator::tr("Error allocating core memory buffers. There was an error allocating memory buffers.")},
{23, QTranslator::tr("Partial transfer due to error. The rsync command completed with an error, but some files may have been transferred successfully.")},
{24, QTranslator::tr("Partial transfer due to vanished source files. Some source files disappeared before they could be transferred.")}
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QCoreApplication::setOrganizationName("RsyncUI");
QCoreApplication::setApplicationName("RsyncUI");
this->setWindowTitle(a.applicationName());
// context menu for treewidget (list of files)
ui->treeWidget->addAction(ui->actionDownload);
// init configuration window
config.setupUi(&Configuration);
// init about window
AboutW.setupUi(&aboutDialog);
// text of About
this->about.version = version;
QString aboutText = tr("<h2>Client for rsync server</h2>") +
"<b>" + tr("Version") + ": " + version + "</b><br>" +
"<b>" + tr("Licence") + ": " + this->about.licence + "</b><br>" +
"<b>" + tr("Author") + ": " + this->about.author + "</b><br>" +
"<b>" + tr("EMail") + ": " + this->about.email + "</b><br>" +
"<b>" + tr("Source code") + ": " + this->about.git + "</b><br>" +
tr("You click on file to enqueue it, and RyncUI Download one file a time");
AboutW.TextAbout->setHtml(aboutText);
// initialization = true;
info(DEBUGMACRO, "Initialization of widgets");
// init of widgets
ui->ktreewidgetsearchline->setTreeWidget(ui->treeWidget); // attach search widget to treewidget
ui->ktreewidgetsearchline->setCaseSensitivity(Qt::CaseInsensitive); // and set it case insensitive
ui->treeWidget->setHeaderLabels({tr("Path"), tr("Type"), tr("Size"), tr("Date"), "fullSize"} ); // set header of columns of tree widget
// setting arrowcursor for treeWidget, listWidget and listDownload to arrow
ui->treeWidget->setCursor(Qt::ArrowCursor);
ui->listWidget->setCursor(Qt::ArrowCursor);
ui->listDownload->setCursor(Qt::ArrowCursor);
info(DEBUGMACRO, "Hiding progress bar");
// Hiding progress bar
ui->progressBar->hide();
//setting configuration window
config.comboBox->setCurrentIndex(ui->toolBar->toolButtonStyle()); // setting combobox to saved settings
//setting unit of bandwidth limit
config.UnitCombobox->addItems({tr("KB/s"), tr("MB/s"), tr("GB/s"), tr("TB/s"), tr("PB/s")});
// hide fullsize column of treeview
ui->treeWidget->setColumnHidden(4, true);
loadSettings();
initSystemTrayIcon();
init();
// if last server exists in settings
if (this->settings.contains("connexion/lastServer"))
{
info(DEBUGMACRO, "Setting previous server");
// set window to previous server/port configuration
ui->portEdit->setText(this->settings.value("connexion/lastPort").toString());
ui->khistorycombobox->setCurrentText(this->settings.value("connexion/lastServer").toString());
}else
{
info(DEBUGMACRO, "No previous server, so setting only default port");
ui->portEdit->setText(QString::number(this->connexion.port));
ui->khistorycombobox->clear();
}
// connectors
connect(this, &MainWindow::fileName, ui->progressBar, &QProgressBar::setFormat);
connect(this, &MainWindow::progressSignal, ui->progressBar, &QProgressBar::setValue);
connect(this, &MainWindow::stopDownloading, this, &MainWindow::cancelled);
connect(config.buttonBox, SIGNAL(accepted()), this, SLOT(on_buttonBox_accepted()));
connect(config.comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::on_comboBox_currentIndexChanged);
populateList(ui->khistorycombobox->currentText(), ui->portEdit->text().toUInt());
initialization = false;
}
void MainWindow::init()
{
QAbstractButton * reply;
QMessageBox msgBox;
//if exists list of donwloads in saved settings
if (this->settings.value("Downloads/rows").toInt() != 0)
{
// asking if we load the list and continue downloading
msgBox.setWindowTitle(a.applicationName());
msgBox.setInformativeText(tr("A list of interrupted downloads exists, do you want to continue downloading ? if not the list will be cleared" ));
QPushButton *deleteButton = msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
msgBox.addButton(QMessageBox::No);
QPushButton *yes = msgBox.addButton(QMessageBox::Yes);
msgBox.setDefaultButton(QMessageBox::Yes);
msgBox.exec();
reply = msgBox.clickedButton();
info(DEBUGMACRO, "Response => " + reply->text());
// if response is yes then loading list
info(DEBUGMACRO, "reply text is : " + reply->text());
if(reply == yes)
{
loadDownloadList();
}else if (reply == deleteButton)
{
// delete saved download list
deleteDownloadList();
}
}
}
void MainWindow::initSystemTrayIcon()
{
QMenu *trayIconMenu;
QAction * quitAction;
QIcon icon;
info(DEBUGMACRO, "initSystemTrayIcon");
icon.addFile(this->icon);
this->trayIcon = new QSystemTrayIcon;
this->trayIcon->setIcon(icon);
quitAction = new QAction(tr("&Quit"), this);
connect(quitAction, &QAction::triggered, this, &MainWindow::quitApp);
trayIconMenu = new QMenu(this);
trayIconMenu->addAction(quitAction);
trayIcon->setContextMenu(trayIconMenu);
info(DEBUGMACRO, "Connecting signal of trayIcon");
connect(this->trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(on_trayIcon_clicked(QSystemTrayIcon::ActivationReason)));
this->trayIcon->show();
}
void MainWindow::on_trayIcon_clicked(QSystemTrayIcon::ActivationReason reason)
{
info(DEBUGMACRO, "on_trayIcon_clicked");
if (reason == QSystemTrayIcon::Trigger)
{
info(DEBUGMACRO, "trayIcon is clicked");
if (this->isHidden())
{
this->show();
}else
{
this->hide();
}
}
}
void MainWindow::hideWindow()
{
info(DEBUGMACRO, "hideWindow()\n Hiding window");
this->hide();
}
void MainWindow::showWindow()
{
info(DEBUGMACRO, "showWindow()\n showing window");
this->show();
}
MainWindow::~MainWindow()
{
info(DEBUGMACRO, "Exiting application");
delete ui;
QCoreApplication::quit();
}
void MainWindow::quitApp()
{
QMessageBox::StandardButton reply;
QMessageBox::StandardButtons param;
QString displayText;
info(DEBUGMACRO, "Exiting application");
// saving settings
saveSettings();
if (ui->listDownload->count() != 0) // some downloads waiting
{
info(DEBUGMACRO, "Asking confirmation to exit");
// Asking for stopping or continuing
param = QMessageBox::Yes|QMessageBox::No;
if (config.autosaveCheckbox->checkState() != Qt::Checked)
{
param |= QMessageBox::Save;
displayText = tr("Clicking Save button, You can save the list of downloads\n");
}
reply = QMessageBox::question(
this,
a.applicationName(),
tr("Exiting will stop downloading, and will clear the download queue.\nDo you want to exit ?") + displayText,
param,
QMessageBox::No);
this->downloading.quit = true;
if(reply == QMessageBox::Yes)
{
info(DEBUGMACRO,"Stopping downloads");
if (this->downloading.process->state() != QProcess::NotRunning)
{
// emission of signal to downloading thread and stopping
emit (stopDownloading(this->downloading.process));
}
}else if (reply == QMessageBox::Save)
{
if (config.autosaveCheckbox->checkState() == Qt::Unchecked)
{
info(DEBUGMACRO, "Saving download list");
//saveDownloadList();
emit (stopDownloading(this->downloading.process));
}
}else if (reply == QMessageBox::No)
{
return;
}
}
QCoreApplication::quit();
}
// Close window has been clicked
void MainWindow::closeEvent (QCloseEvent *event)
{
QMessageBox msgBox;
QCheckBox *cb;
info(DEBUGMACRO, "closeEvent() => Closing window to systray");
if (!event->spontaneous() || !isVisible())
return;
if (trayIcon->isVisible() and this->settings.value("CloseCheckbox").toBool() == false)
{
cb = new QCheckBox("Don't show this again ?");
msgBox.setWindowTitle(a.applicationName());
msgBox.setInformativeText(tr("The program will keep running in the "
"system tray. To terminate the program, "
"choose <b>Quit</b> in the context menu "
"of the system tray entry."));
msgBox.addButton(QMessageBox::Ok);
msgBox.setCheckBox(cb);
msgBox.exec();
msgBox.clickedButton();
// if response is yes then loading list
if (cb->isChecked())
{
this->settings.setValue("CloseCheckbox", true);
}
hide();
//event->accept();
event->ignore();
}
}
// Populate treeview with list of files
void MainWindow::populateTree()
{
QString path;
info(DEBUGMACRO, "populateTree() =>Populating listview");
// Clear treewidget
ui->treeWidget->clear();
if (!this->connexion.server.isEmpty() and this->connexion.port > 0 and this->connexion.port < 65536)
{
// setting cursor to "Wait"
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
info(DEBUGMACRO, "Validating server");
// validating server's address
this->connexion.ipversion = validateServer(this->connexion.server);
if (this->connexion.ipversion != 0)
{
// server is validated, scanning directory
path = this->connexion.service + "/";
while (this->rescan)
{
scanDir(&this->connexion, nullptr, path);
}
}
// Restoring cursor
QGuiApplication::restoreOverrideCursor();
}
}
// Populate Listview with list of services
void MainWindow::populateList(QString server, uint port)
{
QString service;
QStringList hidden;
int i;
bool ok = false;
info(DEBUGMACRO, "populateList() => Populating list of services");
info(DEBUGMACRO, "port: " + QString::number(port) + " - server: " + server);
if (server.isEmpty())
{
ui->listWidget->clear();
ui->treeWidget->clear();
return;
}
if ((server != this->connexion.server))
{
// Determine version of Ip Protocol
info(DEBUGMACRO, "Server changed");
this->connexion.server = server;
if (port != false)
{
this->connexion.port = port;
}
// setting cursor to "Wait"
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
// verify if server is in history
this->settings.beginGroup("connexion/Servers");
if (this->settings.contains(server))
{
this->settings.beginGroup(server);
info(DEBUGMACRO, "Server configuration exists in settings");
// server is in history => setting port value
port = this->settings.value("port").toUInt();
this->connexion.ipversion = this->settings.value("ipversion").toUInt();
ui->portEdit->setText(QString::number(port));
this->connexion.port = port;
ok = true;
//display list of services
//listServices(); // TODO clear in listServices
this->settings.endGroup();
}
this->settings.endGroup();
this->settings.endGroup();
}
if (port != this->connexion.port)
{
info(DEBUGMACRO, "Port changed");
this->connexion.port = port;
ok = true;
//listServices(); // TODO clear in listServices
}
if (ok == true)
{
if (this->connexion.ipversion == 0)
{
this->connexion.ipversion = validateServer(server);
info(DEBUGMACRO, "IP version : " + QString::number(this->connexion.ipversion));
}
if (this->connexion.ipversion != 0)
{
info(DEBUGMACRO, QString::number(this->connexion.ipversion));
info(DEBUGMACRO, "server: " + server);
info(DEBUGMACRO,"Saving server configuration in settings");
// storing serverURL and port in settings
this->settings.beginGroup("connnexion");
this->settings.beginGroup("Servers");
this->settings.beginGroup(server);
this->settings.setValue("port", port);
this->settings.setValue("ipversion", this->connexion.ipversion);
this->settings.endGroup();
this->settings.endGroup();
this->settings.endGroup();
this->settings.sync();
this->downloading.server = server;
this->downloading.port = port;
this->downloading.ipversion = this->connexion.ipversion;
// storing in history of combobox
ui->khistorycombobox->addToHistory(server);
// load and display rsync services of the rsync server
}else
{
info(DEBUGMACRO, "ERROR : Unknown IP version");
}
}else
{
info(DEBUGMACRO, "Error server not changed");
}
listServices();
this->settings.beginGroup("Hidden");
this->settings.beginGroup(server);
hidden = this->settings.allKeys();
if (hidden.count() > 0)
{
for (i = 0; i < hidden.size(); i++)
{
service = hidden[i];
//TODO detect if service is already present
if (testServerPresence(service, false))
{
ui->listWidget->addItem(service + "\n\t");
}
}
}
this->settings.endGroup();
this->settings.endGroup();
// clearing listview
ui->treeWidget->clear();
QGuiApplication::restoreOverrideCursor(); //setting cursor to default
}
// Test if service is already present on the server
bool MainWindow::testServerPresence(QString service, bool askPassword)
{
QString cmd;
QStringList param;
QString line;
QString errorRsync;
QStringList v;
QProcess *myProcess;
bool returnValue = false;
int loop =0;
bool r = false;
info(DEBUGMACRO, "testServerPresence() => Test of rsync server");
info(DEBUGMACRO, "ipversion = " + QString::number(this->connexion.ipversion ));
cmd = "/usr/bin/rsync";
if (this->connexion.ipversion == 4 || this->connexion.ipversion ==6)
{
param << "-" + QString::number(this->connexion.ipversion);
}
param << "--contimeout=10" << "-nq" << "--port=" + QString::number(this->connexion.port) << + "[" + this->connexion.server + "]::" + preparePath(service);
myProcess = new QProcess(this);
myProcess->setProcessChannelMode(QProcess::MergedChannels);
myProcess->start(cmd, param);
myProcess->waitForStarted();
myProcess->write("\n");
while(myProcess->waitForReadyRead(10000))
{
while(1)
{
// line empty then buffer is empty so returning to wait new datas
line = QString::fromUtf8(myProcess->readLine());
if (line.isEmpty())
{
break;
}
if (line.contains("auth failed"))
{
if (askPassword == true and loop >=2 and r == true)
{
r = getUserPassword(&this->connexion);
loop++;
}
returnValue = true;
}
}
}
if (myProcess->exitCode() == 0)
{
returnValue = true;
}
myProcess->close();
return returnValue;
}
//list services of the rsync server
void MainWindow::listServices()
{
QString cmd = "/usr/bin/rsync";
QStringList param;
QString line;
QString errorRsync;
QStringList v;
QString service;
QString server;
QProcess *myProcess;
bool flag = false;
info(DEBUGMACRO, "listServices() => Listing services offered by server");
// clearing listwidget
ui->listWidget->clear();
if (this->connexion.ipversion == 0)
{
this->connexion.ipversion = whatIpVersion(this->connexion.server);
}
if (this->connexion.ipversion == 4)
{
param << "-" + QString::number(this->connexion.ipversion).trimmed();
server = this->connexion.server.trimmed();
}else if (this->connexion.ipversion == 6)
{
param << "-" + QString::number(this->connexion.ipversion).trimmed();
server = "[" + this->connexion.server + "]";
}else
{
error(tr("Error : Unknown IP version"));
return;
}
param << "--contimeout=20" << "--port=" + QString::number(this->connexion.port) << server + "::";
myProcess = new QProcess(this);
info(DEBUGMACRO, cmd + " " + param.join(" "));
myProcess->start(cmd, param);
// waiting for response of the server with a timeout of 10 seconds
while(myProcess->waitForReadyRead(10000))
{
info(DEBUGMACRO, "Reading");
while(!flag)
{
line = QString::fromUtf8(myProcess->readLine());
// line empty then buffer is empty so returning to wait new datas
info(DEBUGMACRO, "line: #" + line + "#");
if (line.isEmpty())
{
info(DEBUGMACRO, "line is empty");
flag = true;
break;
}
info(DEBUGMACRO, "extracting services informations");
// extracting name and comment of the service
v = line.split("\t");
v[0].replace(" ", "");
v[1].replace("\n", "");
service = v[0] + "\n\t" + v[1];
info(DEBUGMACRO, "service is:" + service);
info(DEBUGMACRO, "adding to list widget of services");
ui->listWidget->addItem(service);
}
// buffer empty go to waiting new datas
flag = false;
}
// verifying error code
testRsyncReturn(this, myProcess);
myProcess->close();
info(DEBUGMACRO, "listServices finished");
}
// connect to rsync server to get list of files
bool MainWindow::scanDir(Connexion * connexion, QTreeWidgetItem *parent, QString path)
{
QString cmd = "rsync";
QStringList param;
QStringList sizeA;
QString line;
QString size;
QString fullsize;
QString filename;
QString fileType;
QString date;
QString dirName = "";
QString user;
QProcess * myProcess;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
bool isDir = false;
bool flag = false;
bool readOk = false;
bool passwdOk = false;
int nChild = 0;
static uint looping;
QStringList dirs;
QString server = connexion->server;
bool ret = false;
info(DEBUGMACRO, "scandir() => connect to rsync server to get list of files");
ui->listWidget->setCursor(Qt::WaitCursor);
myProcess = new QProcess(this);
myProcess->setProcessChannelMode(QProcess::MergedChannels);
if (parent != nullptr)
{
nChild = parent->childCount();
}
if (nChild == 0)
{
myProcess->setProcessEnvironment(env);
info(DEBUGMACRO, "ipversion => " + QString::number(connexion->ipversion));
if(this->connexion.ipversion == 4 || this->connexion.ipversion == 6)
{
param << "-" + QString::number(connexion->ipversion).trimmed();
}
if (!connexion->user.isEmpty())
{
user = connexion->user + "@";
env.insert("RSYNC_PASSWORD", connexion->password); // Add an environment variable
}else
{
user = "anonymous@";
env.insert("RSYNC_PASSWORD", "anonymous"); // Add an environment variable
}
param << "--contimeout=20" << "--port=" + QString::number(connexion->port) << user + "[" + server + "]::" + preparePath(path) ;
info(DEBUGMACRO, cmd + " " + param.join(" "));
myProcess->start(cmd, param);
this->rescan = true;
info(DEBUGMACRO, "Waiting server response");
// waiting for response of the server with a timeout of 10 seconds
do
{
readOk = myProcess->waitForReadyRead(10000);
if (readOk)
{
while (!flag)
{
line = QString::fromUtf8(myProcess->readLine());
// line empty then buffer is empty so returning to wait new datas
if (line.isEmpty())
{
flag = true;
break;
}
if (line.contains("auth failed"))
{
myProcess->readAllStandardOutput();
getUserPassword(connexion);
if (looping <= 1)
{
this->rescan = true;
looping++;
}else
{
this->rescan = false;
looping = 0;
warning(tr("Authentication failed" ));
}
return false;
}
// extracting name, size and is dir/file
line = line.simplified();
filename = line.section(" ", 4);
if (filename != '.')
{
size = line.section(" ", 1, 1);
fullsize = size;
fullsize.remove(",");
sizeA = size.split(',');
if (sizeA.count() <= 1)
{
size = sizeA.at(0) + " " + UnitText[0] + " ";
}else
{
myProcess->setProcessEnvironment(env);
size = sizeA.at(0) + "," + sizeA.at(1).left(2) + " " + UnitText[sizeA.count()-1] + " ";
}
if (line[0] == "d")
{
isDir = true;
}else
{
isDir = false;
}
date = line.section(' ', 2, 2);
fileType = getFileType(filename);
if (!path.isEmpty())
{
if (path.endsWith('/'))
{
path.chop(1);
}
dirs = path.split('/');
dirName = dirs[dirs.size()-1];
}
addTreeItem(filename, size, fullsize, fileType, date, isDir, dirName, parent);
if (passwdOk == false and !connexion->password.isEmpty())
{
this->settings.setValue("Passwords/" + connexion->server + "/" + connexion->service + "/" + connexion->user, true);
setPassword(connexion->user, connexion->password);
this->settings.sync();
}
this->rescan = false;
}
}
flag = false;
}else
{
if (myProcess->state() == QProcess::Running)
{
if (myProcess->waitForFinished(10000) == 0)
{
warning(tr("The processus does'nt respond: ") + myProcess->errorString());
}
}
}
}while(readOk);
// buffer empty go to waiting new datas
ret = testRsyncReturn(this, myProcess);
myProcess->close();
}
ui->listWidget->setCursor(Qt::ArrowCursor);
return ret;
}
// validate address server
uint MainWindow::validateServer(QString server)
{
QString cmd;
QStringList param;
QString line;
QProcess * myProcess;
QStringList responseList;
uint ipversion;
int answerFound = 0;
int queryPos = 0;
info(DEBUGMACRO, "ValidateServer() => Validating server address");
ipversion = whatIpVersion(server);
if (ipversion == 0) // not an Ip address, perhaps a server name
{
info(DEBUGMACRO, "Digging server name");
cmd = "dig";
param << server.trimmed() ;
param << "A"; // IP V4 query
param << server.trimmed() ;
param << "AAAA"; // IP V6 query
info(DEBUGMACRO, cmd + " " + param.join(" "));
myProcess = new QProcess(this);
myProcess->start(cmd, param);
// making a dig on the server's address
while(myProcess->waitForReadyRead())
{
while (1) //!bflag)
{
line = QString::fromUtf8(myProcess->readAllStandardOutput());
info(DEBUGMACRO, line);
// line empty then buffer is empty so returning to wait new datas
if (line.isEmpty())
{
break;
}else
{
answerFound = line.indexOf(";; ANSWER SECTION:") +19;
info(DEBUGMACRO, "Position of answer line is : " + QString::number(answerFound));
if (answerFound != -1)
{
info(DEBUGMACRO, "Serching IP address");
queryPos = line.indexOf(";; Query");
info(DEBUGMACRO, "Position of Query line is : " + QString::number(queryPos));
if (answerFound < queryPos)
{
line = line.mid(answerFound, queryPos - answerFound);
info(DEBUGMACRO, "line is:\n" + line);
responseList = line.split(QRegExp("\\s+"));
info(DEBUGMACRO, "ip Address is => " + responseList.at(4));
if(responseList.at(3) == "A")
{
return 4;
}else if(responseList.at(3) == "AAAA")
{
return 6;
}
}
}
}
}
}
myProcess->close();
}
info(DEBUGMACRO, "Returning ip version: " + QString::number(ipversion));
return ipversion;
return 0;
}
// slot activated when button connection is clicked
void MainWindow::on_connectButton_clicked()
{
QString server;
uint port;
info(DEBUGMACRO, "on_connectButton_clicked() => Connexion button clicked");
port = ui->portEdit->text().toUInt();
if (port > 0 and port < 65535)
{
server = ui->khistorycombobox->currentText();
populateList(server, port);
}else
{
error(tr("Bad port number, correct it"));
ui->portEdit->clear();
ui->portEdit->setFocus();
}
}
// add parent in treeview
void MainWindow::addTreeItem(QString name, QString fileSize, QString fullsize, QString type, QString date, bool isDir=false, QString dirName="", QTreeWidgetItem *parent=nullptr)
{
QFont font;
QTreeWidgetItem *treeItem;
info(DEBUGMACRO, "addTreeItem() => adding item to treeview");
if (parent != nullptr)
{
treeItem = new QTreeWidgetItem();
}else
{
treeItem = new QTreeWidgetItem(ui->treeWidget);
}
// item is a file
if (QFile::exists(this->downloading.savePath + "/" + name) or QFile::exists(this->downloading.savePath + "/" + dirName + "/" + name))
{
QBrush b (Qt::green);
treeItem->setForeground(0, b);
treeItem->setFont(0, font);
}else if (this->settings.contains(name))
{
QBrush b (Qt::red);
treeItem->setForeground(0, b);
treeItem->setFont(0, font);
}
if (isDir == true)
{
// item is a dir
treeItem->setText(1, tr("Dir"));
treeItem->setIcon(0, QIcon::fromTheme("folder"));
}else
{
// item is a file
this->settings.beginGroup("Downloaded");
//TODO add to settings
this->settings.endGroup();
treeItem->setText(1,type);
}
treeItem->setText(0, name);
treeItem->setText(2, fileSize);
treeItem->setText(3, date);
treeItem->setText(4, fullsize);
treeItem->setTextAlignment(2, Qt::AlignRight);
if (parent != nullptr)
{
parent->addChild(treeItem);
}
}
// Slot activated when a service in the list is clicked
void MainWindow::on_listWidget_clicked()
{
info(DEBUGMACRO, "on_listWidget_clicked() => Slot activated when a service in the list is clicked");
this->connexion.service = ui->listWidget->currentItem()->text().section("\n", 0 ,0);
ui->treeWidget->clear();
preparePopulateTree();
}
void MainWindow::preparePopulateTree()
{
QString str;
QStringList logins;
info(DEBUGMACRO, "preparePopulateTree()");
this->rescan = true;
this->connexion.user = nullptr;
this->connexion.password = nullptr;
str = "Folder/" + this->connexion.server + "/" + this->connexion.service;
// if service exists in settings for this server
if (this->settings.contains(str))
{
// setting savePath from settings
this->downloading.savePath = this->settings.value(str).toString();
}
populateTree();
}
// get password and user login
// if object = false ==> searching from connexion object
// else searching from downloading object
bool MainWindow::getUserPassword(Connexion * object)
{
QStringList logins;
QString login = "";
QString password = "";
QString server;
QString service;
int c;
info(DEBUGMACRO, "getUserPassword() => get password and user login");
bool returnValue = false;
bool ok = false;
server = object->server;
service = object->service;
object->user = "";
object->password = "";
this->settings.beginGroup("Passwords/" + server + "/" + service);
logins = this->settings.allKeys();
c = logins.count();
if ( c != 1)
{
//choose login in case of multiples logins
login = QInputDialog::getItem(this,
"RsincUI",
tr("Select the user you want to connect with or enter a new one"),
logins,
0,
true,
&ok,
Qt::Popup,
Qt::ImhNoPredictiveText
);
if (ok and !login.isEmpty())
{
if (!logins.contains(login))
{
password = QInputDialog::getText(this,
a.applicationName() + tr(" Request"),
tr("Enter password"), QLineEdit::Password,
"", &ok, Qt::Popup,
Qt::ImhNoPredictiveText);
if (!ok or password.isEmpty())
{
password = "";
returnValue = false;
}
}else
{
password = getPassword(login);
returnValue = true;
}
object->user = login;
object->password = password;
}
}else
{
object->user = logins.at(0);
object->password = getPassword(object->user);
returnValue = true;
}
this->settings.endGroup();
return returnValue;
}
//Slot activated when a file is clicked in the treeview
void MainWindow::on_treeWidget_itemClicked(QTreeWidgetItem *item, bool downloadDir)
{
QTreeWidgetItem * itemR;
QString path;
QString str;
QMessageBox::StandardButton reply;
int sizeFromRsync;
bool ret = false;
info(DEBUGMACRO, "on_treeWidget_itemClicked() => Slot activated when a file is clicked in the treeview");
if (this->treeviewClicked == true)
{
return;
}
this->treeviewClicked = true;
itemR = item;
this->rescan = true;
// assembling path from treewidget
path = item->text(0);
sizeFromRsync = item->text(4).toUInt();
while(itemR->parent() != NULL)
{
itemR = itemR->parent();
// concatening parent to path
path.prepend(itemR->text(0) + "/");
};
cout << item->text(1).toStdString() <<endl;
if (item->text(1) != tr("Dir") or downloadDir == true)
{
// exists saving path in settings ?
str = "Folder/" + this->connexion.server + "/" + this->connexion.service;
if(!this->settings.contains(str))
{
info(DEBUGMACRO, "saving path do not exists, asking for it");
// saving path do not exists, asking for it
if(!on_DefaultSaveFolder_triggered())
{
info(DEBUGMACRO, "no directory selectioned, ignoring download request");
this->treeviewClicked = false;
return;
}
}else
{
this->downloading.savePath = this->settings.value(str).toString();
info(DEBUGMACRO, "saving folder : " + this->downloading.savePath);
}
// Item is a file
// searching if file exists in savepath
info(DEBUGMACRO, "searching if file exists in savepath");
if (QFile::exists(this->downloading.savePath + "/" + path))
{
info(DEBUGMACRO, "file exists in savepath");
QFileInfo fileinfo(this->downloading.savePath + "/" + path);
if (fileinfo.size() < sizeFromRsync)
{
reply = QMessageBox::question(
this,
a.applicationName(),
tr("File is partially downloaded. Do you want to resume download ? if no, the file will be deleted from destination directory"),
QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel,
QMessageBox::Cancel);
if (reply == QMessageBox::Cancel)
{
this->treeviewClicked = false;
return;
}else if(reply == QMessageBox::No)
{
QFile::remove(this->downloading.savePath + "/" + path);
this->treeviewClicked = false;
return;
}
}else
{
reply = QMessageBox::question(
this,
a.applicationName(),
tr("File is already downloaded. Do you want to reload it ? The old file will be deleted"),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No);
if (reply == QMessageBox::No)
{
this->treeviewClicked = false;
return;
}else
{
QFile::remove(this->downloading.savePath + "/" + path);
}
}
}
if(ui->listDownload->findItems(path, Qt::MatchStartsWith).empty())
{
// is there a downloading process ?
info(DEBUGMACRO, "is there a downloading process ?");
if (this->downloading.process == nullptr)
{
// no downloading process launching it
info(DEBUGMACRO, "no downloading process launching it");
this->downloading.path = path;
this->downloading.server = this->connexion.server;
this->downloading.port = this->connexion.port;
this->downloading.service = this->connexion.service;
startDownloading();
// wait 1 second to process start
//sleep(1);
}
info(DEBUGMACRO, "Downloading process exist, adding download to download list");
// Adding download to download list
str = path + " => " + this->connexion.server + "/" + this->connexion.service;
ui->listDownload->addItem(str);
}else
{
warning(tr("File is already downloading" ));
}
}else
{
//Item is a Directory
info(DEBUGMACRO, "Item is a Directory");
if (item->isExpanded() == false)
{
info(DEBUGMACRO, "Item is not expanded");
//if (this->rescan)
//{
info(DEBUGMACRO, "Re-scanning path: " + this->connexion.service + "/" + path +"/");
ret = scanDir(&this->connexion, item, this->connexion.service + "/" + path +"/");
if(!ret)
{
item->setExpanded(true);
}
//}
}else
{
item->setExpanded(false);
}
}
if (config.autosaveCheckbox->checkState() == Qt::Checked)
{
info(DEBUGMACRO, "Saving download list");
saveDownloadList();
}
info(DEBUGMACRO, "Exiting");
this->treeviewClicked = false;
}
// Launch the thread which download the file
void MainWindow::startDownloading()
{
info(DEBUGMACRO, "startDownloading() => Launch the thread which download the file");
ui->progressBar->setValue(0);
ui->progressBar->show();
//getUserPassword();
//QtConcurrent::run(&this->downloadO, &downloadFile::download, this);
this->download();
this->trayIcon->showMessage(a.applicationName(), tr("Starting downloading\n") + this->downloading.path, QSystemTrayIcon::Information);
}
// Slot stopping download
void MainWindow::stoppingDownload()
{
info(DEBUGMACRO, "stoppingDownload");
emit (stopDownloading(this->downloading.process));
}
// when download is finished, launch download of next file in queue
void MainWindow::downloadFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
QString path;
QString str;
int pos;
QString aborted = tr("finished");
QMessageBox::StandardButton reply;
bool retry = false;
info(DEBUGMACRO, "downloadFinished() => when download is finished, launch download of next file in queue");
info(DEBUGMACRO, "Download finished with status " + QString::number(exitCode));
// test if process crashed
if (exitStatus == QProcess::CrashExit)
{
warning(tr("Rsync process crashed"));
}
//test result code of command (if 20 then command stopped by user)
if (exitCode != 0 and this->stopDlAsked != true)
{
if (exitCode == 20)
{
if (this->exiting)
{
return;
}
aborted = tr("stopped by user");
}else if (exitCode == 5) // password asked
{
getUserPassword(&this->downloading);
retry = true;
}
// displaying warning with exit code
reply = QMessageBox::warning(
this,
a.applicationName(),
tr(rsyncErrorStrings[exitCode].toStdString().c_str()) + tr("\nDo you want to retry?"),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::Yes);
if (reply == QMessageBox::Yes)
{
retry = true;
}
}
this->stopDlAsked = false;
this->trayIcon->showMessage(a.applicationName(), tr("Download ") + aborted + "\n" + this->downloading.path, QSystemTrayIcon::Information);
// disconnecting signals to slots
disconnect(this->downloading.process, 0, 0, 0);
// reset variables and window, close process
this->downloading.process->close();
ui->progressBar->hide();
if (retry == false)
{
QString filename = ui->listDownload->item(0)->text();
pos = filename.contains(" => ");
filename.resize(pos);
delete ui->listDownload->takeItem(0);
}
this->downloading.clear();
// Some downloads staying in queue
if (ui->listDownload->count() != 0)
{
// initializing download
path = ui->listDownload->item(0)->text();
pos = path.lastIndexOf("/");
this->downloading.service = path.midRef(pos+1).toString();
path.resize(pos);
pos = path.lastIndexOf(" => ");
this->downloading.server = path.midRef(pos+4).toString();
path.resize(pos);
this->downloading.port = this->settings.value("connexion/Servers/" + this->downloading.server + "/port").toInt();
this->downloading.path = path;
//getUserPassword(true);
testServerPresence(this->downloading.service, true);
// savepath exists in settings ?
str = "Folder/" + this->downloading.server + "/" + this->downloading.service;
if (this->settings.contains(str))
{
// setting savepath from saved settings
this->downloading.savePath = this->settings.value(str).toString();
sleep(2);
startDownloading();
}else
{
// no save path
if(!on_DefaultSaveFolder_triggered())
{
cout << "Error no save path so removing download";
return;
}
}
}
if(config.autosaveCheckbox->checkState() == Qt::Checked and this->downloading.quit == false)
{
saveDownloadList();
}
}
// Slot activated when a line is clicked in queue list
void MainWindow::on_listDownload_itemClicked(QListWidgetItem *item)
{
QMessageBox::StandardButton reply;
info(DEBUGMACRO, "on_listDownload_itemClicked() => Slot activated when a line is clicked in queue list");
if (item->listWidget()->row(item) == 0)
{
// first line clicked on download list
reply = QMessageBox::question(
this,
a.applicationName(),
tr("Do you want to stop downloading and delete this file from download queue ?"),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No);
if (reply == QMessageBox::Yes)
{
// stopping download
this->stopDlAsked = true;
emit (stopDownloading(this->downloading.process));
}
}else
{
// not first line on download list
reply = QMessageBox::question(
this,
a.applicationName(),
tr("Do you want to delete this file from download queue ?"),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No);
if (reply == QMessageBox::Yes)
{
// removing line from download list
ui->listDownload->removeItemWidget(item);
delete item;
}
}
if (config.autosaveCheckbox->checkState() == Qt::Checked)
{
// autosave acivated,so saving download list
saveDownloadList();
}
}
// load settings
void MainWindow::loadSettings()
{
info(DEBUGMACRO, "loadSettings() => restoring geometry and state of window and widgets");
// restoring geometry and state of window and widgets
this->restoreGeometry(this->settings.value("window/geometry").toByteArray());
this->restoreState(this->settings.value("window/state").toByteArray());
ui->treeWidget->header()->restoreState(this->settings.value("treeWidget/state").toByteArray());
ui->splitter->restoreState(this->settings.value("splitter/state").toByteArray());
ui->splitter_2->restoreState(this->settings.value("splitter2/state").toByteArray());
ui->toolBar->setToolButtonStyle((Qt::ToolButtonStyle)this->settings.value("toolbar/state").toInt());
if (this->settings.value("Autosave").toInt() == Qt::Checked)
{
this->config.autosaveCheckbox->setChecked(true);
}
// loading connexion settings
// loading servers history
info(DEBUGMACRO, "Restoring server combobox history");
this->settings.beginGroup("connexion/Servers");
QStringList servers = this->settings.allKeys();
this->settings.endGroup();
ui->khistorycombobox->insertItems(1,this->settings.value("kHistoryComboBox").toStringList());
// loading save path
this->downloading.savePath = this->settings.value("Folder").toString();
// loading bandwidth limit
this->connexion.bandwidthLimit = this->settings.value("bandwidthlimit").toUInt();
this->connexion.bandwidthLimitUnit = this->settings.value("bandwidthlimitunit").toInt();
}
// save settings
void MainWindow::saveSettings()
{
info(DEBUGMACRO, "saveSettings() => Saving settings");
this->settings.setValue("window/geometry", saveGeometry());
this->settings.setValue("window/state", saveState());
this->settings.setValue("treeWidget/state", ui->treeWidget->header()->saveState());
this->settings.setValue("splitter/state", ui->splitter->saveState());
this->settings.setValue("splitter2/state", ui->splitter_2->saveState());
this->settings.setValue("connexion/lastServer", this->connexion.server);
this->settings.setValue("connexion/lastPort", QString::number(this->connexion.port));
this->settings.setValue("connexion/lastIpversion", QString::number(this->connexion.ipversion));
this->settings.setValue("toolbar/state", ui->toolBar->toolButtonStyle());
this->settings.setValue("Autosave", this->config.autosaveCheckbox->checkState());
this->settings.setValue("kHistoryComboBox", ui->khistorycombobox->historyItems());
this->settings.sync();
}
// About
void MainWindow::on_actionAbout_triggered()
{
info(DEBUGMACRO, "on_actionAbout_triggered() => display about informations");
aboutDialog.show();
}
// About QT
void MainWindow::on_actionAbout_Qt_triggered()
{
info(DEBUGMACRO, "on_actionAbout_Qt_triggered() => display QT about information");
QMessageBox::aboutQt(this);
}
// Activated when menu "change folder" is clicked
bool MainWindow::on_DefaultSaveFolder_triggered()
{
QFileDialog dialog;
QString folder;
QString path;
QString dir;
info(DEBUGMACRO, "on_DefaultSaveFolder_triggered() => Activated when menu 'change folder' is clicked");
// if service not selected display a message
if (this->connexion.service.isEmpty() or this->connexion.server.isEmpty())
{
warning(tr("You need to select a service before you can select a folder"));
return false;
}
// Asking for directory to save files
folder = "Folder/" + this->connexion.server + "/" + this->connexion.service;
path = this->settings.value(folder).toString();
info(DEBUGMACRO, "Path = " + path);
dir = dialog.getExistingDirectory(this, tr("Choose folder where to save file"), path, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!dir.isEmpty())
{
this->downloading.savePath = dir;
// saving save path in settings
this->settings.setValue(folder, this->downloading.savePath);
this->settings.sync();
}else
{
return false;
}
return true;
}
// Activated when menu "settings" is clicked
void MainWindow::on_action_Settings_triggered()
{
info(DEBUGMACRO, "on_action_Settings_triggered() => Activated when menu 'settings' is clicked");
config.UnitCombobox->setCurrentIndex(this->connexion.bandwidthLimitUnit);
config.spinBox->setValue(this->connexion.bandwidthLimit);
Configuration.show();
}
// Acivated when "Ok" is clicked in Configuration window
void MainWindow::on_buttonBox_accepted()
{
QString unit;
QString bw;
info(DEBUGMACRO, "on_buttonBox_accepted() => Acivated when 'Ok' is clicked in Configuration window");
bw = config.spinBox->text();
if (bw.toInt() == 0)
{
// bandwidth = 0
this->connexion.bandwidthLimit = 0;
this->connexion.bandwidthLimitUnit = 0;
}else
{
this->connexion.bandwidthLimit = config.spinBox->value();
this->connexion.bandwidthLimitUnit = config.UnitCombobox->currentIndex();
}
this->settings.setValue("bandwidthlimit", this->connexion.bandwidthLimit);
this->settings.setValue("bandwidthlimitunit", this->connexion.bandwidthLimitUnit);
this->settings.setValue("Autosave", this->config.autosaveCheckbox->checkState());
this->settings.sync();
Configuration.hide();
}
// Saving download list
void MainWindow::saveDownloadList()
{
int nRows;
info(DEBUGMACRO, "saveDownloadList() => Saving download list");
// remove list of downloads
this->settings.remove("Downloads/");
// Saving list of current downloads
nRows = ui->listDownload->count();
this->settings.beginGroup("Downloads");
this->settings.setValue("rows", nRows);
for (int i = 0; i < nRows; i++)
{
this->settings.setValue(QString::number(i), ui->listDownload->item(i)->text());
}
this->settings.endGroup();
this->settings.sync();
}
// Loading download list
void MainWindow::loadDownloadList()
{
QString path;
QString str;
int pos;
info(DEBUGMACRO, "loadDownloadList() => loading download list");
this->settings.sync();
this->settings.beginGroup("Downloads");
int size = this->settings.value("rows").toInt();
for (int i = 0; i < size; ++i)
{
ui->listDownload->addItem(this->settings.value(QString::number(i)).toString());
}
this->settings.endGroup();
path = ui->listDownload->item(0)->text();
pos = path.lastIndexOf("/");
this->downloading.service = path.midRef(pos+1).toString();
path.resize(pos);
pos = path.lastIndexOf(" => ");
this->downloading.server = path.midRef(pos+4).toString();
this->downloading.port = this->settings.value("connexion/Servers/" + this->downloading.server+ "/port").toInt();
path.resize(pos);
this->downloading.path = path;
str = "Folder/" + this->downloading.server + "/" + this->downloading.service;
if (this->settings.contains(str))
{
this->downloading.savePath = this->settings.value(str).toString();
}
startDownloading();
}
// Deleting download list
void MainWindow::deleteDownloadList()
{
info(DEBUGMACRO, "deleteDownloadList() => Deleting existing download list");
this->settings.remove("Downloads");
}
// clear object downloading
void Connexion::clear()
{
info(DEBUGMACRO, "clear() => clear object 'downloading'");
this->path.clear();
this->server.clear();
this->savePath.clear();
this->service.clear();
this->user.clear();
this->password.clear();
this->port = 0;
this->process = nullptr;
this->quit = false;
}
// Context menu of file list clicked
void MainWindow::on_actionDownload_triggered()
{
// action made in qt-designer and added in init function.
QTreeWidgetItem *item;
item = ui->treeWidget->currentItem();
on_treeWidget_itemClicked(item, true);
}
// Change toolbar style
void MainWindow::on_comboBox_currentIndexChanged(int index)
{
info(DEBUGMACRO, "on_comboBox_currentIndexChanged()");
ui->toolBar->setToolButtonStyle((Qt::ToolButtonStyle)index);
}
void MainWindow::on_actionExit_triggered()
{
info(DEBUGMACRO, "on_actionExit_triggered() => slot activated when button exit clicked");
this->exiting = true;
quitApp();
}
void MainWindow::setDlSpeed(QString speed)
{
info(DEBUGMACRO, "setDlSpeed(= => setting download speed");
speed.squeeze();
}
void MainWindow::on_actionHiddenService_triggered()
{
QInputDialog hiddenFolderDialog;
info(DEBUGMACRO, "on_actionHiddenService_triggered() => activated when the 'hidden service' button");
bool ok;
QString text = QInputDialog::getText(this, a.applicationName() + tr(" Request"),
tr("Hidden service name"), QLineEdit::Normal,
"test", &ok);
if (ok && !text.isEmpty())
{
this->connexion.service = text;
this->settings.setValue("Hidden/" + this->connexion.server + '/' + text, true);
ui->listWidget->addItem(text + "\n\t");
preparePopulateTree();
}
}
void MainWindow::on_treeWidget_itemDoubleClicked(QTreeWidgetItem *item, int column)
{
(void) item;
(void) column;
}
void MainWindow::on_treeWidget_doubleClicked(const QModelIndex &index)
{
(void) index;
}
void MainWindow::on_actionPause_downloads_triggered()
{
quint64 processID = this->downloading.process->processId();
QString fileStr = "/proc/" + QString::number(processID) + "/task/" + QString::number(processID) + "/children";
static QMessageBox msgBox;
//msgBox = new QMessageBox(this);
msgBox.setIcon( QMessageBox::Warning );
msgBox.setText("Download suspended");
//QPushButton *btnCancel = msgBox->addButton( "Cancel", QMessageBox::RejectRole );
//msgBox.setAttribute(Qt::WA_DeleteOnClose); // delete pointer after close
msgBox.setModal(false);
info (DEBUGMACRO, "downloading state : " + QString::fromUtf8((this->downloading.paused==0)?"running":"paused"));
info (DEBUGMACRO, "Downloading process ID : " + QString::number(processID));
info (DEBUGMACRO, fileStr);
QFile file(fileStr);
if(!file.open(QIODevice::ReadOnly))
{
QMessageBox::information(0, "error", file.errorString());
}
QTextStream in(&file);
QString children = in.readLine();
info (DEBUGMACRO, "Child process is : " + children);
if (this->downloading.paused)
{
info(DEBUGMACRO, "resuming Download");
if (kill(children.toLongLong(), SIGCONT))
{
info(DEBUGMACRO, "resuming failed : " + QString::number(errno));
}
if (msgBox.isVisible())
{
msgBox.close();
}
this->downloading.paused = false;
}else
{
info(DEBUGMACRO, "pausing Download");
if (kill(children.toLongLong(), SIGTSTP))
{
info(DEBUGMACRO, "resuming failed : " + QString::number(errno));
}
msgBox.show();
this->downloading.paused = true;
}
}