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

9.2-releaseenter/10releng/10.0releng/10.0.1releng/10.0.2releng/10.0.3releng/10.1releng/10.1.1releng/10.1.2
Last change on this file since 33a568a was 33a568a, checked in by Ken Moore <ken@…>, 23 months ago

Make sure the new snapshot button is actively visible/invisible

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