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

9.2-releasereleng/10.0releng/10.0.1releng/10.0.2releng/10.0.3
Last change on this file since aac7789e was aac7789e, checked in by Ken Moore <ken@…>, 13 months ago

Make sure that the keys button get enabled as well as made visible if appropriate

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