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

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

Clean up the tooltip for the life preserver tray: will now list the latest snapshot for each managed dataset/zpool.
Also clean up the ordering of the available snapshots - they should always list in the proper chronological order for snapshots that life-preserver created, with an additional section at the end for any other snapshots that are available for that data subset.

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