class BackupPanel(EditPanel, gui.BackupPanel): ''' classdocs ''' def __init__(self, parent): ''' Constructor ''' log.info("***BackupPanel.init") gui.BackupPanel.__init__(self, parent) self.btnAddFolder.SetBitmapLabel(wx.Bitmap(os.path.join(const.PixmapDir, "add.png"))) self.db = DB() self.config = Config.get_config() self.state = ViewState self.update_data(False) self.nbBackup.SetSelection(0) self.clear() self.nbBackup.Layout() self.Fit() self.radSchedDailyWeekly.SetValue(True) if self.lstItems.GetCount() > 0: self.lstItems.SetSelection(0) self.onItemSelected(None) # self.onNotifyEmail(None) self.image = wx.Bitmap(os.path.join(const.PixmapDir, "backup.png")) self.title = _("Backups") self.onBackupSchedule(None) log.trace("Done BackupPanel.init") def update_data(self, set_selection=True): # The next line should be # for child in self.pnlScheduleTab.GetChildren(): # if child.GetName().find("cboTime") == 0: # but there is a bug in wxFormBuilder. It doesn't set the name attribute. # See http://sourceforge.net/tracker/?func=detail&aid=3187563&group_id=135521&atid=733136 for name in dir(self): if name.find("cboTime") == 0: child = self.__getattribute__(name) child.Clear() child.AppendItems(const.HoursOfDay) child.SetSelection(0) if name.find("cboDay") == 0: child = self.__getattribute__(name) child.Clear() child.AppendItems(const.ShortDaysOfWeek) child.SetSelection(0) if name.find("cboMonthDay") == 0: child = self.__getattribute__(name) child.Clear() child.AppendItems([str(i) for i in xrange(1, 32)]) child.SetSelection(0) self.txtFolders.Clear() self.lstExcludeTypes.Clear() self.lstExcludeTypes.AppendItems(self.config.file_types.keys()) # Lets update this in a smart fasion. Need to keep the current selection if possible old_sel = self.cboStore.GetStringSelection() self.cboStore.Clear() self.cboStore.AppendItems(self.config.storage.keys()) self.cboStore.SetStringSelection(old_sel) # Lastly - lets reload the backup list self.update_backup_list(set_selection) def update_backup_list(self, set_selection=True): sel = self.lstItems.GetStringSelection() # Look for new items backups = self.lstItems.GetItems() keys = self.config.backups.keys() keys.sort() for item in keys: if not item in backups: # new item becomes selected (hopefully the first) sel = item break self.lstItems.Clear() self.lstItems.AppendItems(keys) if set_selection: self.lstItems.SetStringSelection(sel) self.onItemSelected(None) ######################################################################3 # # EVENTS # ######################################################################3 def onHistory(self, event): name = self.lstItems.GetStringSelection() if len(name) > 0: self.history(name) def onRun(self, event): name = self.lstItems.GetStringSelection() if len(name) > 0: self.run_backup(name) def onAddFolder(self, event): dlog = wx.DirDialog(self, _("Select a folder to back up"), "/home") ret = dlog.ShowModal() if ret == wx.ID_OK: folders = self.text_to_list(self.txtFolders.GetValue()) folders.append(dlog.GetPath()) self.txtFolders.Clear() self.txtFolders.AppendText("\n".join(folders)) def onBackupSchedule(self, event): if self.radSchedAdvanced.GetValue(): self.pnlAdvanced.Show() else: self.pnlAdvanced.Hide() ###################################################################### # # Save and Load # ###################################################################### def update_state(self): if self.state == ViewState: self.lblName.Show(True) self.txtName.Show(False) if self.state == NewState: self.lblName.Show(False) self.txtName.Show(True) self.onBackupSchedule(None) self.Fit() self.Refresh() def clear(self): b = Backup(EmptyName) self.show_backup(b) self.nbBackup.SetSelection(0) def delete(self, name): # Lets get some statistics runs = self.db.runs(backupname=name) num_runs = len(runs) size = 0 for run in runs: size += run.size if num_runs > 0: msg = _("Backup '{backup}' has {numruns} runs stored, " \ "totalling {size} of remote data.\n" \ "Are you sure you want to delete the backup definition?\n" \ "(hint - its usually better to just deactivate the backup)").format(\ backup=name, numruns=num_runs, size=utils.readable_form(size)) mbox = OptionDialog(self, msg, _("Delete Backup Definition"), _("Also delete all backup data stored remotely\nNote that this cannot be undone.")) if mbox.ShowModal() != wx.ID_OK: return delete_offsite_data = mbox.chkOption.GetValue() else: msg = _("Backup '{backup}' has never run. Are you " \ "sure you want to delete the backup definition?").format(backup=name) if dlg.OkCancel(self, msg, _("Confirm Delete")) != wx.ID_OK: return delete_offsite_data = False with ProgressDialog(self, _("Deleting"), _("Deleting backup %s%s.\nPlease wait...") % (name, " and all offsite data" if delete_offsite_data else "")): self.delete_backup(name, delete_offsite_data) import time time.sleep(3) self.clear() self.state = ViewState app.broadcast_update() def show(self, name): try: backup = self.config.backups[name] self.state = ViewState self.show_backup(backup) except Exception as e: # Missing backup! dlg.Warn(self, _("The backup '{backup}' seems to be corrupt. {error}").format(backup=name, error=str(e))) # self.update_backup_list() # self.state = ViewState # self.clear() def show_backup(self, b): # General Information self.txtName.SetValue(b.name) self.lblName.SetLabel(b.name) self.chkActive.SetValue(b.active) # Folder Information self.txtFolders.Clear() self.txtFolders.AppendText("\n".join(b.include_folders)) self.chkPackages.SetValue(b.include_packages) # Exclusions self.lstExcludeTypes.SetCheckedStrings(b.exclude_types) self.txtExcludePatterns.Clear() self.txtExcludePatterns.AppendText("\n".join(b.exclude_patterns)) # Destination self.cboStore.SetStringSelection(b.store) self.chkEncrypt.SetValue(b.encrypt) self.chkVerify.SetValue(b.verify) # Schedule if b.sched_type == "custom": self.radSchedAdvanced.SetValue(True) incr, full = b.sched_times.split("\n") self.txtCronIncr.SetValue(incr) self.txtCronFull.SetValue(full) else: # itime, dummy = incr.split("/") # iday not used # ftime, fday = full.split("/") time, day = b.sched_times.split("/") if b.sched_type == "daily/weekly": self.radSchedDailyWeekly.SetValue(True) self.cboTime1.SetStringSelection(time) self.cboDay1.SetStringSelection(day) elif b.sched_type == "daily/monthly": self.radSchedDailyMonthly.SetValue(True) self.cboTime2.SetStringSelection(time) self.cboMonthDay2.SetStringSelection(day) elif b.sched_type == "hourly/weekly": self.radSchedHourlyWeekly.SetValue(True) self.cboTime3.SetStringSelection(time) self.cboDay3.SetStringSelection(day) elif b.sched_type == "none/daily": self.radSchedNoneDaily.SetValue(True) self.cboTime4.SetStringSelection(time) elif b.sched_type == "none/weekly": self.radSchedNoneWeekly.SetValue(True) self.cboDay5.SetStringSelection(day) self.cboTime5.SetStringSelection(time) else: raise Exception(_("This backup is corrupt. Invalid schedule type")) # Notifications self.chkNotifyMsg.SetValue(b.notify_msg) self.chkNotifyEmail.SetValue(b.notify_email) self.chkShutdown.SetValue(b.shutdown_after) self.update_state() def text_to_list(self, text): list = [item.strip() for item in text.split("\n") if len(item.strip()) > 0] return list def get_time_str(self, cronitem): hour = cronitem.hour().render() if not hour.isdigit(): hour = "19" if len(hour) == 1: hour = '0' + hour min = cronitem.minute().render() if not min.isdigit(): min = "00" if len(min) == 1: min = '0' + min time = "%s:%s" % (hour, min) return time def get_dow(self, cronitem): dow = cronitem.dow().render() if not dow.isdigit(): dow = "0" return int(dow) def get_dom(self, cronitem): dom = cronitem.dom().render() if not dom.isdigit(): dom = "0" return int(dom) def save(self): # BUILD THE BACKUP if len(self.txtName.GetValue()) == 0: raise Exception(_("Backup name cannot be blank")) if self.chkEncrypt.GetValue() and not self.config.data_passphrase: raise Exception(_("You cannot select encryption when the passphrase is blank (see Configuration page).")) if self.txtName.GetValue() == EmptyName: raise Exception(_("You need to provide a proper backup name")) try: # Create the new backup object b = Backup(self.txtName.GetValue()) # General Information b.active = self.chkActive.GetValue() # Folder Information b.include_folders = self.text_to_list(self.txtFolders.GetValue()) b.include_packages = self.chkPackages.GetValue() # Exclusions b.exclude_types = list(self.lstExcludeTypes.GetCheckedStrings()) # returns a tuple, convert to array b.exclude_patterns = self.text_to_list(self.txtExcludePatterns.GetValue()) # Destination b.store = self.cboStore.GetStringSelection() b.encrypt = self.chkEncrypt.GetValue() b.verify = self.chkVerify.GetValue() # Schedule if self.radSchedAdvanced.GetValue(): b.sched_type = "custom" b.sched_times = "%s\n%s" % (self.txtCronIncr.GetValue(), self.txtCronFull.GetValue()) else: if self.radSchedDailyWeekly.GetValue(): b.sched_type = "daily/weekly" time = self.cboTime1.GetStringSelection() day = self.cboDay1.GetStringSelection() elif self.radSchedDailyMonthly.GetValue(): b.sched_type = "daily/monthly" time = self.cboTime2.GetStringSelection() day = self.cboMonthDay2.GetStringSelection() elif self.radSchedHourlyWeekly.GetValue(): b.sched_type = "hourly/weekly" time = self.cboTime3.GetStringSelection() day = self.cboDay3.GetStringSelection() elif self.radSchedNoneDaily.GetValue(): b.sched_type = "none/daily" time = self.cboTime4.GetStringSelection() day = "*" elif self.radSchedNoneWeekly.GetValue(): b.sched_type = "none/weekly" time = self.cboTime5.GetStringSelection() day = self.cboDay5.GetStringSelection() else: raise Exception(_("Corrupt backup")) b.sched_times = time + "/" + day # Notifications b.notify_msg = self.chkNotifyMsg.GetValue() b.notify_email = self.chkNotifyEmail.GetValue() b.shutdown_after = self.chkShutdown.GetValue() b.check() except Exception as e: raise e if self.state == ViewState: # Delete the old name oldname = self.lstItems.GetStringSelection() try: del self.config.backups[oldname] except: pass self.config.backups[b.name] = b self.config.save() self.update_backup_list() # Attempt to save the crontab. If this fails, the backup was corrupt. # But it has been saved. So that is a problem update_crontab(self.config.backups) ######################################################################3 # # Misc Routines # ######################################################################3 def hour_min_from_str(self, str): hour, min = str.split(":") return int(hour), int(min) def delete_backup(self, name, delete_offsite_data): # Delete the database runs. backup = self.config.backups[name] # Read the runs dummy = self.db.runs(name) success = True try: if delete_offsite_data: wx.Yield() store = self.config.storage[backup.store].copy() store.delete_backup_data(name) wx.Yield() self.db.delete_backup(name) except: # Most likely this will happen with a corrupt backup object. # We dont want that corruption to stop the deletion. success = False # Now delete the configuration. wx.Yield() del self.config.backups[name] update_crontab(self.config.backups) self.config.save() self.update_backup_list() if not success: dlg.Warn(self, _("There were errors during the delete. You should check/delete the offsite store manually."), _("Error During Delete")) def history(self, default_name): # Open the file list window win = HistoryWindow(self, default_name) win.Show() def run_backup(self, backup_name): win = RunBackupWindow(self, backup_name) win.Show()
class ConfigPanel(gui.ConfigPanel): ''' classdocs ''' def __init__(self, parent): ''' Constructor ''' log.info("***ConfigPanel.init") gui.ConfigPanel.__init__(self, parent) self.config = Config.get_config() self.db = DB() self.state = ViewState self.update_filetype_list() self.clear_filetype() self.image = wx.Bitmap(os.path.join(const.PixmapDir, "configure.png")) self.title = _("Configuration") if self.lstFileTypes.GetCount() > 0: self.lstFileTypes.SetSelection(0) self.onFileType(None) self.show_mail() self.txtMailServer.SetFocus() self.nb_config.SetSelection(0) self.show_security() self.pwd_hidden = True self.mail_hidden = True log.trace("Done ConfigPanel.init") def update_data(self): pass def update_filetype_list(self): self.lstFileTypes.Clear() self.lstFileTypes.AppendItems(self.config.file_types.keys()) ######################################################################3 # # FILE TYPE EVENTS # ######################################################################3 def onSaveTypes(self, event): self.save_filetype() self.state = ViewState self.update_state() self.lstFileTypes.SetStringSelection(self.txtName.GetValue()) def onFileType(self, event): # Get the name to be showed name = self.lstFileTypes.GetStringSelection() if len(name) == 0: return # Load it try: list = self.config.file_types[name] self.state = ViewState self.show_filetype(name, list) except Exception: # Missing backup! dlg.Warn(self, _("That File Type seems to be missing or corrupt.")) self.update_filetype_list() self.state = ViewState self.clear_filetype() return def onDelete(self, event): # Get the name to be showed name = self.lstFileTypes.GetStringSelection() if len(name) == 0: return if dlg.OkCancel(self, _("Delete File Type definition %s and all its data! Are you sure?") % name) == wx.ID_OK: self.delete_filetype(name) self.clear_filetype() self.state = ViewState def onNew(self, event): log.info("New!") self.state = NewState self.clear_filetype() self.txtName.SetFocus() def onName(self, event): self.update_state() def onSSL(self, event): if self.chkMailSSL.GetValue(): self.txtMailPort.SetValue("465") else: self.txtMailPort.SetValue("25") ######################################################################3 # # EMAIL EVENTS # ######################################################################3 def onHideMailPassword(self, event): if self.mail_hidden: self.txtMailPassword.SetWindowStyle(wx.NORMAL) self.mail_hidden = False self.btnHideMailPassword.SetLabel("Hide") else: self.txtMailPassword.SetWindowStyle(wx.TE_PASSWORD) self.mail_hidden = True self.btnHideMailPassword.SetLabel("Show") def onMailAuth(self, event): auth = self.chkMailAuth.GetValue() self.txtMailLogin.Enable(auth) self.txtMailPassword.Enable(auth) def onMailSave(self, event): self.config.mail_server = self.txtMailServer.GetValue() self.config.mail_port = self.txtMailPort.GetValue() self.config.mail_ssl = self.chkMailSSL.GetValue() self.config.mail_auth = self.chkMailAuth.GetValue() self.config.mail_login = self.txtMailLogin.GetValue() self.config.mail_password = self.txtMailPassword.GetValue() self.config.mail_from = self.txtMailFrom.GetValue() self.config.mail_to = self.txtMailTo.GetValue() self.config.save() def onMailTest(self, event): try: if not self.txtMailServer.GetValue() \ or not self.txtMailFrom.GetValue() \ or not self.txtMailTo.GetValue(): raise Exception(_("Mail server, from address and to address are required.")) with ProgressDialog(self, _("Sending"), _("Sending a test email.\nPlease wait...")): import time time.sleep(1) log.debug("Doing send") sendemail.sendemail2(self.txtMailServer.GetValue(), int(self.txtMailPort.GetValue()), self.chkMailSSL.GetValue(), self.txtMailFrom.GetValue(), self.txtMailTo.GetValue(), self.chkMailAuth.GetValue(), self.txtMailLogin.GetValue(), self.txtMailPassword.GetValue(), _('The Vault Backup System - Test Message'), _("This is a test message from The Vault Backup System.\n" "If you have received this, then email is correctly configured.")) dlg.Info(self, _("Mail was sent successfully. Please check it arrived.")) except Exception as e: dlg.Warn(self, str(e)) def show_mail(self): self.txtMailServer.SetValue(self.config.mail_server) self.txtMailPort.SetValue(str(self.config.mail_port)) self.chkMailSSL.SetValue(self.config.mail_ssl) self.chkMailAuth.SetValue(self.config.mail_auth) self.txtMailLogin.SetValue(self.config.mail_login) self.txtMailPassword.SetValue(self.config.mail_password) self.txtMailFrom.SetValue(self.config.mail_from) self.txtMailTo.SetValue(self.config.mail_to) self.onMailAuth(None) ######################################################################3 # # Security EVENTS # ######################################################################3 def onHidePassword(self, event): if self.pwd_hidden: self.txtMasterPassword.SetWindowStyle(wx.NORMAL) self.pwd_hidden = False self.btnHidePassword.SetLabel("Hide") else: self.txtMasterPassword.SetWindowStyle(wx.TE_PASSWORD) self.pwd_hidden = True self.btnHidePassword.SetLabel("Show") def show_security(self): if not self.config.data_passphrase: self.txtMasterPassword.SetValue("") else: self.txtMasterPassword.SetValue(self.config.data_passphrase) self.onMasterPasswordChar(None) def onMasterPasswordChar(self, event): """Recalculate entropy any time the password changes.""" pwd = self.txtMasterPassword.GetValue() e = int(cryptor.entropy(pwd)) if e < 0: e = 0 if e > 100: e = 100 self.strength.SetValue(e) if event: event.Skip() def onSavePassword(self, event): pwd = self.txtMasterPassword.GetValue() if pwd != self.config.data_passphrase: # Password has changed. Do we have any stored backups? # If so, they should be deleted. runs = self.db.runs() num_runs = len(runs) if num_runs > 0: size = 0 for run in runs: size += run.size # Check with the user. msg = _("You current have {numruns} backup runs stored, " \ "totalling {size} of remote data.\n" \ "Changing the Master Password means old encrypted backups cannot be used.\n" \ "Note that they can be kept for disaster recovery if needed,\n" \ "but we suggest you simply start fresh.").format(\ numruns=num_runs, size=utils.readable_form(size)) mbox = OptionDialog(self, msg, _("Delete Backup Runs"), _("Also delete all encrypted backup data stored remotely."), default=True) if mbox.ShowModal() != wx.ID_OK: return delete_offsite_data = mbox.chkOption.GetValue() # TODO skip if no runs # We keep track of all errors errors = "" with ProgressDialog(self, _("Deleting"), _("Deleting old encrypted backup data.\nPlease wait...")): for backup in self.config.backups.itervalues(): # If its encrypted if backup.encrypt: # If the option set - delete all offline data at the store if delete_offsite_data: try: # Get the list of unique stores used by runs of this backup runs = self.db.runs(backup.name) stores = set([r.store for r in runs]) # Get the store and delete all data. for storename in stores: store = self.config.storage[storename].copy() store.delete_backup_data(backup.name) except Exception as e: errors += "\nDelete offline data for %s failed: %s" % (backup.name, str(e)) # Now delete the database records of the run abd backup try: self.db.delete_backup(backup.name) except Exception as e: errors += "\nDelete local backup information for %s failed: " % (backup.name, str(e)) if len(errors) > 0: dlg.Error(self, errors) if not pwd: self.config.data_passphrase = None app.show_message('Password cleared') else: self.config.data_passphrase = pwd app.show_message('Password set') self.config.save() # Now delete all the backups and offsite data. ###################################################################### # # Save and Load # ###################################################################### def update_state(self): if self.state == ViewState: self.lblName.Show(True) self.txtName.Show(False) if self.state == NewState: self.lblName.Show(False) self.txtName.Show(True) #self.pnlDetails.Fit() self.pnlDetails.Refresh() def clear_filetype(self): self.show_filetype("<name>", []) def show_filetype(self, name, list): try: # General Information self.txtName.SetValue(name) self.lblName.SetLabel(name) # TODO! self.txtExtensions.Clear() list.sort() self.txtExtensions.AppendText("\n".join(list)) self.update_state() except Exception as e: log.error("Error showing File Type:", str(e)) def save_filetype(self): # BUILD THE Storage if len(self.txtName.GetValue()) == 0: dlg.Warn(self, _("File Type name cannot be blank")) return list = self.txtExtensions.GetValue().split("\n") try: # Create the new file_type object name = self.txtName.GetValue() # We already have list from above # ensure the list is clean cleanlist = [] for item in list: item = item.strip() while len(item) > 0 and item[0] == ".": item = item[1:] if len(item) == 0: continue if item not in cleanlist: cleanlist.append(item) cleanlist.sort() except Exception as e: dlg.Warn(self, str(e)) return if self.state == ViewState: # Delete the old name oldname = self.lstFileTypes.GetStringSelection() try: del self.config.file_types[oldname] except: pass self.config.file_types[name] = cleanlist self.config.save() self.update_filetype_list() self.show_filetype(name, cleanlist) ######################################################################3 # # Misc Routines # ######################################################################3 def delete_filetype(self, name): del self.config.file_types[name] self.config.save() self.update_filetype_list()