Example #1
0
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()
Example #2
0
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()