Ejemplo n.º 1
0
 def bootstrap_profile(self, bootstrap_file=PROFILE_BOOTSTRAP_FILE):
     """
     Make sure there is at least one profile when first starting Vorta.
     Will either import a profile placed in ~/.vorta-init.json
     or add an empty "Default" profile.
     """
     if bootstrap_file.is_file():
         try:
             profile_export = ProfileExport.from_json(bootstrap_file)
             profile = profile_export.to_db(overwrite_profile=True,
                                            overwrite_settings=True)
         except Exception as exception:
             double_newline = os.linesep + os.linesep
             QMessageBox.critical(
                 None, self.tr('Failed to import profile'),
                 "{}{}\"{}\"{}{}".format(
                     self.tr('Failed to import a profile from {}:').format(
                         bootstrap_file),
                     double_newline,
                     str(exception),
                     double_newline,
                     self.tr('Consider removing or repairing this file to '
                             'get rid of this message.'),
                 ))
             return
         bootstrap_file.unlink()
         notifier = VortaNotifications.pick()
         notifier.deliver(self.tr('Profile import successful!'),
                          self.tr('Profile {} imported.').format(
                              profile.name),
                          level='info')
         logger.info('Profile {} imported.'.format(profile.name))
     if BackupProfileModel.select().count() == 0:
         default_profile = BackupProfileModel(name='Default')
         default_profile.save()
Ejemplo n.º 2
0
def test_interval(clockmock, passed_time, scheduled, now, unit, count,
                  added_time):
    """Test scheduling in interval mode."""
    # setup
    scheduler = VortaScheduler()

    time = dt(2020, 5, 4, 0, 0) + now
    clockmock.now.return_value = time

    profile = BackupProfileModel.get(name=PROFILE_NAME)
    profile.schedule_make_up_missed = False
    profile.schedule_mode = INTERVAL_SCHEDULE
    profile.schedule_interval_unit = unit
    profile.schedule_interval_count = count
    profile.save()

    event = EventLogModel(subcommand='create',
                          profile=profile.id,
                          returncode=0,
                          category='scheduled' if scheduled else '',
                          start_time=time - passed_time,
                          end_time=time - passed_time)
    event.save()

    # run test
    scheduler.set_timer_for_profile(profile.id)
    assert scheduler.timers[profile.id]['dt'] == time + added_time
Ejemplo n.º 3
0
    def profile_delete_action(self):
        if self.profileSelector.count() > 1:
            to_delete_id = self.profileSelector.currentData()
            to_delete = BackupProfileModel.get(id=to_delete_id)

            msg = self.tr(
                "Are you sure you want to delete profile '{}'?".format(
                    to_delete.name))
            reply = QMessageBox.question(self, self.tr("Confirm deletion"),
                                         msg, QMessageBox.Yes | QMessageBox.No,
                                         QMessageBox.No)

            if reply == QMessageBox.Yes:
                to_delete.delete_instance(recursive=True)
                self.app.scheduler.remove_job(
                    to_delete_id)  # Remove pending jobs
                self.profileSelector.removeItem(
                    self.profileSelector.currentIndex())
                self.profile_select_action(0)

        else:
            warn = self.tr("Can't delete the last profile.")
            point = QPoint(0, self.profileDeleteButton.size().height() / 2)
            QToolTip.showText(self.profileDeleteButton.mapToGlobal(point),
                              warn)
Ejemplo n.º 4
0
    def post_backup_tasks(self, profile_id):
        """
        Pruning and checking after successful backup.
        """
        profile = BackupProfileModel.get(id=profile_id)
        logger.info('Doing post-backup jobs for %s', profile.name)
        if profile.prune_on:
            msg = BorgPruneJob.prepare(profile)
            if msg['ok']:
                job = BorgPruneJob(msg['cmd'], msg, profile.repo.id)
                self.app.jobs_manager.add_job(job)

                # Refresh archives
                msg = BorgListRepoJob.prepare(profile)
                if msg['ok']:
                    job = BorgListRepoJob(msg['cmd'], msg, profile.repo.id)
                    self.app.jobs_manager.add_job(job)

        validation_cutoff = dt.now() - timedelta(days=7 *
                                                 profile.validation_weeks)
        recent_validations = EventLogModel.select().where(
            (EventLogModel.subcommand == 'check')
            & (EventLogModel.start_time > validation_cutoff)
            & (EventLogModel.repo_url == profile.repo.url)).count()
        if profile.validation_on and recent_validations == 0:
            msg = BorgCheckJob.prepare(profile)
            if msg['ok']:
                job = BorgCheckJob(msg['cmd'], msg, profile.repo.id)
                self.app.jobs_manager.add_job(job)

        logger.info('Finished background task for profile %s', profile.name)
