1 //===========================================
2 // Lumina-Desktop source code
3 // Copyright (c) 2016, Ken Moore
4 // Available under the 3-clause BSD license
5 // See the LICENSE file for full details
6 //===========================================
7 #include "imgDialog.h"
8 #include "ui_imgDialog.h"
9
10 #include <QMessageBox>
11
12 #include <LuminaOS.h>
13 #include <LuminaXDG.h>
14
15 #include <unistd.h>
16 #include <sys/types.h>
17 #include <signal.h>
18
imgDialog(QWidget * parent)19 imgDialog::imgDialog(QWidget *parent) : QDialog(parent), ui(new Ui::imgDialog()){
20 ui->setupUi(this); //load the designer form
21 QString title = tr("Burn Disk Image to Device");
22 if( getuid()==0){ title.append(" ("+tr("Admin Mode")+")"); }
23 this->setWindowTitle(title);
24 ui->frame_running->setVisible(false);
25 ui->frame_setup->setVisible(true);
26 ddProc = 0;
27 unitdiv = 1;
28 //Setup the signals/slots
29 ui->tool_refresh->setIcon( LXDG::findIcon("view-refresh","drive-removable-media-usb-pendrive") );
30 connect(ui->push_cancel, SIGNAL(clicked()), this, SLOT(cancel()) );
31 connect(ui->push_start, SIGNAL(clicked()), this, SLOT(start_process()) );
32 connect(ui->tool_refresh, SIGNAL(clicked()), this, SLOT(loadDeviceList()) );
33 loadDeviceList(); //do the initial load of the available devices
34 //Setup the possible transfer rate units
35 ui->combo_rate_units->clear();
36 ui->combo_rate_units->addItem(tr("Kilobyte(s)"), "k");
37 ui->combo_rate_units->addItem(tr("Megabyte(s)"), "m");
38 ui->combo_rate_units->addItem(tr("Gigabyte(s)"), "g");
39 ui->combo_rate_units->setCurrentIndex(1); //MB
40 //Setup the Process Timer
41 procTimer = new QTimer(this);
42 procTimer->setInterval(1000); //1 second updates
43 connect(procTimer, SIGNAL(timeout()), this, SLOT(getProcStatus()) );
44 //Determine which type of system this is for the process status signal
45 BSD_signal = LOS::OSName().contains("BSD"); //assume everything else is Linux-like
46 }
47
~imgDialog()48 imgDialog::~imgDialog(){
49
50 }
51
loadIMG(QString filepath)52 void imgDialog::loadIMG(QString filepath){
53 ui->label_iso->setText(filepath.section("/",-1)); //only show the filename
54 ui->label_iso->setWhatsThis(filepath); //save the full path for later
55 }
56
57 //============================
58 // PRIVATE SLOTS
59 //============================
start_process()60 void imgDialog::start_process(){
61 //Sanity Checks
62 if( !QFile::exists(ui->combo_devices->currentData().toString()) ){ loadDeviceList(); return; } //USB device no longer available
63 if(!QFile::exists(ui->label_iso->whatsThis()) ){ return; } //IMG file no longer available
64 qDebug() << "Start Process...";
65 //Read the size of the img file
66 QString units = ui->combo_rate_units->currentData().toString();
67 if(units=="k"){ unitdiv = 1024; }
68 else if(units=="m"){ unitdiv = 1024*1024; }
69 else if(units=="g"){ unitdiv = 1024*1024*1024; }
70 qint64 bytes = QFileInfo(ui->label_iso->whatsThis()).size();
71 //qDebug() << "IMG File size:" << bytes;
72 //Set the progressBar maximum
73 ui->progressBar->setRange(0, qRound(bytes/unitdiv) );
74 ui->progressBar->setValue(0);
75 ui->label_dev->setText( ui->combo_devices->currentText() );
76 ui->label_time->setText("0:00");
77 ui->frame_running->setVisible(true);
78 ui->frame_setup->setVisible(false);
79 //qDebug() << "Blocks:" << ui->progressBar->maximum();
80 //Initialize the process
81 if(ddProc==0){
82 ddProc = new QProcess(this);
83 connect(ddProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(procFinished()) );
84 connect(ddProc, SIGNAL(readyRead()), this, SLOT(procInfoAvailable()) );
85 ddProc->setProcessChannelMode(QProcess::MergedChannels);
86 }
87 //Generate the command
88 QString prog; QStringList args;
89 //if( ::getuid()!=0){ prog = "qsudo"; args<<"dd"; }
90 //else{
91 prog = "dd";
92 //}
93 args << "if="+ui->label_iso->whatsThis();
94 args << "of="+ui->combo_devices->currentData().toString();
95 args << "bs="+QString::number(ui->spin_rate_num->value())+units;
96 if(ui->check_sync->isChecked()){ args << "conv=sync"; }
97 //Start the process
98 startTime = QDateTime::currentDateTime();
99 ddProc->start(prog, args);
100 //Start the timer to watch for updates
101 procTimer->start();
102 ui->push_start->setEnabled(false);
103 }
104
cancel()105 void imgDialog::cancel(){
106 if(ddProc==0 || ddProc->state()==QProcess::NotRunning){
107 this->close();
108 }else{
109 //Prompt if the transfer should be cancelled
110 if(QMessageBox::Yes == QMessageBox::question(this, tr("Cancel Image Burn?"), tr("Do you wish to stop the current disk image burn process?")+"\n\n"+tr("Warning: This will leave the USB device in an inconsistent state")) ){
111 ddProc->kill();
112 }
113 }
114 }
115
loadDeviceList()116 void imgDialog::loadDeviceList(){
117 ui->combo_devices->clear();
118 //Probe the system for USB devices
119 QDir devDir("/dev");
120 QString filter = (BSD_signal) ? "da*" : "sd*";
121 QStringList usb = devDir.entryList(QStringList() << filter, QDir::System, QDir::Name);
122 //Filter out any devices which are currently mounted/used
123
124 //Create the list
125 for(int i=0; i<usb.length(); i++){
126 ui->combo_devices->addItem(usb[i], devDir.absoluteFilePath(usb[i]));
127 }
128 }
129
getProcStatus()130 void imgDialog::getProcStatus(){
131 if(ddProc==0 || ddProc->state()!=QProcess::Running ){ return; }
132 QStringList pidlist = LUtils::getCmdOutput("pgrep -S -f \"dd if="+ui->label_iso->whatsThis()+"\"");
133 if(pidlist.isEmpty()){ return; }
134 int pid = pidlist.first().simplified().toInt(); //just use the first pid - the pgrep should be detailed enough to only match one
135 //qDebug() << "Sending signal to show status on PID:" << pid;
136 #ifndef __linux__
137 ::kill(pid, SIGINFO); //On BSD systems, the INFO signal is used to poke dd for status updates
138 #else
139 //LINUX systems do not even have a SIGINFO defined - need to block this off with defines...
140 ::kill(pid, SIGUSR1); //On linux systems, the USR1 signal is used to poke dd for status updates
141 #endif
142 //Now update the elapsed time on the UI
143 int elapsed = startTime.secsTo( QDateTime::currentDateTime() );
144 int min = elapsed/60;
145 int secs = elapsed%60;
146 ui->label_time->setText( QString::number(min)+":"+ (secs < 10 ? "0" : "")+QString::number(secs) );
147 }
148
procInfoAvailable()149 void imgDialog::procInfoAvailable(){
150 lastmsg = ddProc->readAll();
151 if(lastmsg.endsWith("\n")){ lastmsg.chop(1); }
152 //qDebug() << "Got Process Info:" << lastmsg;
153 //Now look for the " bytes transferred" line
154 QStringList records = lastmsg.split("\n").filter(" bytes transferred ");
155 if(!records.isEmpty()){
156 //Update the progress bar
157 //qDebug() << "Got status update:" << records.last();
158 ui->progressBar->setValue( qRound(records.last().section(" bytes",0,0).toDouble()/unitdiv) );
159 }
160 }
161
procFinished()162 void imgDialog::procFinished(){
163 qDebug() << "Process Finished:" << ddProc->exitCode();
164 procTimer->stop();
165 ui->frame_running->setVisible(false);
166 ui->frame_setup->setVisible(true);
167 if(ddProc->exitStatus()==QProcess::NormalExit){
168 if(ddProc->exitCode() !=0 ){
169 if(lastmsg.contains("permission denied", Qt::CaseInsensitive) && LUtils::isValidBinary("qsudo") ){
170 if(QMessageBox::Yes == QMessageBox::question(this, tr("Administrator Permissions Needed"), tr("This operation requires administrator priviledges.")+"\n\n"+tr("Would you like to enable these priviledges?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) ){
171 QProcess::startDetached("qsudo", QStringList() << "lumina-archiver" << "--burn-img" << ui->label_iso->whatsThis());
172 exit(0);
173 }
174 }else{
175 QMessageBox::warning(this, tr("ERROR"), tr("The process could not be completed:")+"\n\n"+lastmsg);
176 }
177 }else{
178 QMessageBox::information(this, tr("SUCCESS"), tr("The image was successfully burned to the device") );
179 this->close();
180 }
181 }
182 }
183