source: src-qt4/life-preserver/mainUI.cpp @ 5d6497b

9.2-releasereleng/10.0releng/10.0.1releng/10.0.2
Last change on this file since 5d6497b was 5d6497b, checked in by Kris Moore <kris@…>, 10 months ago

Fix building on HEAD

  • Property mode set to 100644
File size: 18.8 KB
Line 
1#include "mainUI.h"
2#include "ui_mainUI.h"
3#include <unistd.h>
4
5mainUI::mainUI(QWidget *parent) : QMainWindow(parent), ui(new Ui::mainUI){
6  //Initialize the graphical items
7  ui->setupUi(this);  //load the mainUI.ui file
8  revMenu = new QMenu();
9  brMenu = new QMenu();
10  addMenu = new QMenu();
11  //Setup the menu system
12  ui->tool_revert->setMenu(revMenu);
13  ui->tool_browse->setMenu(brMenu);
14  ui->tool_add->setMenu(addMenu);
15  connect(revMenu,SIGNAL(triggered(QAction*)),this,SLOT(slotRevertToSnapshot(QAction*)) );
16  connect(brMenu,SIGNAL(triggered(QAction*)),this,SLOT(slotBrowseSnapshot(QAction*)) );
17  connect(addMenu, SIGNAL(triggered(QAction*)),this,SLOT(slotAddDataset(QAction*)) );
18  //Setup the Key menu items (static items, never changed)
19  keyMenu = new QMenu();
20    keyMenu->addAction(ui->actionKeyNew); //action from designer
21    keyMenu->addAction(ui->actionKeyCopy); //action from designer
22  ui->tool_keys->setMenu(keyMenu);
23  //Setup the update frequency limiter
24  freqTimer = new QTimer();
25        freqTimer->setSingleShot(true);
26        freqTimer->setInterval(15000);
27        connect(freqTimer, SIGNAL(timeout()), this, SLOT(setupUI()) );
28}
29
30mainUI::~mainUI(){
31       
32}
33
34void mainUI::setupUI(){
35  //Initialize the Hash (make sure it is not run too frequently - causes kernel panics)
36  if(lastUpdate.isNull() || lastUpdate.addSecs(15) < QTime::currentTime() ){
37    enableButtons(false); //disable the buttons temporarily
38    lastUpdate = QTime::currentTime();   
39    qDebug() << "Updating the database";
40    ui->statusbar->showMessage(tr("Updating the database"),0);
41    updateHash();
42    ui->statusbar->clearMessage();
43  }else{
44    freqTimer->start();
45  }
46  //Update the display
47  updateUI();
48  updateMenus();
49}
50
51LPDataset mainUI::newDataset(QString ds){
52  //subroutine to create and fill a new dataset with system information
53  qDebug() << "New Dataset: " << ds;
54  LPDataset DSC;
55  //List all the mountpoints in this dataset
56  QStringList subsets = LPBackend::listDatasetSubsets(ds);
57  QStringList lpsnaps = LPBackend::listLPSnapshots(ds);
58  //populate the list of snapshots available for each mountpoint
59  for(int i=0; i<subsets.length(); i++){
60    //qDebug() << "Subset:" << subsets[i];
61    QStringList snaps = LPBackend::listSnapshots(subsets[i]);
62    //qDebug() << " - Snapshots:" << snaps;
63    if(snaps.isEmpty()){
64      //invalid subset - remove it from the list
65      subsets.removeAt(i);
66      i--;
67    }else{
68      QStringList subsnaps;
69      //only list the valid snapshots that life preserver created
70      for(int s=0; s<lpsnaps.length(); s++){
71        int index = snaps.indexOf(lpsnaps[s]);
72        if(index > -1){ subsnaps << lpsnaps[s]; snaps.removeAt(index); }
73      }
74      //Now list all the other available snapshots (no certain ordering)
75      if(!snaps.isEmpty()){
76        subsnaps << "--"; //so we know that this is a divider between the sections
77        subsnaps << snaps;
78      }
79      DSC.subsetHash.insert(subsets[i],subsnaps); //add it to the internal container hash
80    }
81  }
82  //Get the time for the latest life-preserver snapshot (and total number)
83  //Find the index for the current list
84  int ci = 0;
85  while(ci < CLIST.length()){
86    if(CLIST[ci].startsWith(ds+":::")){ break; }
87    else{ ci++; }
88  }
89  if(CLIST.isEmpty()){ ci = -1; } //catch for empty list
90  if(DSC.subsetHash.size() < 1){
91    DSC.numberOfSnapshots = "0";
92    DSC.latestSnapshot= "";
93  }else{
94    DSC.numberOfSnapshots = QString::number(lpsnaps.length());
95    if(lpsnaps.isEmpty()){ DSC.latestSnapshot=""; }
96    else if(ci > -1 && ci < CLIST.length()){ 
97      QString sna = CLIST[ci].section(":::",1,1);
98      if(sna != "-"){ DSC.latestSnapshot= sna; }
99      else{ DSC.latestSnapshot = ""; }     
100    }else{ DSC.latestSnapshot=lpsnaps[0]; }
101  }
102  //List the replication status
103  if(RLIST.contains(ds) && (ci > -1)){ 
104    QString rep = CLIST[ci].section(":::",2,2);
105    if(rep != "-"){ DSC.latestReplication = rep; }
106    else{ DSC.latestReplication= tr("Enabled"); }
107  }else{ 
108    DSC.latestReplication= tr("Disabled");
109  }
110  //Return the dataset
111  return DSC;
112}
113
114void mainUI::enableButtons(bool enable){
115  if(enable){
116    //special care must be taken here to only enable the ones that are valid
117    updateMenus();
118  }else{
119    ui->tool_add->setEnabled(false);
120    ui->tool_browse->setEnabled(false);
121    ui->tool_config->setEnabled(false);
122    ui->tool_keys->setEnabled(false);
123    ui->tool_newsnapshot->setEnabled(false);
124    ui->tool_remove->setEnabled(false);
125    ui->tool_revert->setEnabled(false);
126  }
127 
128}
129
130// =================
131//    PRIVATE FUNCTIONS
132// =================
133void mainUI::updateHash(QString ds){
134  //qDebug() << "Get replication targets";
135  RLIST = LPBackend::listReplicationTargets(); //update list of replication datasets
136  //qDebug() << "Get possible datasets";
137  SLIST = LPBackend::listPossibleDatasets();
138  //qDebug() << "List current status";
139  CLIST = LPBackend::listCurrentStatus();
140  //qDebug() << "Check hash";
141  if(HLIST.contains(ds) && !ds.isEmpty()){
142    //only update the entry for the given dataset
143    HLIST.insert(ds, newDataset(ds)); //will overwrite the current entry in the hash
144  }else{
145    //Clear and fill the hash
146    //qDebug() << "Clear hash";
147    HLIST.clear();
148    //qDebug() << "List datasets";
149    QStringList dsList = LPBackend::listDatasets();
150    for(int i=0; i<dsList.length(); i++){
151      HLIST.insert( dsList[i], newDataset(dsList[i]) );
152    }
153  }
154  //qDebug() << "Done with Hash Update";
155}
156
157void mainUI::updateUI(){
158  ui->treeWidget->clear();
159  QStringList dsList = HLIST.keys();
160  for(int i=0; i<dsList.length(); i++){
161    //Create the item for the treeWidget
162    // [ dataset, latest snapshot, num snapshots, is Replicated ]
163    QStringList cols;
164          cols << dsList[i]; //[0] - dataset
165          cols << HLIST[dsList[i]].latestSnapshot; // [1] newest snapshot name
166          cols << HLIST[dsList[i]].numberOfSnapshots; // [2] total number of snapshots
167          cols << HLIST[dsList[i]].latestReplication; // [3] latest replication
168    //Add the item to the widget
169    ui->treeWidget->addTopLevelItem( new QTreeWidgetItem(cols) );
170  }
171  //Now adjust the columns in the widget
172  for(int i=0; i<4; i++){
173    ui->treeWidget->resizeColumnToContents(i);
174  }
175  //Now make sure that the add button menu only shows the available datasets
176  addMenu->clear();
177  for(int i=0; i<SLIST.length(); i++){
178    if(!HLIST.contains(SLIST[i])){ addMenu->addAction( new QAction(SLIST[i],this) ); }
179  }
180  if(addMenu->isEmpty()){ ui->tool_add->setEnabled(false); }
181  else{ ui->tool_add->setEnabled(true); }
182}
183
184void mainUI::updateMenus(){
185  //Reset the button menu's to correspond to the selected dataset
186  QString ds = getSelectedDS();
187  //Enable/disable the remove/config buttons if nothing selected
188  if(ds.isEmpty()){
189    ui->tool_remove->setVisible(false);
190    ui->tool_config->setVisible(false);
191    ui->tool_newsnapshot->setVisible(false);
192  }else{
193    ui->tool_remove->setVisible(true); ui->tool_remove->setEnabled(true);
194    ui->tool_config->setVisible(true);  ui->tool_config->setEnabled(true);
195    ui->tool_newsnapshot->setVisible(true); ui->tool_newsnapshot->setEnabled(true);
196  }
197  //Enabled/disable the SSH key management
198  if(RLIST.contains(ds) && !ds.isEmpty()){
199    ui->tool_keys->setVisible(true);
200    ui->tool_keys->setEnabled(true);
201  }else{
202    ui->tool_keys->setVisible(false);
203  }
204  //check for a valid ds/snapshot combination
205  bool ok = !ds.isEmpty();
206  if(ok){ ok = HLIST.contains(ds); }
207  if(ok){ ok = (HLIST[ds].numberOfSnapshots.toInt() > 0); }
208  //Now set the items appropriately
209  revMenu->clear();
210  brMenu->clear();
211  if(ok){
212    //Reset the Menu Contents
213    QStringList subsets = HLIST[ds].subsets(); 
214    for(int i=0; i<subsets.length(); i++){
215      //Build the menu of snapshots for this subset
216        QStringList snaps = HLIST[ds].snapshots(subsets[i]);
217        if(snaps.isEmpty()){ continue; }
218        QMenu *menu = new QMenu(subsets[i],this);
219        for(int s =0; s<snaps.length(); s++){
220          if(snaps[s] == "--"){ menu->addSeparator(); }
221          else{
222            QAction *act = new QAction(snaps[s],this);
223                act->setWhatsThis(ds+":::"+subsets[i]+":::"+snaps[s]);
224            menu->addAction(act);
225          }
226        }
227        revMenu->addMenu(menu);
228        brMenu->addMenu(menu);
229    }       
230    //Enable the buttons if appropriate
231    if(revMenu->isEmpty()){
232      ui->tool_revert->setEnabled(false);
233      ui->tool_browse->setEnabled(false);
234    }else{
235      ui->tool_revert->setEnabled(true);
236      ui->tool_browse->setEnabled(true);
237    }
238  }else{
239    //Disable the buttons
240    ui->tool_revert->setEnabled(false);
241    ui->tool_browse->setEnabled(false);
242  }
243}
244
245QString mainUI::getSelectedDS(){
246  //Get the currently selected dataset
247  QString ds="";
248  if(ui->treeWidget->topLevelItemCount() > 0){
249    if( ui->treeWidget->currentItem() != 0){
250      ds = ui->treeWidget->currentItem()->text(0); //first column
251    }
252  }
253  return ds;
254}
255
256// =================
257//        PRIVATE SLOTS
258// =================
259//  --- Buttons Clicked
260void mainUI::on_treeWidget_itemSelectionChanged(){
261  if(ui->statusbar->currentMessage().isEmpty()){
262    //only update the menu's if they are not currently disabled while a process is running
263    updateMenus();
264  }
265}
266
267void mainUI::on_tool_config_clicked(){
268  QString ds = getSelectedDS();
269  if(ds.isEmpty()){ return; }
270  LPConfig CFG(this);
271  CFG.loadDataset(ds, RLIST.contains(ds));
272  CFG.exec();
273  //Now check for return values and update appropriately
274  bool change = false;
275  if(CFG.localChanged){
276    enableButtons(false);
277    ui->statusbar->showMessage(QString(tr("Configuring dataset: %1")).arg(ds),0);
278    LPBackend::setupDataset(ds, CFG.localSchedule, CFG.localSnapshots);
279    ui->statusbar->clearMessage();
280    change = true;
281  }
282  if(CFG.remoteChanged){
283    change = true;
284    enableButtons(false);
285    if(CFG.isReplicated){
286      ui->statusbar->showMessage(QString(tr("Configuring replication: %1")).arg(ds),0);
287      LPBackend::setupReplication(ds, CFG.remoteHost, CFG.remoteUser, CFG.remotePort, CFG.remoteDataset, CFG.remoteFreq);
288      QMessageBox::information(this,tr("Reminder"),tr("Don't forget to save your SSH key to a USB stick so that you can restore your system from the remote host later!!"));
289    }else{
290      ui->statusbar->showMessage(QString(tr("Removing replication: %1")).arg(ds),0);
291      LPBackend::removeReplication(ds);
292    }
293    ui->statusbar->clearMessage();
294  }
295  //Now update the UI if appropriate
296  if(change){
297    setupUI();
298  }
299}
300
301void mainUI::on_tool_remove_clicked(){
302  QString ds = getSelectedDS();
303  if(!ds.isEmpty()){
304    //Verify the removal of the dataset
305    if( QMessageBox::Yes == QMessageBox::question(this,tr("Verify Dataset Backup Removal"),tr("Are you sure that you wish to cancel automated snapshots and/or replication of the following dataset?")+"\n\n"+ds,QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ){           
306      enableButtons(false);
307      //verify the removal of all the snapshots for this dataset
308      QStringList snaps = LPBackend::listLPSnapshots(ds);
309      if(!snaps.isEmpty()){
310        if( QMessageBox::Yes == QMessageBox::question(this,tr("Verify Snapshot Deletion"),tr("Do you wish to remove the local snapshots for this dataset?")+"\n"+tr("WARNING: This is a permanant change that cannot be reversed"),QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ){
311          //Remove all the snapshots
312          ui->statusbar->showMessage(QString(tr("%1: Removing snapshots")).arg(ds),0);
313          for(int i=0; i<snaps.length(); i++){
314            LPBackend::removeSnapshot(ds,snaps[i]);
315          }
316          ui->statusbar->clearMessage();
317        }
318      }
319      //Remove the dataset from life-preserver management
320      if(RLIST.contains(ds)){ 
321        ui->statusbar->showMessage(QString(tr("%1: Disabling Replication")).arg(ds),0);
322        LPBackend::removeReplication(ds); 
323        ui->statusbar->clearMessage();     
324      }
325      ui->statusbar->showMessage(QString(tr("%1: Disabling Life-Preserver Management")).arg(ds),0);
326      LPBackend::removeDataset(ds);
327      ui->statusbar->clearMessage();
328    }
329  }
330  setupUI();
331}
332
333void mainUI::on_tool_newsnapshot_clicked(){
334  QString ds = getSelectedDS();
335  if(ds.isEmpty()){return; }
336  //Get the new snapshot name from the user
337  bool ok;
338  QString name = QInputDialog::getText(this,tr("New Snapshot Name"), tr("Snapshot Name:"), QLineEdit::Normal, tr("Name"), &ok, 0, Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly | Qt::ImhDigitsOnly );
339  if(!ok || name.isEmpty()){ return; } //cancelled
340  qDebug() << "Creating a new snapshot:" << ds << name;
341  //Now create the new snapshot
342  LPBackend::newSnapshot(ds,name);
343  QMessageBox::information(this,tr("Snapshot Pending"), tr("The new snapshot creation has been added to the queue"));
344  setupUI();
345}
346
347// --- Menu Items Clicked
348void mainUI::slotRevertToSnapshot(QAction *act){
349  QString info = act->whatsThis();
350  QString ds = info.section(":::",0,0);
351  QString subset = info.section(":::",1,1);
352  QString snap = info.section(":::",2,2);
353  qDebug() << "Revert Clicked:" << ds << subset << snap;
354  if(!ds.isEmpty()){
355    //Verify the reversion
356     if( QMessageBox::Yes == QMessageBox::question(this,tr("Verify Snapshot Reversion"),
357             QString(tr("Are you sure that you wish to revert %1 to the selected snapshot?")).arg(subset)+"\n"+tr("WARNING: This will result in the loss of any data not previously backed up."),
358             QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ){
359        //Perform the reversion
360        enableButtons(false);
361        ui->statusbar->showMessage(QString(tr("%1: Reverting dataset: %2")).arg(ds,subset),0);
362        if( !LPBackend::revertSnapshot(ds+subset,snap) ){
363          //Error performing the reversion
364          qDebug() << " - Error:" << ds+subset << snap;
365          QMessageBox::warning(this,tr("Reversion Error"), tr("The snapshot reversion could not be completed successfully."));
366        }else{
367          //Good reversion
368          qDebug() << " - Revert Complete";
369          QMessageBox::information(this,tr("Reversion Success"), tr("The snapshot reversion was completed successfully."));     
370        }
371        ui->statusbar->clearMessage();
372        enableButtons(true);
373     }
374  }
375}
376
377void mainUI::slotBrowseSnapshot(QAction *act){
378  QString info = act->whatsThis();
379  QString ds = info.section(":::",0,0);
380  QString subset = info.section(":::",1,1);
381  QString snap = info.section(":::",2,2);
382  //Now let the user select a file within the snapshot to revert
383  QString snapPath = subset+"/.zfs/snapshot/"+snap+"/";
384  QString filepath = QFileDialog::getOpenFileName(this,tr("Revert a file"), snapPath, tr("Backup Files (*)") );
385  qDebug() << "File to revert:" << filepath;
386  //Check to make sure that it is a valid file (within the snapshot)
387  if(filepath.isEmpty() ){
388    qDebug() << " - Cancelled";
389    //action cancelled  -  do nothing
390  }else if(!filepath.startsWith(snapPath)){
391    qDebug() << " - Invalid File";
392    QMessageBox::warning(this, tr("Invalid Snapshot File"), tr("Please select a file from within the chosen snapshot that you wish to revert"));
393  }else{
394    enableButtons(false);
395    ui->statusbar->showMessage(QString(tr("%1: Reverting File: %2")).arg(ds,filepath.remove(".zfs/snapshot/"+snap+"/").replace(QDir::homePath(),"~")),0);
396    //Revert the file
397    QString newfile = LPBackend::revertSnapshotFile(subset,snap,filepath);
398    if(newfile.isEmpty()){
399      //Error copying the new file over
400      qDebug() << " - Error copying file";
401      QMessageBox::warning(this, tr("Error Reverting File"), QString(tr("An error occurred while tring to revert the file %1. Please try again.")).arg(filepath));
402    }else{
403      //Let the user know the location of the reverted file
404      qDebug() << " - Successful reversion:" << newfile;
405      QMessageBox::information(this, tr("FIle Reverted"), QString(tr("The reverted file is now available at: %1")).arg(newfile) );
406    }
407    ui->statusbar->clearMessage();
408    enableButtons(true);
409  }
410  return;
411}
412
413void mainUI::slotAddDataset(QAction *act){
414  QString dataset = act->text();
415  qDebug() << "Start Wizard for new dataset:" << dataset;
416  LPWizard wiz(this);
417  wiz.setDataset(dataset);
418  wiz.exec();
419  //See if the wizard was cancelled or not
420  if(!wiz.cancelled){
421    enableButtons(false);
422    ui->statusbar->showMessage(QString(tr("Enabling dataset management: %1")).arg(dataset),0);
423    //run the proper commands to get the dataset enabled
424    if( LPBackend::setupDataset(dataset, wiz.localTime, wiz.totalSnapshots) ){
425      if(wiz.enableReplication){
426         LPBackend::setupReplication(dataset, wiz.remoteHost, wiz.remoteUser, wiz.remotePort, wiz.remoteDataset, wiz.remoteTime);     
427         QMessageBox::information(this,tr("Reminder"),tr("Don't forget to save your SSH key to a USB stick so that you can restore your system from the remote host later!!"));
428      }
429    }
430    ui->statusbar->clearMessage();
431  }
432  //Now update the UI/Hash
433  setupUI();
434}
435
436void mainUI::on_actionClose_triggered(){
437  this->close();
438}
439
440void mainUI::on_actionKeyNew_triggered(){
441  QString ds = getSelectedDS();
442  if(ds.isEmpty()){ return; }
443  qDebug() << "New SSH Key triggered for DS:" << ds;
444  enableButtons(false);
445  ui->statusbar->showMessage(QString(tr("%1: Setting up SSH Key")).arg(ds),0);
446  //Get the remote values for this dataset
447  QString remoteHost, user, remotedataset;
448  int port, time;
449  bool ok = LPBackend::replicationInfo(ds, remoteHost, user, port, remotedataset,  time);
450  if(ok){
451    if( !LPBackend::setupSSHKey(remoteHost, user, port) ){
452      QMessageBox::warning(this,tr("Failure"), tr("There was an error while creating the SSH key."));
453    }else{
454      QMessageBox::information(this,tr("Success"), tr("The SSH key was successfully generated."));
455    }
456  }else{
457    QMessageBox::warning(this,tr("Failure"), tr("There was an error in retrieving the remote replication information for this dataset. Please ensure that replication is enabled and try agin.") );
458  }
459  ui->statusbar->clearMessage();
460  enableButtons(true);
461}
462
463void mainUI::on_actionKeyCopy_triggered(){
464  QString ds = getSelectedDS(); 
465  if(ds.isEmpty()){ return; }
466  qDebug() << "Copy SSH Key triggered for DS:" << ds;
467  //Get the local hostname
468  char host[1023] = "\0";
469  gethostname(host,1023);
470  QString localHost = QString(host).simplified();
471  qDebug() << " - hostname:" << localHost;
472  //Scan for mounted USB devices
473  QStringList devs = LPBackend::findValidUSBDevices();
474  qDebug() << " - devs:" << devs;
475  if(devs.isEmpty()){
476    QMessageBox::warning(this,tr("No Valid USB Devices"), tr("No valid USB devices could be found. Please mount a FAT32 formatted USB stick and try again."));
477    return;
478  }
479  //Ask the user which one to save the file to
480  bool ok;
481  QString dev = QInputDialog::getItem(this, tr("Select USB Device"), tr("Available USB Devices:"), devs,0,false,&ok);   
482  if(!ok or dev.isEmpty()){ return; } //cancelled
483  QString devPath = dev.section("(",0,0).simplified();
484  //Now copy the file over
485  ok = LPBackend::copySSHKey(devPath, localHost);
486  if(ok){
487    QMessageBox::information(this,tr("Success"), tr("The public SSH key file was successfully copied onto the USB device."));
488  }else{
489    QMessageBox::information(this,tr("Failure"), tr("The public SSH key file could not be copied onto the USB device."));
490  }
491}
492
493// =============
494//      PROTECTED
495// =============
496void mainUI::closeEvent(QCloseEvent *event){
497  //Make sure this window only gets hidden rather than closed
498  // this prevents the entire tray application from closing down as well
499  event->ignore();
500  this->hide();
501}
Note: See TracBrowser for help on using the repository browser.