Ejemplo n.º 5
0
def test_fixed(clockmock, passed_time, scheduled, now, hour, minute):
    """Test scheduling in fixed mode."""
    # setup
    scheduler = VortaScheduler()

    time = dt(2020, 5, 4, 0, 0) + now
    clockmock.now.return_value = time

    profile = BackupProfileModel.get(name=PROFILE_NAME)
    profile.schedule_make_up_missed = False
    profile.schedule_mode = FIXED_SCHEDULE
    profile.schedule_fixed_hour = hour
    profile.schedule_fixed_minute = minute
    profile.save()

    last_time = time - passed_time
    event = EventLogModel(subcommand='create',
                          profile=profile.id,
                          returncode=0,
                          category='scheduled' if scheduled else '',
                          start_time=last_time,
                          end_time=last_time)
    event.save()

    # run test
    expected = time.replace(hour=hour, minute=minute)

    if time >= expected or last_time.date() == expected.date():
        expected += td(days=1)

    scheduler.set_timer_for_profile(profile.id)
    assert scheduler.timers[profile.id]['dt'] == expected
Ejemplo n.º 6
0
def test_simple_schedule(clockmock):
    """Test a simple scheduling including `next_job` and `remove_job`."""
    scheduler = VortaScheduler()

    # setup
    clockmock.now.return_value = dt(2020, 5, 6, 4, 30)

    profile = BackupProfileModel.get(name=PROFILE_NAME)
    profile.schedule_make_up_missed = False
    profile.schedule_mode = INTERVAL_SCHEDULE
    profile.schedule_interval_unit = 'hours'
    profile.schedule_interval_count = 3
    profile.save()

    # test set timer and next_job
    scheduler.set_timer_for_profile(profile.id)
    assert len(scheduler.timers) == 1
    assert scheduler.next_job() == '07:30 ({})'.format(PROFILE_NAME)
    assert scheduler.next_job_for_profile(profile.id) == '2020-05-06 07:30'

    # test remove_job and next_job
    scheduler.remove_job(profile.id)
    assert len(scheduler.timers) == 0
    assert scheduler.next_job() == 'None scheduled'
    assert scheduler.next_job_for_profile(profile.id) == 'None scheduled'
Ejemplo n.º 7
0
def test_profile_edit(qapp, qtbot):
    main = qapp.main_window
    qtbot.mouseClick(main.profileRenameButton, QtCore.Qt.LeftButton)

    edit_profile_window = main.window
    qtbot.addWidget(edit_profile_window)

    edit_profile_window.profileNameField.setText("")
    qtbot.keyClicks(edit_profile_window.profileNameField, 'Test Profile')
    qtbot.mouseClick(
        edit_profile_window.buttonBox.button(QDialogButtonBox.Save),
        QtCore.Qt.LeftButton)

    assert BackupProfileModel.get_or_none(name='Default') is None
    assert BackupProfileModel.get_or_none(name='Test Profile') is not None
    assert main.profileSelector.currentText() == 'Test Profile'
