diff --git a/RsyncUI.pro b/RsyncUI.pro index 34375bb..4e9f8ab 100644 --- a/RsyncUI.pro +++ b/RsyncUI.pro @@ -31,7 +31,7 @@ FORMS += \ mainwindow.ui TRANSLATIONS += \ - languages/fr_FR/RsyncUI_fr_FR.ts + RsyncUI_fr_FR.ts INCLUDEPATH += \ /usr/include/KF5 @@ -41,7 +41,6 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /usr/bin/ !isEmpty(target.path): INSTALLS += target - INSTALLS += lang INSTALLS += documentation INSTALLS += desktopfile @@ -53,6 +52,7 @@ DISTFILES += \ RsyncUI.desktop lang.path = /languages/ +lang.path = /usr/share/locale/ lang.files = languages/* documentation.path = /usr/share/doc/RsyncUI documentation.files = README* diff --git a/RsyncUI.pro.user b/RsyncUI.pro.user index 3d883ab..4c521a3 100644 --- a/RsyncUI.pro.user +++ b/RsyncUI.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/RsyncUI.spec b/RsyncUI.spec index 598670f..c4888c6 100644 --- a/RsyncUI.spec +++ b/RsyncUI.spec @@ -2,7 +2,7 @@ Name: rsyncui Summary: Client for rsync server -Version: 1.9.1 +Version: 1.9.2 Release: %mkrel 1 License: GPLv3 Group: Networking/Remote access diff --git a/RsyncUI_fr_FR.ts b/RsyncUI_fr_FR.ts new file mode 100644 index 0000000..3cef1ea --- /dev/null +++ b/RsyncUI_fr_FR.ts @@ -0,0 +1,431 @@ + + + + + Configuration + + + Configuration + Configuration + + + + Text follow style + Le texte suit le style + + + + Toolbar view + Toolbar + + + + Bandwidth limit + Bandwidth limit + Limite de bande passante + + + + Enter the bandwidth limit (0 to 1024) + Entrez la limite de bande passante (0 à 1024) + + + + Select th unit of bandwidth limit in Bytes, KiloBytes, MegaBytes, GigaBytes or PetaBytes + Unit of bandwidth + Sélectionnez l'unité de limite de bande passante en otctets, Ko, Mo, Go, Po + + + + Icon only + Icône seule + + + + Text only + Texte seul + + + + Text beside icon + Text à côté de l'icône + + + + Text under icon + Texte sous l'icöne + + + + Automatic saving of download queue + Enregistrement automatique de la file + + + + Auto save download queue + Enregistrement automatique de la file d'enregistrement + + + + MainWindow + + + MainWindow + Fenêtre principale + + + + Server + Serveur + Serveur + + + + Enter server's URL + Entrez l'adresse du serveur + + + + Port + Port + + + + Enter rsync port on server + Entrez le port du serveur rsync + + + + Press button to connect to rsync server + Connect to server + Cliquez pour se connecter au serveur rsync + + + + Connection + Connexion + + + + Return + Retour + + + + Click to view the list of files of this folder + Cliquez pour afficher la liste des documents + + + + Click to add to download queue + Cliquez pour ajouter à la file de téléchargement + + + + Click on file to stop downloading + Cliquez sur le document pour arrêter le téléchargement et l'enlever de la file + + + + %p% + Downloading + Téléchargement + + + + toolBar + Barre d'outils + + + + Download + Télécharger + + + + Click on menu button to download the entire folder + Cliquer sur le bouton droit de la souris pour télécharger le dossier entier + + + + + Change save folder + Changer le dossier de destination + + + + About + À propos + + + + About Qt + À propos de Qt + + + + Settings + Paramètres + + + + KB + Ko + + + + MB + Mo + + + + GB + Go + + + + TB + To + + + + PB + Po + + + + Client for rsync server + +You click on file to enqueue it, and RyncUI Download one file a time + Client pour serveur rsync + +Cliquez sur un fichier pour l'ajouter dans la file de téléchargement + + + + Path + Chemin + + + + Size + Taille + + + + Type + Type + + + + A list of interrupted downloads exists, do you want to continue downloading ? if not the list will be cleared + Une liste de téléchargement existe, voulez-vous utiliser cette liste ? si non, elle sera effacée + + + + server does not exists + Le serveur n'existe pas + + + + + Dir + dossier + + + + + File + Doc + + + + Rsync process crashed + Le processus rsync à planté + + + + Version + Version + + + + Licence + License + + + + Author + Auteur + + + + EMail + Courriel + + + + Source code + Code source + + + + Since the save path is linked to service, you need to select a service before you can select a folder + Vous devez sélectionnez un service pour pouvoir sélectionnez un dossier par défaut + + + + Choose folder where to save file + Choisissez un dossier où enregistrer le document + + + Choose directory to save file + Choisissez le dossier où enregistrer + + + + Do you want to stop downloading and delete this file from download queue ? + Voulez-vous arrêter le téléchargement et enlever ce fichier de la file de téléchargement ? + + + A list of interrupted downloads exists, do you want to continue downloading ? or you can delete the list + Voulez-vous reprendre les téléchargements interrompus la fois précédente? ou vous pouvez supprimer la liste + + + Remove + Supprimer + + + + Exiting will stop downloading, and will clear the download queue. +You can save the list of downloads +Do you want to exit ? + i + Si vous sortez, les téléchargements seront arrêter, mais vous pouvez enregistrer la liste pour la prochaine fois. +Voulez-vous vraiment sortir ? + + + + Do you want to delete this file from download queue ? + Voulez-vous enlever ce fichier de la file de téléchargement ? + + + + The process failed to start. Either the invoked program is missing, or you may have insufficient permissions or resources to invoke the program. + Le processus échoué, soit le programme est manquant, soit vous n'avez pas l'autorisation de l'exécuter. + + + + The process crashed some time after starting successfully. + Le processus à planté après avoir démarré avec succès. + + + + The last waitFor...() function timed out. The state of QProcess is unchanged, and you can try calling waitFor...() again. + La fonction waitFor...() a dépassé la limite de temps, you pouvez essayer de la relancer. + + + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + Une erreur est survenue lors de l'envoi des données vers le processus. Le processus est peut être arrté ou il a fermé son canal d'entrée. + + + + An error occurred when attempting to read from the process. For example, the process may not be running. + Une erreur est survenue lors de la lecturee de données, le processus est probablement arrêté. + + + + An unknown error occurred. This is the default return value of error(). + Une erreur inconnue est survenue. C'est la valeur de retour par défaut de error(). + + + + QTranslator + + + Success. The rsync command completed successfully without any errors. + Succès. La command rsync s'est terminée sans erreurs. + + + + Syntax or usage error. There was a problem with the syntax of the rsync command or with the options specified. + Erreur de syntaxe ou d'usage. il y a un problème avec la synaxe de rsync ou avec les paramètres spécifiés. + + + + Protocol incompatibility. There was a problem with the protocol version or negotiation between the rsync client and server. + Incompatibilité de protocole. Il y a eu un problème avec la version du protocole ou la négotiation avec le serveur rsync. + + + + Errors selecting input/output files, dirs. There was a problem with the source or destination file or directory specified in the rsync command. + Erreurs dans la sélection des fichiers ou dossiers. il y a eu une problème avec le fichier/dossier source ou destination spécifié. + + + + Requested action not supported: An attempt was made to use an unsupported action or option. + Action non suportée. Une tentative d'utiliser une action, ou une option, non supportée à été faite. + + + + Error starting client-server protocol. There was an error starting the client-server protocol. + Erreur de démarrage du protocole client-seveur. Il ya eu une erreur en démarrant le protocole slient-serveur. + + + + Daemon unable to append to log-file. The rsync daemon was unable to write to its log file. + Le démon rsyncd n'arrive pas écrire dans son fichier de log. + + + + Error in socket I/O. There was an error with the socket input/output. + Erreur d'entrée/sortie socket. Il y a eu une erreur d'entrée/sortie sur le socket. + + + + Error in file I/O. There was an error reading or writing to a file. + Erreur de 'entrée/sortie. Il y a eu une erreur de lecture/écriture sur un fichier. + + + + Error in rsync protocol data stream. There was an error in the rsync protocol data stream. + Erreur dans le protocole de flux de données de rsync. + + + + Errors with program diagnostics. There was an error generating program diagnostics. + Erreur avec les diagnostiques du programme. + + + + Error in IPC code. There was an error in the inter-process communication (IPC) code. + Erreur dans le code IPC. IL y a eu une erreur dans la communication inter-processus. + + + + Received SIGUSR1 or SIGINT. The rsync process was interrupted by a signal. + Reçu les signaux SIGUSR1 ou SIGINT. Le processus rsync a été interrompu par un signal. + + + + Some error returned by waitpid(). An error occurred while waiting for a child process to complete. + Erreurs retournées par waitpid().Une erreur est survenue pendant l'attente de la complétion du processus fils. + + + + Error allocating core memory buffers. There was an error allocating memory buffers. + Erreur d'allocation des buffers mémoire. + + + + Partial transfer due to error. The rsync command completed with an error, but some files may have been transferred successfully. + Transfert partiel due à une erreur. La command rsync s'est terminée avec une erreur, mais des fichiers on été transférés avec succès. + + + + Partial transfer due to vanished source files. Some source files disappeared before they could be transferred. + Transfert partiel dû à la disparition des fichiers source. Des fichiers source ont disparu avec d'avoir été transférés. + + + diff --git a/configuration.ui b/configuration.ui index 3018e37..baeb441 100644 --- a/configuration.ui +++ b/configuration.ui @@ -9,8 +9,8 @@ 0 0 - 400 - 163 + 491 + 196 @@ -24,44 +24,14 @@ 9 9 - 381 - 101 + 464 + 109 QLayout::SetDefaultConstraint - - - - Toolbar view - - - - - - - Bandwidth limit - - - - - - - Enter the bandwidth limit (0 to 1024) - - - 5000 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 1024 - - - @@ -87,6 +57,22 @@ + + + + Enter the bandwidth limit (0 to 1024) + + + 5000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1024 + + + @@ -117,18 +103,32 @@ - Text follow icon + Text follow style + + + + Bandwidth limit + + + + + + + Toolbar view + + + - 60 - 110 + 150 + 160 321 34 @@ -143,6 +143,34 @@ QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + 10 + 120 + 461 + 31 + + + + + + + Automatic saving of download queue + + + 5000 + + + Auto save download queue + + + true + + + + + diff --git a/downloadfile.cpp b/downloadfile.cpp index b2055a0..8464ee2 100644 --- a/downloadfile.cpp +++ b/downloadfile.cpp @@ -25,85 +25,86 @@ downloadFile::downloadFile() } //Slot activated when download is cancelled -void downloadFile::cancelled(int pid) +void MainWindow::cancelled(QProcess * process) { - if (pid == 0) - { - perror("Pid = 0 : I do not kill"); // Error rsync process not launched so it can't be killed - }else if (kill(pid, SIGTERM) == -1) - { - //TODO managing error of kill - } + process->terminate(); } // launch a rsync processus downloading a file -void downloadFile::download(MainWindow *mw) +void MainWindow::download() { - string line; - string errorRsync; - int pos; - array argv; - stringstream output; - vector v; - int value; - char buffer[4096]; - Downloading d; - - d = mw->downloading; + QString cmd; + QStringList param; // Populating array with command and parameters for popen2 - argv[0] = "/usr/bin/rsync"; - if (mw->connexion.bandwidthLimit == 0) + cmd = "rsync"; + if (this->connexion.bandwidthLimit != 0) { - argv[1] = "--bwlimit=1000P"; - }else - { - argv[1] = "--bwlimit=" + to_string(mw->connexion.bandwidthLimit) + mw->connexion.bandwidthLimitUnit; + param << "--bwlimit=" + QString::number(this->connexion.bandwidthLimit) + bwUnitChar[this->connexion.bandwidthLimitUnit]; } - argv[2] = "--port=" + to_string(mw->connexion.port); - argv[3] = "-aP"; - argv[4] = d.server + "::" + d.service + "/" + d.path; - argv[5] = d.savePath + "/"; - argv[6].clear(); + param << "--port=" + QString::number(this->connexion.port); + param << "-aXP"; + param << this->downloading.server + "::" + this->downloading.service + "/" + this->downloading.path << this->downloading.savePath + "/"; - //launching downloading thread - FILE * fp = popen2(argv, "r", mw->pid); - if (fp <= (FILE *) 0) - { - sprintf(buffer, "popen2() failed!: returning code:%d", fileno(fp)); - throw runtime_error(buffer); - return; - } + this->downloading.process = new QProcess(this); - // waiting rsync output - while (fgets(buffer, 4096, fp) != nullptr) + connect(this->downloading.process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(downloadFinished(int, QProcess::ExitStatus))); + //connect(this->downloading.process, SIGNAL(errorOccurred(QProcess::ProcessError error)), this, SLOT(downloadProcessError(QProcess::ProcessError error))); + //connect(this->downloading.process, SIGNAL(readyReadStandardError()), this, SLOT(downloadProcessStderr())); + connect(this->downloading.process, SIGNAL(readyReadStandardOutput()), this, SLOT(readRsyncOutput())); + + this->downloading.process->start(cmd, param); +} + +void MainWindow::readRsyncOutput() +{ + QString line; + bool flag = false; + int value; + int pos; + + while(!flag) { - // Downloading is cancelled, we return - if (this->canceled == true) + line = QString::fromUtf8(this->downloading.process->readLine()); + if (line.isEmpty()) { - return; + flag = true; + break; } - line = buffer; - - // extracting percentage of completion - pos = line.find('%'); + pos = line.indexOf("%"); if (pos != -1) { - line.erase(pos); - pos = line.find_last_of(' '); + line.resize(pos); + pos = line.lastIndexOf(' '); if (pos != -1) { - line.erase(0, pos); - value = stoi(line); + line.remove(0, pos); + value = line.toInt(); // sending progress to Main window emit progressSignal(value); } } - // download ended } - pclose2(fp, mw->pid); - - // ProgressBar to 100% and emit signal finished to main application - emit progressSignal(100); - emit finishedSignal(true); } + +/*void MainWindow::downloadProcessError(QProcess::ProcessError error) +{ + QMessageBox::warning( + this, + "RsyncUI", + downloadProcessErrorString[error].toStdString().c_str() + ); +}*/ + +void MainWindow::downloadProcessStderr() +{ + QByteArray errorLine; + + errorLine = this->downloading.process->readAllStandardError(); + QMessageBox::warning( + this, + "RsyncUI", + errorLine + ); +} + diff --git a/downloadfile.h b/downloadfile.h index 1cfef71..74098f5 100644 --- a/downloadfile.h +++ b/downloadfile.h @@ -13,15 +13,8 @@ class downloadFile : public QObject Q_OBJECT public: downloadFile(); - void download(MainWindow *parent = nullptr); bool canceled; -signals: - void progressSignal(int); - void finishedSignal(bool = true); - -public slots: - void cancelled(int pid); }; #endif // DOWNLOADFILE_H diff --git a/languages/fr_FR/RsyncUI_fr_FR.qm b/languages/fr_FR/RsyncUI_fr_FR.qm index 77bbaeb..ff2cc56 100644 Binary files a/languages/fr_FR/RsyncUI_fr_FR.qm and b/languages/fr_FR/RsyncUI_fr_FR.qm differ diff --git a/languages/fr_FR/RsyncUI_fr_FR.ts b/languages/fr_FR/RsyncUI_fr_FR.ts deleted file mode 100644 index 4f626be..0000000 --- a/languages/fr_FR/RsyncUI_fr_FR.ts +++ /dev/null @@ -1,302 +0,0 @@ - - - - - Configuration - - - Configuration - Configuration - - - - Toolbar view - Toolbar - - - - Bandwidth limit - Bandwidth limit - Limite de bande passante - - - - Enter the bandwidth limit (0 to 1024) - Entrez la limite de bande passante (0 à 1024) - - - - Select th unit of bandwidth limit in Bytes, KiloBytes, MegaBytes, GigaBytes or PetaBytes - Unit of bandwidth - Sélectionnez l'unité de limite de bande passante en otctets, Ko, Mo, Go, Po - - - - Icon only - Icône seule - - - - Text only - Texte seul - - - - Text beside icon - Text à côté de l'icône - - - - Text under icon - Texte sous l'icöne - - - - Text follow icon - Texte suit l'icône - - - - Automatic saving of download queue - Enregistrement automatique de la file - - - - Auto save download queue - Enregistrement automatique de la file d'enregistrement - - - - MainWindow - - - MainWindow - Fenêtre principale - - - - Server - Serveur - Serveur - - - - Enter server's URL - Entrez l'adresse du serveur - - - - Port - Port - - - - Enter rsync port on server - Entrez le port du serveur rsync - - - - Press button to connect to rsync server - Connect to server - Cliquez pour se connecter au serveur rsync - - - - Connection - Connexion - - - - Return - Retour - - - - Click to view the list of files of this folder - Cliquez pour afficher la liste des documents - - - - Click to add to download queue - Cliquez pour ajouter à la file de téléchargement - - - - Click on file to stop downloading - Cliquez sur le document pour arrêter le téléchargement et l'enlever de la file - - - - %p% - Downloading - Téléchargement - - - - toolBar - Barre d'outils - - - - Download - Télécharger - - - - Click on menu button to download the entire folder - Cliquer sur le bouton droit de la souris pour télécharger le dossier entier - - - - - Change save folder - Changer le dossier de destination - - - - About - À propos - - - - About Qt - À propos de Qt - - - - Settings - Paramètres - - - - Byte - Octet - - - - KB - Ko - - - - MB - Mo - - - - GB - Go - - - - TB - To - - - - - PB - Po - - - - Client for rsync server - -You click on file to enqueue it, and RyncUI Download one file a time - Client pour serveur rsync - -Cliquez sur un fichier pour l'ajouter dans la file de téléchargement - - - - Path - Chemin - - - - Size - Taille - - - - Type - Type - - - - server does not exists - Le serveur n'existe pas - - - - - Dir - dossier - - - - - File - Doc - - - - Version - Version - - - - Licence - License - - - - Author - Auteur - - - - EMail - Courriel - - - - Source code - Code source - - - - Choose directory to save file - Choisissez le dossier où enregistrer - - - - Do you want to stop downloading and delete this file from download queue ? - Voulez-vous arrêter le téléchargement et enlever ce fichier de la file de téléchargement ? - - - - A list of interrupted downloads exists, do you want to continue downloading ? or you can delete the list - Voulez-vous reprendre les téléchargements interrompus la fois précédente? ou vous pouvez supprimer la liste - - - - Remove - Supprimer - - - - Exiting will stop downloading, and will clear the download queue. -You can save the list of downloads -Do you want to exit ? - i - Si vous sortez, les téléchargements seront arrêter, mais vous pouvez enregistrer la liste pour la prochaine fois. -Voulez-vous vraiment sortir ? - - - - Do you want to delete this file from download queue ? - Voulez-vous enlever ce fichier de la file de téléchargement ? - - - diff --git a/mainwindow.cpp b/mainwindow.cpp index d5ce810..1558e85 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,774 +1,858 @@ -#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"); - - // context menu for treewidget - ui->treeWidget->addAction(ui->actionDownload); - - // 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, bool downloadDir) -{ - 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") or downloadDir == true) - { - // 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(); -} - -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); -} +#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 + ui->treeWidget->addAction(ui->actionDownload); + + // init shortcut + 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); + ui->ktreewidgetsearchline->setCaseSensitivity(Qt::CaseInsensitive); + ui->treeWidget->setHeaderLabels({tr("Path"), tr("Type"), tr("Size")} ); + + 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(); + + loadSettings(); + + //setting configuration window + config.comboBox->setCurrentIndex(ui->toolBar->toolButtonStyle()); + config.UnitCombobox->addItems({tr("KB"), tr("MB"), tr("GB"), tr("TB"), tr("PB")}); + + 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 ? if not the list will be cleared" )); + + //QPushButton *remove = msgBox.addButton(tr("Remove"), QMessageBox::ActionRole); + QPushButton *yes = msgBox.addButton(QMessageBox::Yes); + + msgBox.addButton(QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + + msgBox.exec(); + reply = msgBox.clickedButton(); + if(reply == yes) + { + loadDownloadList(); + } + } + populateList(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +// Closing window has been clicked +void MainWindow::closeEvent (QCloseEvent *event) +{ + QMessageBox::StandardButton reply; + QMessageBox::StandardButtons param; + + // 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; + } + 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 ?"), + 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) +{ + stringstream ss; + 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); + + 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() +{ + //stringstream ss; + QString str; + 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; + + 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; + 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); + + // "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() +{ + 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); + + while(myProcess->waitForReadyRead(10000)) + { + while(!flag) + { + line = QString::fromUtf8(myProcess->readLine()); + if (line.isEmpty()) + { + flag = true; + break; + } + v = line.split("\t"); + v[0].replace(" ", ""); + v[1].replace("\n", ""); + service = v[0] + "\n\t" + v[1]; + + ui->listWidget->addItem(service); + } + flag =false; + } + + 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 errorRsync; + QString size; + QString filename; + QString dir; + 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); + + while(myProcess->waitForReadyRead(100000)) + { + while (!flag) + { + line = QString::fromUtf8(myProcess->readLine()); + if (line.isEmpty()) + { + flag = true; + break; + } + 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) + { + item = addTreeChild(parent, filename, size, isDir); + }else + { + item = addTreeRoot(filename, size, isDir); + } + } + } + flag = false; + } + + testRsyncReturn(myProcess); +} + +// Verify if server address is IP address +bool MainWindow::isIpAddress(QString server) +{ + QStringList r; + int elementN; + QString qr; + 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; + QString errorDig; + QProcess * myProcess; + bool flag = false; + + cmd = "dig"; + param << server; + + myProcess = new QProcess(this); + myProcess->start(cmd, param); + + while(myProcess->waitForReadyRead()) + { + line = QString::fromUtf8(myProcess->readAllStandardOutput()); + if (line.indexOf(";; ANSWER SECTION:") != -1) + { + flag = true; + } + } + + //testRsyncReturn(myProcess); + + 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) +{ + 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(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() +{ + QString service; + QString str; + + this->connexion.service = ui->listWidget->currentItem()->text().section("\n", 0 ,0); + str = "Folder/" + this->connexion.server + "/" + this->connexion.service; + if (this->settings.contains(str)) + { + 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; + + path = item->text(0); + while(itemR->parent() != NULL) + { + itemR = itemR->parent(); + path = itemR->text(0) + "/" + path; + }; + + 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; + int pos; + QString str; + + if (exitStatus == QProcess::CrashExit) + { + QMessageBox::warning( + NULL, + "RsyncUI", + tr("Rsync process crashed")); + } + if (exitCode != 0 and exitCode != 20) + { + QMessageBox::warning( + NULL, + "RsyncUI", + rsyncErrorStrings[exitCode]); + } + + disconnect(this->downloading.process, 0, 0, 0); + this->downloading.process= nullptr; + ui->progressBar->hide(); + delete ui->listDownload->takeItem(0); + this->downloading.clear(); + if (ui->listDownload->count() != 0) + { + if (config.autosaveCheckbox->checkState() == Qt::Checked) + { + saveDownloadList(); + } + 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(); + }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->downloading.process)); + } + }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; + if (config.autosaveCheckbox->checkState() == Qt::Checked) + { + 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() +{ + //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; + QString folder; + QString path; + + 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; + } + 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()) + { + 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) + { + 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; + + this->settings.remove("Downloads/"); + 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() +{ + QString path; + QString 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.sync(); + + 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(); +} + +void Downloading::clear() +{ + this->path.clear(); + this->server.clear(); + this->savePath.clear(); + this->service.clear(); +} + +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); +} +*/ + +void MainWindow::on_comboBox_currentIndexChanged(int index) +{ + ui->toolBar->setToolButtonStyle((Qt::ToolButtonStyle)index); +} diff --git a/mainwindow.h b/mainwindow.h index af8fbc6..562f2d9 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -36,29 +36,35 @@ #include #include #include +#include +#include +#include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE +extern QMap rsyncErrorStrings; + class Connexion { public: int bandwidthLimit = 0; - std::string bandwidthLimitUnit = ""; - std::string server; - std::string service; - + int bandwidthLimitUnit; + QString server; + QString service; int port = 873; + bool comboboxChanged; }; class Downloading { public: - std::string server; - std::string service; - std::string path; - std::string savePath; + QString server; + QString service; + QString path; + QString savePath; + QProcess * process = nullptr; void clear(); }; @@ -67,7 +73,7 @@ class About { public: QString title = "RsyncUI"; - QString version = "1.9.1"; + QString version = "1.9.2"; QString author = "Daniel TARTAVEL-JEANNOT"; QString licence = "GPL_V3"; QString description; @@ -84,38 +90,55 @@ class MainWindow : public QMainWindow MainWindow(QWidget *parent = nullptr); ~MainWindow(); QProgressDialog *progress; - int pid = 0; Connexion connexion; Downloading downloading; - downloadFile downloadO; + //downloadFile downloadO; QSettings settings; About about; QDialog Configuration; Ui::Configuration config; std::vector serversList; - map bwUnixIndex { - {'B', 0}, - {'K', 1}, - {'M', 2}, - {'G', 3}, - {'P', 4} + QList bwUnitText { + "KB", + "MB", + "TB", + "GB", + "PB" + }; + QList bwUnitChar{ + 'K', + 'M', + 'T', + 'G', + 'P' + }; + + QVector downloadProcessErrorString = + { + tr("The process failed to start. Either the invoked program is missing, or you may have insufficient permissions or resources to invoke the program."), + tr("The process crashed some time after starting successfully."), + tr("The last waitFor...() function timed out. The state of QProcess is unchanged, and you can try calling waitFor...() again."), + tr("An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel."), + tr("An error occurred when attempting to read from the process. For example, the process may not be running."), + tr("An unknown error occurred. This is the default return value of error().") }; void displayTree(); void populateTree(QTreeWidgetItem * parent); void populateList(); void listServices(); - bool validateServer(std::string server); - bool isIpAddress(std::string server); + bool validateServer(QString server); + bool isIpAddress(QString server); QTreeWidgetItem * addTreeRoot(QString name, QString description, bool isDir); QTreeWidgetItem * addTreeChild(QTreeWidgetItem *parent, QString name, QString size, bool isDir); - void scanDir(std::string server, int portN, QTreeWidgetItem *parent = NULL, std::string path = "" ); + void scanDir(QString server, int portN, QTreeWidgetItem *parent = NULL, QString path = "" ); void startDownloading(); void loadSettings(); void saveSettings(); void closeEvent (QCloseEvent *event); void saveDownloadList(); void loadDownloadList(); + void download(); private slots: @@ -123,7 +146,13 @@ class MainWindow : public QMainWindow void on_treeWidget_itemClicked(QTreeWidgetItem *item, bool downloadDir = false); - void downloadFinished(); + void downloadFinished(int exitCode, QProcess::ExitStatus exitStatus); + +// void downloadProcessError(QProcess::ProcessError error); + + void downloadProcessStderr(); + + void readRsyncOutput(); void stoppingDownload(); @@ -143,11 +172,20 @@ class MainWindow : public QMainWindow void on_actionDownload_triggered(); -public slots: + //void downloadingErrorSlot(QString); + + void on_comboBox_currentIndexChanged(int index); + void on_buttonBox_accepted(); + void cancelled(QProcess *); + signals: - void stopDownloading(int); + void stopDownloading(QProcess *); + void progressSignal(int); + void finishedSignal(bool = true); + //void errorSignal(QString); + }; #endif // MAINWINDOW_H diff --git a/tools.cpp b/tools.cpp index a047764..b0cd9e6 100644 --- a/tools.cpp +++ b/tools.cpp @@ -5,6 +5,8 @@ using namespace std; #define READ 0 #define WRITE 1 +extern QMap rsyncErrorStrings; + //Take a string and explode it in array // s => string to explode // c => character separator @@ -41,90 +43,26 @@ const vector explode(const string& s, const char& c, int n = 0) return v; } -// open a pipe, fork and return pid of child -// argv => array of string with command in first and parameters following -FILE * popen2(array argv, string type, int & pid) +bool testRsyncReturn(QProcess * myProcess) { - pid_t child_pid; - int fd[2]; - QString message; - string command; - - if (pipe(fd) == -1) + if (myProcess->exitStatus() != 0) { - message = "Open pipe failed" + QString::fromStdString(strerror(errno)); QMessageBox::warning( - NULL, - "RsyncUI", - message); - exit(-1); - }else + NULL, + "RsyncUI", + myProcess->errorString(), + QMessageBox::Ok, + QMessageBox::Ok); + return true; + }else if (myProcess->exitCode() != 0) { - if((child_pid = fork()) == -1) - { - perror("fork"); - exit(1); - } - - /* child process */ - if (child_pid == 0) - { - if (type == "r") - { - close(fd[READ]); //Close the READ end of the pipe since the child's fd is write-only - dup2(fd[WRITE], 1); //Redirect stdout to pipe - } - else - { - close(fd[WRITE]); //Close the WRITE end of the pipe since the child's fd is read-only - dup2(fd[READ], 0); //Redirect stdin to pipe - } - - setpgid(child_pid, child_pid); //Needed so negative PIDs can kill children of /bin/sh - //TODO : change for execvp - - if (execlp(argv[0].c_str(), argv[0].c_str(), argv[1].c_str(), argv[2].c_str(), argv[3].c_str(), argv[4].c_str(), argv[5].c_str(), NULL ) == -1) - exit (0); - } - else - { - if (type == "r") - { - close(fd[WRITE]); //Close the WRITE end of the pipe since parent's fd is read-only - } - else - { - close(fd[READ]); //Close the READ end of the pipe since parent's fd is write-only - } - } - - pid = child_pid; - if (type == "r") - { - return fdopen(fd[READ], "r"); - } - return fdopen(fd[WRITE], "w"); + QMessageBox::warning( + NULL, + "RsyncUI", + rsyncErrorStrings[myProcess->exitCode()], + QMessageBox::Ok, + QMessageBox::Ok); + return true; } - return 0; + return false; } - -// close pipe open by popen2 while pid is finished -// fp => file pointer -// pid => pid of the processus open bu popen2 - -int pclose2(FILE * fp, pid_t pid) -{ - int stat; - - fclose(fp); - while (waitpid(pid, &stat, 0) == -1) - { - if (errno != EINTR) - { - stat = -1; - break; - } - } - return stat; -} - diff --git a/tools.h b/tools.h index c6e1b78..22c7420 100644 --- a/tools.h +++ b/tools.h @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include using namespace std; @@ -13,4 +17,7 @@ FILE * popen2(array argv, string type, int & pid); int pclose2(FILE * fp, pid_t pid); +bool testRsyncReturn(QProcess *); + + #endif // TOOLS_H