#include "mainwindow.h" #include #include using namespace std; bool display = false; //extern QDialog Configuration; extern Ui::Configuration config; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { QAbstractButton * reply; QMessageBox msgBox; ui->setupUi(this); QCoreApplication::setOrganizationName("RsyncUI"); QCoreApplication::setApplicationName("RsyncUI"); // init shortcut loadSettings(); config.setupUi(&Configuration); config.UnitCombobox->addItems({tr("Byte"), tr("KB"), tr("MB"), tr("GB"), tr("TB"), tr("PB")}); // 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(&downloadO, &downloadFile::progressSignal, ui->progressBar, &QProgressBar::setValue); connect(&downloadO, &downloadFile::finishedSignal, this, &MainWindow::downloadFinished); connect(this, &MainWindow::stopDownloading, &downloadO, &downloadFile::cancelled); connect(config.buttonBox, SIGNAL(accepted()), this, SLOT(on_buttonBox_accepted())); // init of widgets ui->ktreewidgetsearchline->setTreeWidget(ui->treeWidget); ui->ktreewidgetsearchline->setCaseSensitivity(Qt::CaseInsensitive); ui->treeWidget->setHeaderLabels({tr("Path"), tr("Type"), tr("Size")} ); config.comboBox->setCurrentIndex(ui->toolBar->toolButtonStyle()); if (this->settings.contains("connexion/lastServer")) { 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 ui->treeWidget->setCursor(Qt::ArrowCursor); ui->listWidget->setCursor(Qt::ArrowCursor); ui->listDownload->setCursor(Qt::ArrowCursor); // Hiding progress bar ui->progressBar->hide(); if (this->settings.value("Downloads/rows").toInt() != 0) { this->settings.endArray(); msgBox.setWindowTitle("RsyncUI"); msgBox.setInformativeText(tr("A list of interrupted downloads exists, do you want to continue downloading ? or you can delete the list" )); QPushButton *remove = msgBox.addButton(tr("Remove"), QMessageBox::ActionRole); QPushButton *yes = msgBox.addButton(QMessageBox::Yes); msgBox.addButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); msgBox.exec(); reply = msgBox.clickedButton(); if(reply == yes) { loadDownloadList(); }else if (reply == remove) { this->settings.remove("Downloads"); } } populateList(); } MainWindow::~MainWindow() { delete ui; } // Closing window has been clicked void MainWindow::closeEvent (QCloseEvent *event) { QMessageBox::StandardButton reply; // saving settings saveSettings(); if (ui->listDownload->count() != 0) // some downloads waiting { // Asking for stopping or continuing reply = QMessageBox::question( this, "RsyncUI", tr("Exiting will stop downloading, and will clear the download queue.\nYou can save the list of downloads\nDo you want to exit ?"), QMessageBox::Yes|QMessageBox::Save|QMessageBox::No, 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->pid)); waitpid(this->pid, NULL, WUNTRACED); }else { saveDownloadList(); } } event->accept(); } // Populate treeview with list of files void MainWindow::populateTree(QTreeWidgetItem * parent) { stringstream ss; vector path; // Clear treewidget ui->treeWidget->clear(); if (!this->connexion.server.empty() and this->connexion.port > 0 and this->connexion.port < 65536) { // setting cursor to "Wait" QGuiApplication::setOverrideCursor(Qt::WaitCursor); if (validateServer(this->connexion.server)) { // server is validated path = explode(ui->listWidget->currentItem()->text().toStdString(), '\n', 2); scanDir(this->connexion.server, this->connexion.port, parent, path[0].append("/") ); } // Restoring cursor QGuiApplication::restoreOverrideCursor(); } } // Populate Listview with list of services void MainWindow::populateList() { stringstream ss; QString str; QString server; int port; server = ui->khistorycombobox->currentText(); port = ui->portEdit->text().toUInt(); if ((server.toStdString() != this->connexion.server) or (port != this->connexion.port)) { // clearing listwidget ui->listWidget->clear(); this->connexion.server.assign(server.toStdString()); this->connexion.port = port; QGuiApplication::setOverrideCursor(Qt::WaitCursor); // verify if server is in history this->settings.beginGroup("connexion/server"); if (this->settings.contains(server)) { // server is in history => setting port value port = this->settings.value(server).toUInt(); ui->portEdit->setText(QString::number(port)); this->connexion.port = port; listServices(); }else { if (!server.isEmpty() and (port > 0 and port < 65536)) { if (validateServer(server.toStdString())) { cout << server.toStdString() << endl; // storing serverURL and port in settings this->settings.setValue(server, port); this->settings.sync(); this->downloading.server = server.toStdString(); // storing in history of combobox ui->khistorycombobox->addToHistory(server); // "waiting" cursor // load and display rsync services of the rsync server listServices(); } } } this->settings.endGroup(); QGuiApplication::restoreOverrideCursor(); //setOverrideCursor(Qt::ArrowCursor); } } //list services of the rsync server void MainWindow::listServices() { char cmd[4096]; string line; string errorRsync; vector v; char service[4096]; sprintf(cmd, "rsync --contimeout=10 -P \"%s::\" --port %d ", this->connexion.server.c_str(), this->connexion.port ); redi::ipstream in(cmd, redi::pstreams::pstdout | redi::pstreams::pstderr); while (getline(in.out(), line)) { boost::replace_all(line," ",""); boost::replace_all(line, "\t", " - "); v = explode(line, ' ', 3 ); sprintf(service, "%s\n\t%s", v[0].c_str(), v[2].c_str()); ui->listWidget->addItem(service); } } // connect to rsync server to get list of files void MainWindow::scanDir(string server, int portN, QTreeWidgetItem *parent, string path) { char cmd[4096]; string line; string errorRsync; vector v; QTreeWidgetItem * item; bool isDir = false; sprintf(cmd, "rsync --contimeout=10 -P \"%s::%s\" --port %d ", server.c_str(), path.c_str(), portN ); redi::ipstream in(cmd, redi::pstreams::pstdout | redi::pstreams::pstderr); while (getline(in.out(), line)) { v = explode(line, ' ', 5); if (v.size() == 5) { if (v[4].at(0) != '.' and (v[0].at(0) == '-' or v[0].at(0) == 'd')) { if (v[0].at(0) == 'd') { isDir = true; }else { isDir = false; } if (parent != NULL) { item = addTreeChild(parent,QString::fromStdString(v[4]), QString::fromStdString(v[1]), isDir); }else { item = addTreeRoot(QString::fromStdString(v[4]), QString::fromStdString(v[1]), isDir); } } } } // if reading stdout stopped at EOF then reset the state: if (in.eof() && in.fail()) in.clear(); // read child's stderr while (getline(in.err(), line)) { cout << "stderr: " << line << endl; errorRsync.append(line); errorRsync.append("\n"); } if ( !errorRsync.empty()) { QMessageBox::warning( this, "RsyncUI", errorRsync.c_str()); } } // Verify if server address is IP address bool MainWindow::isIpAddress(string server) { vector r; int elementN; QString qr; bool ok; r = explode(server, '.', 5); if (r.size() == 4) { for (auto element : r) { elementN = QString::fromStdString(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(string server) { char cmd[512]; string line; string errorDig; bool flag = false; sprintf(cmd, "dig %s", server.c_str()); redi::ipstream in(cmd, redi::pstreams::pstdout | redi::pstreams::pstderr); while (getline(in.out(), line)) { //cout << "stdout: " << line << '\n'; if (line.find(";; ANSWER SECTION:") != string::npos) { flag = true; } } // if reading stdout stopped at EOF then reset the state: if (in.eof() && in.fail()) in.clear(); // read child's stderr while (std::getline(in.err(), line)) { cout << "stderr: " << line << '\n'; errorDig.append(line); errorDig.append("\n"); } if ( !errorDig.empty()) { QMessageBox::warning( this, "RsyncUI", errorDig.c_str() ); } if ( flag == false) { flag = isIpAddress(server); } if ( flag == false) { QMessageBox::warning( this, "RsyncUI", tr("server does not exists" ) ); } return flag; } // slot activated when combobox is changed void MainWindow::on_khistorycombobox_currentIndexChanged(int i) { on_connectButton_clicked(); } // 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(QTreeWidget * parent, int type = Type) QTreeWidgetItem *treeItem = new QTreeWidgetItem(ui->treeWidget); // QTreeWidgetItem::setText(int column, const QString & text) if (isDir == true) { treeItem->setText(1, tr("Dir")); }else { 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(QTreeWidget * parent, int type = Type) QTreeWidgetItem *treeItem = new QTreeWidgetItem(); // QTreeWidgetItem::setText(int column, const QString & text) if (isDir == true) { treeItem->setText(1, tr("Dir")); }else { 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() { vector v; QString str; v = explode(ui->listWidget->currentItem()->text().toStdString(), '\n', 2); this->connexion.service = v[0]; str = QString::fromStdString("Folder/" + this->connexion.server + "/" + this->connexion.service); if (this->settings.contains(str)) { this->downloading.savePath = this->settings.value(str).toString().toStdString(); } populateTree(NULL); } //Slot activated when a file is clicked in the treeview void MainWindow::on_treeWidget_itemClicked(QTreeWidgetItem *item) { QFuture future; QFileDialog dialog; QTreeWidgetItem * itemR; string path; QString str; //item = ui->treeWidget->currentItem(); itemR = item; path = item->text(0).toStdString(); while(itemR->parent() != NULL) { itemR = itemR->parent(); path = itemR->text(0).toStdString() + "/" + path; }; if (item->text(1) == tr("File")) { // Item is a file this->downloading.path = path; this->downloading.server = this->connexion.server; this->downloading.service = this->connexion.service; // exists saving path in settings ? str = QString::fromStdString("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->pid == 0) { // no downloading process launching it startDownloading(); // wit 1 second to process start //sleep(1); } // Adding download in download list str = QString::fromStdString(this->downloading.path + " => " + this->connexion.server + "/" + this->downloading.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); } } // Launch the thread which download the file void MainWindow::startDownloading() { ui->progressBar->setValue(0); ui->progressBar->show(); QtConcurrent::run(&this->downloadO, &downloadFile::download, this); } // Slot stopping download void MainWindow::stoppingDownload() { emit (stopDownloading(this->pid)); } // when download is finished, launch download of next file in queue void MainWindow::downloadFinished() { string path; int pos; string str; this->pid = 0; ui->progressBar->hide(); delete ui->listDownload->takeItem(0); this->downloading.clear(); if (ui->listDownload->count() != 0) { path = ui->listDownload->item(0)->text().toStdString(); pos = path.rfind("/"); this->downloading.service = path.substr(pos+1); path.resize(pos); pos = path.rfind(" => "); this->downloading.server = path.substr(pos+4); path.resize(pos); this->downloading.path = path; str = "Folder/" + this->downloading.server + "/" + this->downloading.service; if (this->settings.contains(QString::fromStdString(str))) { this->downloading.savePath = this->settings.value(QString::fromStdString(str)).toString().toStdString(); }else { if(!on_DefaultSaveFolder_triggered()) { cout << "Error no save path so deleting download"; downloadFinished(); return; } } startDownloading(); } } // Slot activated when a line is clicked in queue list void MainWindow::on_listDownload_itemClicked(QListWidgetItem *item) { QFileDialog dialog; QMessageBox::StandardButton reply; //cout << item->text().toStdString() << endl; if (item->listWidget()->row(item) == 0) { 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) { emit (stopDownloading(this->pid)); } }else { 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) { ui->listDownload->removeItemWidget(item); delete item; } } } // 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()); // 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().toStdString(); // loading bandwidth limit this->connexion.bandwidthLimit = this->settings.value("bandwidthlimit").toUInt(); this->connexion.bandwidthLimitUnit = this->settings.value("bandwidthlimitunit").toString().toStdString(); } // 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", QString::fromStdString(this->connexion.server)); this->settings.setValue("connexion/lastPort", QString::number(this->connexion.port)); this->settings.setValue("toolbar/state", ui->toolBar->toolButtonStyle()); this->settings.sync(); } // About void MainWindow::on_actionAbout_triggered() { //TODO => initialisation 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; string folder; string path; if (this->connexion.service.empty()) { QMessageBox::warning( NULL, "RsyncUI", "Since the save path is linked to service, you need to select a service before you can select a folder"); return false; } path = dialog.getExistingDirectory(this, tr("Choose directory to save file"), QString::fromStdString(getpwuid(getuid())->pw_dir), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toStdString(); if (!path.empty()) { this->downloading.savePath = path; if (!this->connexion.service.empty() and !this->connexion.server.empty()) { folder = "Folder/" + this->connexion.server + "/" + this->connexion.service; this->settings.setValue(folder.c_str(), this->downloading.savePath.c_str()); this->settings.sync(); } }else { return false; } return true; } // Activated when menu "settings" is clicked void MainWindow::on_action_Settings_triggered() { config.UnitCombobox->setCurrentIndex(bwUnixIndex[this->connexion.bandwidthLimitUnit[0]]); 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) { this->connexion.bandwidthLimit = 1000; this->connexion.bandwidthLimitUnit = tr("PB").toStdString(); }else { this->connexion.bandwidthLimit = config.spinBox->value(); this->connexion.bandwidthLimitUnit = config.UnitCombobox->currentText().toStdString()[0]; } this->settings.setValue("bandwidthlimit", this->connexion.bandwidthLimit); this->settings.setValue("bandwidthlimitunit", this->connexion.bandwidthLimitUnit.c_str()); this->settings.sync(); Configuration.hide(); } // Saving download list void MainWindow::saveDownloadList() { int nRows; nRows = ui->listDownload->count(); //this->settings.beginWriteArray("Downloads/"); 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(); } void MainWindow::loadDownloadList() { string path; string str; int pos; 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(); this->settings.remove("Downloads"); this->settings.sync(); path = ui->listDownload->item(0)->text().toStdString(); pos = path.rfind("/"); this->downloading.service = path.substr(pos+1); path.resize(pos); pos = path.rfind(" => "); this->downloading.server = path.substr(pos+4); path.resize(pos); this->downloading.path = path; str = "Folder/" + this->downloading.server + "/" + this->downloading.service; if (this->settings.contains(QString::fromStdString(str))) { this->downloading.savePath = this->settings.value(QString::fromStdString(str)).toString().toStdString(); } startDownloading(); } void Downloading::clear() { this->path.clear(); this->server.clear(); this->savePath.clear(); this->service.clear(); }