Ejemplo n.º 8
0
    def create_backup(self, profile_id):
        notifier = VortaNotifications.pick()
        profile = BackupProfileModel.get_or_none(id=profile_id)

        if profile is None:
            logger.info('Profile not found. Maybe deleted?')
            return

        # Skip if a job for this profile (repo) is already in progress
        if self.app.jobs_manager.is_worker_running(site=profile.repo.id):
            logger.debug('A job for repo %s is already active.',
                         profile.repo.id)
            return

        self.lock.acquire()
        logger.info('Starting background backup for %s', profile.name)
        notifier.deliver(self.tr('Vorta Backup'),
                         self.tr('Starting background backup for %s.') %
                         profile.name,
                         level='info')
        msg = BorgCreateJob.prepare(profile)
        if msg['ok']:
            logger.info('Preparation for backup successful.')
            msg['category'] = 'scheduled'
            job = BorgCreateJob(msg['cmd'], msg, profile.repo.id)
            job.result.connect(self.notify)
            self.app.jobs_manager.add_job(job)
        else:
            logger.error('Conditions for backup not met. Aborting.')
            logger.error(msg['message'])
            notifier.deliver(self.tr('Vorta Backup'),
                             translate('messages', msg['message']),
                             level='error')
        self.lock.release()
Ejemplo n.º 9
0
def test_import_bootstrap_success(qapp, mocker):
    mocked_unlink = mocker.MagicMock()
    mocker.patch.object(Path, 'unlink', mocked_unlink)
    qapp.bootstrap_profile(Path(VALID_IMPORT_FILE))

    assert mocked_unlink.called

    restored_profile = BackupProfileModel.get_or_none(
        name="Test Profile Restoration")
    assert restored_profile is not None

    restored_repo = restored_profile.repo
    assert restored_repo is not None

    assert len(SourceFileModel.select().where(
        SourceFileModel.profile == restored_profile)) == 3
    assert BackupProfileModel.select().count() == 2
Ejemplo n.º 10
0
 def populate_profile_selector(self):
     self.profileSelector.clear()
     for profile in BackupProfileModel.select().order_by(
             BackupProfileModel.name):
         self.profileSelector.addItem(profile.name, profile.id)
     current_profile_index = self.profileSelector.findData(
         self.current_profile.id)
     self.profileSelector.setCurrentIndex(current_profile_index)
Ejemplo n.º 11
0
 def create_backups_cmdline(self, profile_name):
     profile = BackupProfileModel.get_or_none(name=profile_name)
     if profile is not None:
         if profile.repo is None:
             logger.warning(f"Add a repository to {profile_name}")
         self.create_backup_action(profile_id=profile.id)
     else:
         logger.warning(f"Invalid profile name {profile_name}")
Ejemplo n.º 12
0
def test_prune_intervals(qapp, qtbot):
    prune_intervals = ['hour', 'day', 'week', 'month', 'year']
    main = qapp.main_window
    tab = main.archiveTab
    profile = BackupProfileModel.get(id=1)

    for i in prune_intervals:
        getattr(tab, f'prune_{i}').setValue(9)
        tab.save_prune_setting(None)
        profile = profile.refresh()
        assert getattr(profile, f'prune_{i}') == 9
Ejemplo n.º 13
0
 def profile_select_action(self, index):
     backup_profile_id = self.profileSelector.currentData()
     if not backup_profile_id:
         return
     self.current_profile = BackupProfileModel.get(id=backup_profile_id)
     self.archiveTab.populate_from_profile()
     self.repoTab.populate_from_profile()
     self.sourceTab.populate_from_profile()
     self.scheduleTab.populate_from_profile()
     SettingsModel.update({SettingsModel.str_value: self.current_profile.id}) \
         .where(SettingsModel.key == 'previous_profile_id') \
         .execute()
Ejemplo n.º 14
0
 def init_overwrite_profile_checkbox(self):
     """Disable the overwrite profile checkbox if no profile with that name currently exists."""
     existing_backup_profile = BackupProfileModel.get_or_none(
         BackupProfileModel.name == self.profile_export.name
     )
     if not existing_backup_profile:
         self.overwriteExistingProfile.setChecked(False)
         self.overwriteExistingProfile.setEnabled(False)
         self.overwriteExistingProfile.setToolTip(
             self.tr(
                 'A profile with the name {} does not exist. Nothing to overwrite.'.format(self.profile_export.name)
             )
         )
Ejemplo n.º 15
0
def test_manual_mode():
    """Test scheduling in manual mode."""
    scheduler = VortaScheduler()

    # setup model
    profile = BackupProfileModel.get(name=PROFILE_NAME)
    profile.schedule_make_up_missed = False
    profile.schedule_mode = MANUAL_SCHEDULE
    profile.save()

    # test
    scheduler.set_timer_for_profile(profile.id)
    assert len(scheduler.timers) == 0
