#include "mainwindow.h" #include using namespace std; bool display = false; //extern QDialog Configuration; extern Ui::Configuration config; extern bool testRsyncReturn(QProcess *); QMap 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) { QAbstractButton * reply; QMessageBox msgBox; ui->setupUi(this); QCoreApplication::setOrganizationName("RsyncUI"); QCoreApplication::setApplicationName("RsyncUI"); // context menu for treewidget (list of files) ui->treeWidget->addAction(ui->actionDownload); // init configuration window config.setupUi(&Configuration); // init of About this->about.description = tr("Client for rsync server\n\nYou click on file to enqueue it, and RyncUI Download one file a time"); // connectors connect(this, &MainWindow::progressSignal, ui->progressBar, &QProgressBar::setValue); //connect(this, &MainWindow::errorSignal, this, &MainWindow::downloadingErrorSlot); connect(this, &MainWindow::stopDownloading, this, &MainWindow::cancelled); connect(config.buttonBox, SIGNAL(accepted()), this, SLOT(on_buttonBox_accepted())); connect(config.comboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &MainWindow::on_comboBox_currentIndexChanged); // 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")} ); // set header of columns of tree widget // if last server exists in settings if (this->settings.contains("connexion/lastServer")) { // set window to precedent server/port configuration ui->portEdit->setText(this->settings.value("connexion/port").toString()); ui->khistorycombobox->setCurrentText(this->settings.value("connexion/lastServer").toString()); }else { ui->portEdit->text() = QString::number(this->connexion.port); ui->khistorycombobox->clear(); } // setting arrowcursor for treeWidget, listWidget and listDownload to arrow ui->treeWidget->setCursor(Qt::ArrowCursor); ui->listWidget->setCursor(Qt::ArrowCursor); ui->listDownload->setCursor(Qt::ArrowCursor); // Hiding progress bar ui->progressBar->hide(); loadSettings(); //setting configuration window config.comboBox->setCurrentIndex(ui->toolBar->toolButtonStyle()); // setting combobox to saved settings //setting unit of bandwidth limit config.UnitCombobox->addItems({tr("KB"), tr("MB"), tr("GB"), tr("TB"), tr("PB")}); //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("RsyncUI"); msgBox.setInformativeText(tr("A list of interrupted downloads exists, do you want to continue downloading ? if not the list will be cleared" )); QPushButton *yes = msgBox.addButton(QMessageBox::Yes); msgBox.addButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::Yes); msgBox.exec(); reply = msgBox.clickedButton(); // if response is yes then loading list if(reply == yes) { loadDownloadList(); } } // load list of services populateList(); } MainWindow::~MainWindow() { delete ui; } // Close window has been clicked void MainWindow::closeEvent (QCloseEvent *event) { QMessageBox::StandardButton reply; QMessageBox::StandardButtons param; QString displayText; // saving settings saveSettings(); if (ui->listDownload->count() != 0) // some downloads waiting { // 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, "RsyncUI", tr("Exiting will stop downloading, and will clear the download queue.\nDo you want to exit ?") + displayText, param, QMessageBox::No); if (reply == QMessageBox::No) { // continuing event->ignore(); return; }else if(reply == QMessageBox::Yes) { // emission of signal to downloading thread and stopping emit (stopDownloading(this->downloading.process)); }else { saveDownloadList(); } } event->accept(); } // Populate treeview with list of files void MainWindow::populateTree(QTreeWidgetItem * parent) { QString path; // 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); // validating server's address if (validateServer(this->connexion.server)) { // server is validated path = ui->listWidget->currentItem()->text().section('\n', 0, 0) + "/"; scanDir(this->connexion.server, this->connexion.port, parent, path); } // Restoring cursor QGuiApplication::restoreOverrideCursor(); } } // Populate Listview with list of services void MainWindow::populateList() { QString server; int port; server = ui->khistorycombobox->currentText(); port = ui->portEdit->text().toUInt(); if ((server != this->connexion.server) or (port != this->connexion.port)) { // clearing listwidget ui->listWidget->clear(); this->connexion.server = server; this->connexion.port = port; // setting cursor to "Wait" QGuiApplication::setOverrideCursor(Qt::WaitCursor); // verify if server is in history this->settings.beginGroup("connexion/server"); if (this->settings.contains(server) and this->connexion.comboboxChanged) { // server is in history => setting port value port = this->settings.value(server).toUInt(); ui->portEdit->setText(QString::number(port)); this->connexion.port = port; //display list of services listServices(); }else { if (!server.isEmpty() and (port > 0 and port < 65536)) { if (validateServer(server)) { cout << server.toStdString() << endl; // storing serverURL and port in settings this->settings.setValue(server, port); this->settings.sync(); this->downloading.server = server; // storing in history of combobox ui->khistorycombobox->addToHistory(server); // load and display rsync services of the rsync server listServices(); } } } this->settings.endGroup(); QGuiApplication::restoreOverrideCursor(); //setting cursor to default } } //list services of the rsync server void MainWindow::listServices() { QString cmd; QStringList param; QString line; QString errorRsync; QStringList v; QString service; QProcess *myProcess; bool flag = false; cmd = "/usr/bin/rsync"; param << "--contimeout=10" << "--port=" + QString::number(this->connexion.port) << this->connexion.server + "::"; myProcess = new QProcess(this); myProcess->start(cmd, param); // waiting for response of the server with a timeout of 10 seconds while(myProcess->waitForReadyRead(10000)) { 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; } // 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]; // adding to list of services ui->listWidget->addItem(service); } // buffer empty go to waiting new datas flag =false; } // verifying error code testRsyncReturn(myProcess); } // connect to rsync server to get list of files void MainWindow::scanDir(QString server, int portN, QTreeWidgetItem *parent, QString path) { QString cmd; QStringList param; QString line; QString size; QString filename; QTreeWidgetItem * item; QProcess * myProcess; bool isDir = false; bool flag = false; cmd = "rsync"; param << "--contimeout=10" << "--port=" + QString::number(portN) << server + "::" + path; myProcess = new QProcess(this); myProcess->start(cmd, param); // waiting for response of the server with a timeout of 10 seconds while(myProcess->waitForReadyRead(100000)) { 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; } // extracting name, size and is dir/file line = line.simplified(); size = line.section(" ", 1, 1); filename = line.section(" ", 4); if (filename != '.') { if (line[0] == "d") { isDir = true; }else { isDir = false; } if (parent != NULL) { //adding item to tree item = addTreeChild(parent, filename, size, isDir); }else { //adding item to tree (as directory) item = addTreeRoot(filename, size, isDir); } } } flag = false; } // buffer empty go to waiting new datas testRsyncReturn(myProcess); } // Verify if server address is IP address bool MainWindow::isIpAddress(QString server) { QStringList r; int elementN; bool ok; r = server.split('.'); if (r.size() == 4) { for (auto element : r) { elementN = element.toInt(&ok); if (elementN < 0 or elementN > 255 or ok == false) { return false; } } return true; }else { return false; } } // validate address server bool MainWindow::validateServer(QString server) { QString cmd; QStringList param; QString line; QProcess * myProcess; bool flag = false; bool bflag = false; cmd = "dig"; param << server; myProcess = new QProcess(this); myProcess->start(cmd, param); // maiking a dig on the server's address while(myProcess->waitForReadyRead()) { while (!bflag) { line = QString::fromUtf8(myProcess->readAllStandardOutput()); // line empty then buffer is empty so returning to wait new datas if (line.isEmpty()) { bflag = true; break; }else if (line.indexOf(";; ANSWER SECTION:") != -1) { flag = true; } } bflag = false; } //testRsyncReturn(myProcess); if ( flag == false) { //server's address is not valid testing if ip address is valid flag = isIpAddress(server); } if ( flag == false) { // server-s address not valid QMessageBox::warning( this, "RsyncUI", tr("server does not exists" ) ); } return flag; } // slot activated when combobox is changed void MainWindow::on_khistorycombobox_currentIndexChanged(int i) { this->connexion.comboboxChanged = true; populateList(); } // slot activated when button connection is clicked void MainWindow::on_connectButton_clicked() { populateList(); } // add a dir in treeview QTreeWidgetItem * MainWindow::addTreeRoot(QString name, QString fileSize, bool isDir) { QTreeWidgetItem *treeItem = new QTreeWidgetItem(ui->treeWidget); if (isDir == true) { // item is a dir treeItem->setText(1, tr("Dir")); }else { // item is a file treeItem->setText(1,tr("File")); } treeItem->setText(0, name); treeItem->setText(2, fileSize); return treeItem; } // add a file in treeview QTreeWidgetItem * MainWindow::addTreeChild(QTreeWidgetItem *parent, QString name, QString fileSize, bool isDir) { QTreeWidgetItem *treeItem = new QTreeWidgetItem(); if (isDir == true) { // item is a dir treeItem->setText(1, tr("Dir")); }else { // item is a file treeItem->setText(1,("File")); } treeItem->setText(0, name); treeItem->setText(2, fileSize); // QTreeWidgetItem::addChild(QTreeWidgetItem * child) parent->addChild(treeItem); return treeItem; } // Slot acivated when a service in the list is clicked void MainWindow::on_listWidget_clicked() { QString str; this->connexion.service = ui->listWidget->currentItem()->text().section("\n", 0 ,0); 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(NULL); } //Slot activated when a file is clicked in the treeview void MainWindow::on_treeWidget_itemClicked(QTreeWidgetItem *item, bool downloadDir) { QFuture future; QFileDialog dialog; QTreeWidgetItem * itemR; QString path; QString str; itemR = item; // assembling path from treewidget path = item->text(0); while(itemR->parent() != NULL) { itemR = itemR->parent(); // concatening parent to path path.prepend(itemR->text(0) + "/"); }; if (item->text(1) == tr("File") or downloadDir == true) { // Item is a file if(ui->listDownload->findItems(path, Qt::MatchStartsWith).empty()) { // exists saving path in settings ? str = "Folder/" + this->connexion.server + "/" + this->downloading.service; if(!this->settings.contains(str)) { // saving path do not exists, asking for it if(!on_DefaultSaveFolder_triggered()) { cout << "no directory selectioned, ignoring download request"; return; } } // is there a downloading process ? if (this->downloading.process == nullptr) { // no downloading process launching it this->downloading.path = path; this->downloading.server = this->connexion.server; this->downloading.service = this->connexion.service; startDownloading(); // wit 1 second to process start //sleep(1); } // Adding download in download list str = path + " => " + this->connexion.server + "/" + this->connexion.service; ui->listDownload->addItem(str); } }else { //Item is a Directory scanDir(this->connexion.server, this->connexion.port, item, this->connexion.service + "/" + path +"/"); item->setExpanded(true); } if (config.autosaveCheckbox->checkState() == Qt::Checked) { saveDownloadList(); } } // Launch the thread which download the file void MainWindow::startDownloading() { ui->progressBar->setValue(0); ui->progressBar->show(); //QtConcurrent::run(&this->downloadO, &downloadFile::download, this); download(); } // Slot stopping download void MainWindow::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; // test if process crashed if (exitStatus == QProcess::CrashExit) { QMessageBox::warning( NULL, "RsyncUI", tr("Rsync process crashed")); } //test result code of command (if 20 then command stopped by user) if (exitCode != 0 and exitCode != 20) { // displaying warning with exit code QMessageBox::warning( NULL, "RsyncUI", rsyncErrorStrings[exitCode]); } // disconnecting signals to slots disconnect(this->downloading.process, 0, 0, 0); // reset variables and window this->downloading.process= nullptr; ui->progressBar->hide(); delete ui->listDownload->takeItem(0); this->downloading.clear(); // Some downloads staying in queue if (ui->listDownload->count() != 0) { // autosave is activated if (config.autosaveCheckbox->checkState() == Qt::Checked) { // saving download list saveDownloadList(); } // 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.path = path; // save path 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(); startDownloading(); }else { // no save path if(!on_DefaultSaveFolder_triggered()) { cout << "Error no save path so deleting download"; //downloadFinished(); return; } } } } // Slot activated when a line is clicked in queue list void MainWindow::on_listDownload_itemClicked(QListWidgetItem *item) { QMessageBox::StandardButton reply; if (item->listWidget()->row(item) == 0) { // first line clicked on download list reply = QMessageBox::question( this, "RsyncUI", 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 emit (stopDownloading(this->downloading.process)); } }else { // not first line on download list reply = QMessageBox::question( this, "RsyncUI", 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() { // 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 this->settings.beginGroup("connexion/server"); QStringList servers = this->settings.allKeys(); this->settings.endGroup(); for( const QString &server : servers ) { ui->khistorycombobox->addToHistory(server); } // 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() { 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("toolbar/state", ui->toolBar->toolButtonStyle()); this->settings.setValue("Autosave", this->config.autosaveCheckbox->checkState()); this->settings.sync(); } // About void MainWindow::on_actionAbout_triggered() { QString text = this->about.description + "\n\n" + tr("Version") + ": " + this->about.version + "\n" + tr("Licence") + ": " + this->about.licence + "\n" + tr("Author") + ": " + this->about.author + "\n" + tr("EMail") + ": " + this->about.email + "\n" + tr("Source code") + ": " + this->about.git; QMessageBox::about(this, this->about.title, text); } // About QT void MainWindow::on_actionAbout_Qt_triggered() { QMessageBox::aboutQt(this); } // Activated when menu "change folder" is clicked bool MainWindow::on_DefaultSaveFolder_triggered() { QFileDialog dialog; QString folder; QString path; // if service not selected display a message if (this->connexion.service.isEmpty()) { QMessageBox::warning( NULL, "RsyncUI", tr("Since the save path is linked to service, you need to select a service before you can select a folder")); return false; } // Asking for directory to save files path = dialog.getExistingDirectory(this, tr("Choose folder where to save file"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!path.isEmpty()) { this->downloading.savePath = path; if (!this->connexion.service.isEmpty() and !this->connexion.server.isEmpty()) { // saving save path in settings folder = "Folder/" + this->connexion.server + "/" + this->connexion.service; 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() { 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; 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; // 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; 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(); 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(); } // clear object downloading void Downloading::clear() { this->path.clear(); this->server.clear(); this->savePath.clear(); this->service.clear(); } // 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); } /*void MainWindow::downloadingErrorSlot(QString errorString) { QMessageBox::warning( this, "RsyncUI", errorString, QMessageBox::Ok, QMessageBox::Ok); } */ // Chnage toolbar style void MainWindow::on_comboBox_currentIndexChanged(int index) { ui->toolBar->setToolButtonStyle((Qt::ToolButtonStyle)index); }