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

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

Setup the new snapshot process as an external process and update the message appropriately. Also make sure that we parse every single line in the log, just to make sure that we don't miss an important message (keep it quiet during the initial startup phase though).

  • Property mode set to 100644
File size: 16.4 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  LPBackend::newSnapshot(ds,name);
303  QMessageBox::information(this,tr("Snapshot Pending"), tr("The new snapshot creation has been added to the queue"));
304  setupUI();
305}
306
307// --- Menu Items Clicked
308void mainUI::slotRevertToSnapshot(QAction *act){
309  QString info = act->whatsThis();
310  QString ds = info.section(":::",0,0);
311  QString subset = info.section(":::",1,1);
312  QString snap = info.section(":::",2,2);
313  qDebug() << "Revert Clicked:" << ds << subset << snap;
314  if(!ds.isEmpty()){
315    //Verify the reversion
316     if( QMessageBox::Yes == QMessageBox::question(this,tr("Verify Snapshot Reversion"),
317             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."),
318             QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ){
319        //Perform the reversion
320        if( !LPBackend::revertSnapshot(ds+subset,snap) ){
321          //Error performing the reversion
322          qDebug() << " - Error:" << ds+subset << snap;
323          QMessageBox::warning(this,tr("Reversion Error"), tr("The snapshot reversion could not be completed successfully."));
324        }else{
325          //Good reversion
326          qDebug() << " - Revert Complete";
327          QMessageBox::information(this,tr("Reversion Success"), tr("The snapshot reversion was completed successfully."));     
328        }
329     }
330  }
331}
332
333void mainUI::slotBrowseSnapshot(QAction *act){
334  QString info = act->whatsThis();
335  QString ds = info.section(":::",0,0);
336  QString subset = info.section(":::",1,1);
337  QString snap = info.section(":::",2,2);
338  //Now let the user select a file within the snapshot to revert
339  QString snapPath = subset+"/.zfs/snapshot/"+snap+"/";
340  QString filepath = QFileDialog::getOpenFileName(this,tr("Revert a file"), snapPath, tr("Backup Files (*)") );
341  qDebug() << "File to revert:" << filepath;
342  //Check to make sure that it is a valid file (within the snapshot)
343  if(filepath.isEmpty() ){
344    qDebug() << " - Cancelled";
345    //action cancelled  -  do nothing
346  }else if(!filepath.startsWith(snapPath)){
347    qDebug() << " - Invalid File";
348    QMessageBox::warning(this, tr("Invalid Snapshot File"), tr("Please select a file from within the chosen snapshot that you wish to revert"));
349  }else{
350    //Revert the file
351    QString newfile = LPBackend::revertSnapshotFile(subset,snap,filepath);
352    if(newfile.isEmpty()){
353      //Error copying the new file over
354      qDebug() << " - Error copying file";
355      QMessageBox::warning(this, tr("Error Reverting File"), QString(tr("An error occurred while tring to revert the file %1. Please try again.")).arg(filepath));
356    }else{
357      //Let the user know the location of the reverted file
358      qDebug() << " - Successful reversion:" << newfile;
359      QMessageBox::information(this, tr("FIle Reverted"), QString(tr("The reverted file is now available at: %1")).arg(newfile) );
360    }
361  }
362  return;
363}
364
365void mainUI::slotAddDataset(QAction *act){
366  QString dataset = act->text();
367  qDebug() << "Start Wizard for new dataset:" << dataset;
368  LPWizard wiz(this);
369  wiz.setDataset(dataset);
370  wiz.exec();
371  //See if the wizard was cancelled or not
372  if(!wiz.cancelled){
373    //run the proper commands to get the dataset enabled
374    if( LPBackend::setupDataset(dataset, wiz.localTime, wiz.totalSnapshots) ){
375      if(wiz.enableReplication){
376         LPBackend::setupReplication(dataset, wiz.remoteHost, wiz.remoteUser, wiz.remotePort, wiz.remoteDataset, wiz.remoteTime);     
377         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!!"));
378      }
379    }
380  }
381  //Now update the UI/Hash
382  setupUI();
383}
384
385void mainUI::on_actionClose_triggered(){
386  this->close();
387}
388
389void mainUI::on_actionKeyNew_triggered(){
390  QString ds = getSelectedDS();
391  qDebug() << "New SSH Key triggered for DS:" << ds;
392  //Get the remote values for this dataset
393  QString remoteHost, user, remotedataset;
394  int port, time;
395  bool ok = LPBackend::replicationInfo(ds, remoteHost, user, port, remotedataset,  time);
396  if(ok){
397    if( !LPBackend::setupSSHKey(remoteHost, user, port) ){
398      QMessageBox::warning(this,tr("Failure"), tr("There was an error while creating the SSH key."));
399    }else{
400      QMessageBox::information(this,tr("Success"), tr("The SSH key was successfully generated."));
401    }
402  }else{
403    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.") );
404  }
405}
406
407void mainUI::on_actionKeyCopy_triggered(){
408  QString ds = getSelectedDS(); 
409  qDebug() << "Copy SSH Key triggered for DS:" << ds;
410  //Get the local hostname
411  char host[1023] = "\0";
412  gethostname(host,1023);
413  QString localHost = QString(host).simplified();
414  qDebug() << " - hostname:" << localHost;
415  //Scan for mounted USB devices
416  QStringList devs = LPBackend::findValidUSBDevices();
417  qDebug() << " - devs:" << devs;
418  if(devs.isEmpty()){
419    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."));
420    return;
421  }
422  //Ask the user which one to save the file to
423  bool ok;
424  QString dev = QInputDialog::getItem(this, tr("Select USB Device"), tr("Available USB Devices:"), devs,0,false,&ok);   
425  if(!ok or dev.isEmpty()){ return; } //cancelled
426  QString devPath = dev.section("(",0,0).simplified();
427  //Now copy the file over
428  ok = LPBackend::copySSHKey(devPath, localHost);
429  if(ok){
430    QMessageBox::information(this,tr("Success"), tr("The public SSH key file was successfully copied onto the USB device."));
431  }else{
432    QMessageBox::information(this,tr("Failure"), tr("The public SSH key file could not be copied onto the USB device."));
433  }
434}
435
436
437// =============
438//      PROTECTED
439// =============
440void mainUI::closeEvent(QCloseEvent *event){
441  //Make sure this window only gets hidden rather than closed
442  // this prevents the entire tray application from closing down as well
443  event->ignore();
444  this->hide();
445}
Note: See TracBrowser for help on using the repository browser.