Ejemplo n.º 16
0
    def validate(self):
        name = self.profileNameField.text()
        # A name was entered?
        if len(name) == 0:
            self._set_status(translate('AddProfileWindow', self.name_blank))
            return False

        # Profile with this name already exists?
        exists = BackupProfileModel.select().where(
            BackupProfileModel.name == name).count()
        if exists > 0:
            self._set_status(translate('AddProfileWindow', self.name_exists))
            return False

        self._set_status(self.tr(''))
        return True
Ejemplo n.º 17
0
    def create_backup_action(self, profile_id=None):
        if not profile_id:
            profile_id = self.main_window.current_profile.id

        profile = BackupProfileModel.get(id=profile_id)
        msg = BorgCreateJob.prepare(profile)
        if msg['ok']:
            job = BorgCreateJob(msg['cmd'], msg, profile.repo.id)
            self.jobs_manager.add_job(job)
        else:
            notifier = VortaNotifications.pick()
            notifier.deliver(self.tr('Vorta Backup'),
                             translate('messages', msg['message']),
                             level='error')
            self.backup_progress_event.emit(
                translate('messages', msg['message']))
            return None
Ejemplo n.º 18
0
def test_first_interval(clockmock, now, unit, count, added_time):
    """Test scheduling in interval mode without a previous backup."""
    # setup
    scheduler = VortaScheduler()

    time = dt(2020, 5, 4, 0, 0) + now
    clockmock.now.return_value = time

    profile = BackupProfileModel.get(name=PROFILE_NAME)
    profile.schedule_make_up_missed = False
    profile.schedule_mode = INTERVAL_SCHEDULE
    profile.schedule_interval_unit = unit
    profile.schedule_interval_count = count
    profile.save()

    # run test
    scheduler.set_timer_for_profile(profile.id)
    assert scheduler.timers[profile.id]['dt'] == time + added_time
Ejemplo n.º 19
0
def test_first_fixed(clockmock, now, hour, minute, added_time):
    """Test scheduling in fixed mode without a previous backup."""
    # setup
    scheduler = VortaScheduler()

    time = dt(2020, 5, 4, 0, 0) + now
    clockmock.now.return_value = time

    profile = BackupProfileModel.get(name=PROFILE_NAME)
    profile.schedule_make_up_missed = False
    profile.schedule_mode = FIXED_SCHEDULE
    profile.schedule_fixed_hour = hour
    profile.schedule_fixed_minute = minute
    profile.save()

    # run test
    scheduler.set_timer_for_profile(profile.id)
    assert scheduler.timers[profile.id]['dt'] == time + added_time
Ejemplo n.º 20
0
    def next_job(self):
        next_job = now = dt.now()
        next_profile = None
        for profile_id, timer in self.timers.items():
            if next_job == now and timer['dt'] > next_job and timer[
                    'qtt'].isActive():
                next_job = timer['dt']
                next_profile = profile_id
            elif next_job > now and timer['dt'] < next_job and timer[
                    'qtt'].isActive():
                next_job = timer['dt']
                next_profile = profile_id

        if next_profile is not None:
            profile = BackupProfileModel.get_or_none(id=next_profile)
            return f"{next_job.strftime('%H:%M')} ({profile.name})"
        else:
            return self.tr('None scheduled')
Ejemplo n.º 21
0
def init_db(qapp, qtbot, tmpdir_factory):
    tmp_db = tmpdir_factory.mktemp('Vorta').join('settings.sqlite')
    mock_db = SqliteDatabase(str(tmp_db), pragmas={'journal_mode': 'wal', })
    vorta.store.connection.init_db(mock_db)

    default_profile = BackupProfileModel(name='Default')
    default_profile.save()

    new_repo = RepoModel(url='[email protected]:repo')
    new_repo.encryption = 'none'
    new_repo.save()

    default_profile.repo = new_repo.id
    default_profile.dont_run_on_metered_networks = False
    default_profile.validation_on = False
    default_profile.save()

    test_archive = ArchiveModel(snapshot_id='99999', name='test-archive', time=dt(2000, 1, 1, 0, 0), repo=1)
    test_archive.save()

    test_archive1 = ArchiveModel(snapshot_id='99998', name='test-archive1', time=dt(2000, 1, 1, 0, 0), repo=1)
    test_archive1.save()

    source_dir = SourceFileModel(dir='/tmp/another', repo=new_repo, dir_size=100, dir_files_count=18,
                                 path_isdir=True)
    source_dir.save()

    qapp.main_window.deleteLater()
    del qapp.main_window
    qapp.main_window = MainWindow(qapp)  # Re-open main window to apply mock data in UI

    yield

    qapp.jobs_manager.cancel_all_jobs()
    qtbot.waitUntil(lambda: not qapp.jobs_manager.is_worker_running(), **pytest._wait_defaults)
    mock_db.close()
Ejemplo n.º 22
0
def test_import_success(qapp, qtbot, rootdir, monkeypatch):
    monkeypatch.setattr(QFileDialog, "getOpenFileName",
                        lambda *args: [VALID_IMPORT_FILE])
    monkeypatch.setattr(QMessageBox, 'information', lambda *args: None)

    main = qapp.main_window
    main.profile_import_action()
    import_dialog: ImportWindow = main.window
    import_dialog.overwriteExistingSettings.setChecked(True)

    qtbot.mouseClick(import_dialog.buttonBox.button(QDialogButtonBox.Ok),
                     QtCore.Qt.LeftButton)
    qtbot.waitSignal(import_dialog.profile_imported, **pytest._wait_defaults)

    restored_profile = BackupProfileModel.get_or_none(
        name="Test Profile Restoration")
    assert restored_profile is not None

    restored_repo = restored_profile.repo
    assert restored_repo is not None
    assert len(SourceFileModel.select().where(
        SourceFileModel.profile == restored_profile)) == 3
Ejemplo n.º 23
0
 def react_to_log(self, mgs, context):
     """
     Trigger Vorta actions based on Borg logs. E.g. repo lock.
     """
     msgid = context.get('msgid')
     if msgid == 'LockTimeout':
         profile = BackupProfileModel.get(name=context['profile_name'])
         repo_url = context.get('repo_url')
         msg = QMessageBox()
         msg.setWindowTitle(self.tr("Repository In Use"))
         msg.setIcon(QMessageBox.Critical)
         abortButton = msg.addButton(self.tr("Abort"),
                                     QMessageBox.RejectRole)
         msg.addButton(self.tr("Continue"), QMessageBox.AcceptRole)
         msg.setDefaultButton(abortButton)
         msg.setText(
             self.
             tr(f"The repository at {repo_url} might be in use elsewhere."))
         msg.setInformativeText(
             self.
             tr("Only break the lock if you are certain no other Borg process "
                "on any machine is accessing the repository. Abort or break the lock?"
                ))
         msg.accepted.connect(lambda: self.break_lock(profile))
         self._msg = msg
         msg.show()
     elif msgid == 'LockFailed':
         repo_url = context.get('repo_url')
         msg = QMessageBox()
         msg.setText(
             self.
             tr(f"You do not have permission to access the repository at {repo_url}. Gain access and try again."
                ))  # noqa: E501
         msg.setWindowTitle(self.tr("No Repository Permissions"))
         self._msg = msg
         msg.show()
Ejemplo n.º 24
0
    def on_user_click(self):
        """Build system tray menu based on current state."""

        menu = self.contextMenu()
        menu.clear()

        open_action = menu.addAction(self.tr('Vorta for Borg Backup'))
        open_action.triggered.connect(self.app.open_main_window_action)

        menu.addSeparator()

        next_task_time = self.app.scheduler.next_job()
        status = menu.addAction(next_task_time)
        status.setEnabled(False)

        if self.app.jobs_manager.is_worker_running():
            status.setText(self.tr('Task in progress'))
            cancel_action = menu.addAction(self.tr('Cancel Backup'))
            cancel_action.triggered.connect(self.app.backup_cancelled_event.emit)
        else:
            status.setText(self.tr('Next Task: %s') % next_task_time)
            profiles = BackupProfileModel.select()
            if profiles.count() > 1:
                profile_menu = menu.addMenu(self.tr('Backup Now'))
                for profile in profiles:
                    new_item = profile_menu.addAction(profile.name)
                    new_item.triggered.connect(lambda state, i=profile.id: self.app.create_backup_action(i))
            else:
                profile = profiles.first()
                profile_menu = menu.addAction(self.tr('Backup Now'))
                profile_menu.triggered.connect(lambda state, i=profile.id: self.app.create_backup_action(i))

        menu.addSeparator()

        exit_action = menu.addAction(self.tr('Quit'))
        exit_action.triggered.connect(self.app.quit)
Ejemplo n.º 25
0
    def __init__(self, parent=None):
        super().__init__()
        self.setupUi(self)
        self.setWindowTitle('Vorta for Borg Backup')
        self.app = parent
        self.setWindowIcon(get_colored_icon("icon"))
        self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint
                            | QtCore.Qt.WindowMinimizeButtonHint)
        self.createStartBtn = LoadingButton(self.tr("Start Backup"))
        self.gridLayout.addWidget(self.createStartBtn, 0, 0, 1, 1)
        self.createStartBtn.setGif(get_asset("icons/loading"))

        # Use previous window state
        previous_window_width = SettingsModel.get(key='previous_window_width')
        previous_window_height = SettingsModel.get(
            key='previous_window_height')
        self.resize(int(previous_window_width.str_value),
                    int(previous_window_height.str_value))

        # Select previously used profile, if available
        prev_profile_id = SettingsModel.get(key='previous_profile_id')
        self.current_profile = BackupProfileModel.get_or_none(
            id=prev_profile_id.str_value)
        if self.current_profile is None:
            self.current_profile = BackupProfileModel.select().order_by(
                'name').first()

        # Load tab models
        self.repoTab = RepoTab(self.repoTabSlot)
        self.sourceTab = SourceTab(self.sourceTabSlot)
        self.archiveTab = ArchiveTab(self.archiveTabSlot, app=self.app)
        self.scheduleTab = ScheduleTab(self.scheduleTabSlot)
        self.miscTab = MiscTab(self.miscTabSlot)
        self.miscTab.set_borg_details(borg_compat.version, borg_compat.path)
        self.tabWidget.setCurrentIndex(0)

        self.repoTab.repo_changed.connect(
            self.archiveTab.populate_from_profile)
        self.repoTab.repo_changed.connect(
            self.scheduleTab.populate_from_profile)
        self.repoTab.repo_added.connect(self.archiveTab.list_action)

        self.createStartBtn.clicked.connect(self.app.create_backup_action)
        self.cancelButton.clicked.connect(self.app.backup_cancelled_event.emit)

        QShortcut(QKeySequence("Ctrl+W"),
                  self).activated.connect(self.on_close_window)
        QShortcut(QKeySequence("Ctrl+Q"),
                  self).activated.connect(self.on_close_window)

        self.app.backup_started_event.connect(self.backup_started_event)
        self.app.backup_finished_event.connect(self.backup_finished_event)
        self.app.backup_log_event.connect(self.set_log)
        self.app.backup_progress_event.connect(self.set_progress)
        self.app.backup_cancelled_event.connect(self.backup_cancelled_event)

        # Init profile list
        self.populate_profile_selector()
        self.profileSelector.currentIndexChanged.connect(
            self.profile_select_action)
        self.profileRenameButton.clicked.connect(self.profile_rename_action)
        self.profileExportButton.clicked.connect(self.profile_export_action)
        self.profileDeleteButton.clicked.connect(self.profile_delete_action)
        profile_add_menu = QMenu()
        profile_add_menu.addAction(self.tr('Import from file...'),
                                   self.profile_import_action)
        self.profileAddButton.setMenu(profile_add_menu)
        self.profileAddButton.clicked.connect(self.profile_add_action)

        # OS-specific startup options:
        if not get_network_status_monitor().is_network_status_available():
            # Hide Wifi-rule section in schedule tab.
            self.scheduleTab.wifiListLabel.hide()
            self.scheduleTab.wifiListWidget.hide()
            self.scheduleTab.page_2.hide()
            self.scheduleTab.toolBox.removeItem(1)

        # Connect to existing thread.
        if self.app.jobs_manager.is_worker_running():
            self.createStartBtn.setEnabled(False)
            self.createStartBtn.start()
            self.cancelButton.setEnabled(True)

        self.set_icons()
Ejemplo n.º 26
0
    def set_timer_for_profile(self, profile_id: int):
        """
        Set a timer for next scheduled backup run of this profile.

        Removes existing jobs if set to manual only or no repo is assigned.

        Else will look for previous scheduled backups and catch up if
        schedule_make_up_missed is enabled.

        Or, if catch-up is not enabled, will add interval to last run to find
        next suitable backup time.
        """
        profile = BackupProfileModel.get_or_none(id=profile_id)
        if profile is None:  # profile doesn't exist any more.
            return

        with self.lock:  # Acquire lock

            if profile_id in self.timers:
                self.remove_job(profile_id)  # reset schedule

            if profile.repo is None:  # No backups without repo set
                logger.debug(
                    'Nothing scheduled for profile %s because of unset repo.',
                    profile_id)

            if profile.schedule_mode == 'off':
                logger.debug('Scheduler for profile %s is disabled.',
                             profile_id)
                return

            logger.info('Setting timer for profile %s', profile_id)

            # determine last backup time
            last_run_log = EventLogModel.select().where(
                EventLogModel.subcommand == 'create',
                EventLogModel.category == 'scheduled',
                EventLogModel.profile == profile.id,
                0 <= EventLogModel.returncode <= 1,
            ).order_by(EventLogModel.end_time.desc()).first()

            if last_run_log is None:
                # look for non scheduled (manual) backup runs
                last_run_log = EventLogModel.select().where(
                    EventLogModel.subcommand == 'create',
                    EventLogModel.profile == profile.id,
                    0 <= EventLogModel.returncode <= 1,
                ).order_by(EventLogModel.end_time.desc()).first()

            # calculate next scheduled time
            if profile.schedule_mode == 'interval':
                if last_run_log is None:
                    last_time = dt.now()
                else:
                    last_time = last_run_log.end_time

                interval = {
                    profile.schedule_interval_unit:
                    profile.schedule_interval_count
                }
                next_time = last_time + timedelta(**interval)

            elif profile.schedule_mode == 'fixed':
                if last_run_log is None:
                    last_time = dt.now()
                else:
                    last_time = last_run_log.end_time + timedelta(days=1)

                next_time = last_time.replace(
                    hour=profile.schedule_fixed_hour,
                    minute=profile.schedule_fixed_minute)

            else:
                # unknown schedule mode
                raise ValueError("Unknown schedule mode '{}'".format(
                    profile.schedule_mode))

            # handle missing of a scheduled time
            if next_time <= dt.now():

                if profile.schedule_make_up_missed:
                    self.lock.release()
                    try:
                        logger.debug('Catching up by running job for %s (%s)',
                                     profile.name, profile_id)
                        self.create_backup(profile_id)
                    finally:
                        self.lock.acquire(
                        )  # with-statement will try to release

                    return  # create_backup will lead to a call to this method

                # calculate next time from now
                if profile.schedule_mode == 'interval':
                    # next_time % interval should be 0
                    # while next_time > now
                    delta = dt.now() - last_time
                    next_time = dt.now() - delta % timedelta(**interval)
                    next_time += timedelta(**interval)

                elif profile.schedule_mode == 'fixed':
                    if next_time.date() == dt.now().date():
                        # time for today has passed, schedule for tomorrow
                        next_time += timedelta(days=1)
                    else:
                        # schedule for today
                        next_time = dt.now().replace(
                            hour=profile.schedule_fixed_hour,
                            minute=profile.schedule_fixed_minute)

            # start QTimer
            logger.debug('Scheduling next run for %s', next_time)

            timer_ms = (next_time - dt.now()).total_seconds() * 1000

            timer = QtCore.QTimer()
            timer.setSingleShot(True)
            timer.setInterval(int(timer_ms))
            timer.timeout.connect(lambda: self.create_backup(profile_id))
            timer.start()

            self.timers[profile_id] = {'qtt': timer, 'dt': next_time}

        # Emit signal so that e.g. the GUI can react to the new schedule
        self.schedule_changed.emit(profile_id)
Ejemplo n.º 27
0
    def to_db(self, overwrite_profile=False, overwrite_settings=True):
        profile_schema = self._profile_dict['SchemaVersion']['version']
        keyring = VortaKeyring.get_keyring()
        if SCHEMA_VERSION < profile_schema:
            raise VersionException()
        elif SCHEMA_VERSION > profile_schema:
            # Add model upgrading code here, only needed if not adding columns
            if profile_schema < 16:
                for sourcedir in self._profile_dict['SourceFileModel']:
                    sourcedir['dir_files_count'] = -1
                    sourcedir['dir_size'] = -1
                    sourcedir['path_isdir'] = False

        existing_profile = None
        if overwrite_profile:
            existing_profile = BackupProfileModel.get_or_none(
                BackupProfileModel.name == self.name)
            if existing_profile:
                self._profile_dict['id'] = existing_profile.id
        if not overwrite_profile or not existing_profile:
            # Guarantee uniqueness of ids
            while BackupProfileModel.get_or_none(
                    BackupProfileModel.id == self.id) is not None:
                self._profile_dict['id'] += 1

            # Add suffix incase names are the same
            if BackupProfileModel.get_or_none(
                    BackupProfileModel.name == self.name) is not None:
                suffix = 1
                while BackupProfileModel.get_or_none(
                        BackupProfileModel.name ==
                        f"{self.name}-{suffix}") is not None:
                    suffix += 1
                self._profile_dict['name'] = f"{self.name}-{suffix}"

        # Load existing repo or restore it
        if self._profile_dict['repo']:
            repo = RepoModel.get_or_none(RepoModel.url == self.repo_url)
            if repo is None:
                # Load repo from export
                repo = dict_to_model(RepoModel, self._profile_dict['repo'])
                repo.save(force_insert=True)
            self._profile_dict['repo'] = model_to_dict(repo)

        if self.repo_password:
            keyring.set_password('vorta-repo', self.repo_url,
                                 self.repo_password)
            del self._profile_dict['password']

        # Delete and recreate the tables to clear them
        if overwrite_settings:
            DB.drop_tables([SettingsModel, WifiSettingModel])
            DB.create_tables([SettingsModel, WifiSettingModel])
            SettingsModel.insert_many(
                self._profile_dict['SettingsModel']).execute()
            WifiSettingModel.insert_many(
                self._profile_dict['WifiSettingModel']).execute()

        # Set the profile ids to be match new profile
        for source in self._profile_dict['SourceFileModel']:
            source['profile'] = self.id
        SourceFileModel.insert_many(
            self._profile_dict['SourceFileModel']).execute()

        # Delete added dictionaries to make it match BackupProfileModel
        del self._profile_dict['SettingsModel']
        del self._profile_dict['SourceFileModel']
        del self._profile_dict['WifiSettingModel']
        del self._profile_dict['SchemaVersion']

        # dict to profile
        new_profile = dict_to_model(BackupProfileModel, self._profile_dict)
        if overwrite_profile and existing_profile:
            force_insert = False
        else:
            force_insert = True
        new_profile.save(force_insert=force_insert)
        init_db(
        )  # rerun db init code to perform the same operations on the new as as on application boot
        return new_profile
Ejemplo n.º 28
0
 def save(self):
     renamed_profile = BackupProfileModel.get(id=self.existing_id)
     renamed_profile.name = self.profileNameField.text()
     renamed_profile.save()
     self.profile_changed.emit(renamed_profile.name, renamed_profile.id)
     self.accept()
Ejemplo n.º 29
0
 def __init__(self, parent=None, rename_existing_id=None):
     super().__init__(parent)
     existing_profile = BackupProfileModel.get(id=rename_existing_id)
     self.profileNameField.setText(existing_profile.name)
     self.existing_id = rename_existing_id
     self.modalTitle.setText(self.tr('Rename Profile'))
Ejemplo n.º 30
0
 def save(self):
     new_profile = BackupProfileModel(name=self.profileNameField.text())
     new_profile.save()
     self.profile_changed.emit(new_profile.name, new_profile.id)
     self.accept()