2018-11-22 03:26:26 +00:00
|
|
|
import logging
|
2018-11-04 15:37:46 +00:00
|
|
|
from datetime import date, timedelta
|
2019-01-13 01:51:35 +00:00
|
|
|
|
2018-10-29 05:12:45 +00:00
|
|
|
from apscheduler.schedulers.qt import QtScheduler
|
2018-10-29 06:59:24 +00:00
|
|
|
from apscheduler.triggers import cron
|
2019-01-13 01:51:35 +00:00
|
|
|
from vorta.borg.check import BorgCheckThread
|
2018-11-03 08:55:38 +00:00
|
|
|
from vorta.borg.create import BorgCreateThread
|
2018-11-27 11:33:16 +00:00
|
|
|
from vorta.borg.list_repo import BorgListRepoThread
|
2019-01-13 01:51:35 +00:00
|
|
|
from vorta.borg.prune import BorgPruneThread
|
2019-01-20 03:50:10 +00:00
|
|
|
from vorta.i18n import translate
|
2019-01-13 01:51:35 +00:00
|
|
|
|
2020-06-01 23:58:09 +00:00
|
|
|
from vorta.models import BackupProfileModel, EventLogModel
|
|
|
|
from vorta.notifications import VortaNotifications
|
2018-11-06 05:13:49 +00:00
|
|
|
|
2019-01-13 01:51:35 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2018-11-22 03:26:26 +00:00
|
|
|
|
2018-10-31 16:09:01 +00:00
|
|
|
|
2018-11-17 08:51:53 +00:00
|
|
|
class VortaScheduler(QtScheduler):
|
2018-10-31 11:14:12 +00:00
|
|
|
def __init__(self, parent):
|
|
|
|
super().__init__()
|
|
|
|
self.app = parent
|
2018-10-31 12:42:11 +00:00
|
|
|
self.start()
|
|
|
|
self.reload()
|
2018-10-29 05:12:45 +00:00
|
|
|
|
2019-01-20 03:50:10 +00:00
|
|
|
def tr(self, *args, **kwargs):
|
|
|
|
scope = self.__class__.__name__
|
|
|
|
return translate(scope, *args, **kwargs)
|
|
|
|
|
2018-10-31 11:14:12 +00:00
|
|
|
def reload(self):
|
2018-11-17 08:51:53 +00:00
|
|
|
for profile in BackupProfileModel.select():
|
|
|
|
trigger = None
|
|
|
|
job_id = f'{profile.id}'
|
|
|
|
if profile.schedule_mode == 'interval':
|
2019-01-06 00:22:11 +00:00
|
|
|
if profile.schedule_interval_hours >= 24:
|
2018-12-25 05:53:27 +00:00
|
|
|
days = profile.schedule_interval_hours // 24
|
|
|
|
leftover_hours = profile.schedule_interval_hours % 24
|
2019-01-06 00:22:11 +00:00
|
|
|
|
|
|
|
if leftover_hours == 0:
|
|
|
|
cron_hours = '1'
|
|
|
|
else:
|
|
|
|
cron_hours = f'*/{leftover_hours}'
|
|
|
|
|
2018-12-25 05:53:27 +00:00
|
|
|
trigger = cron.CronTrigger(day=f'*/{days}',
|
2019-01-06 00:22:11 +00:00
|
|
|
hour=cron_hours,
|
2018-12-25 05:53:27 +00:00
|
|
|
minute=profile.schedule_interval_minutes)
|
|
|
|
else:
|
|
|
|
trigger = cron.CronTrigger(hour=f'*/{profile.schedule_interval_hours}',
|
|
|
|
minute=profile.schedule_interval_minutes)
|
2018-11-17 08:51:53 +00:00
|
|
|
elif profile.schedule_mode == 'fixed':
|
|
|
|
trigger = cron.CronTrigger(hour=profile.schedule_fixed_hour,
|
|
|
|
minute=profile.schedule_fixed_minute)
|
|
|
|
if self.get_job(job_id) is not None and trigger is not None:
|
|
|
|
self.reschedule_job(job_id, trigger=trigger)
|
2019-01-13 01:51:35 +00:00
|
|
|
notifier = VortaNotifications.pick()
|
2019-01-20 03:50:10 +00:00
|
|
|
notifier.deliver(self.tr('Vorta Scheduler'), self.tr('Background scheduler was changed.'))
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.debug('Job for profile %s was rescheduled.', profile.name)
|
2018-11-17 08:51:53 +00:00
|
|
|
elif trigger is not None:
|
2018-11-22 02:18:03 +00:00
|
|
|
self.add_job(
|
|
|
|
func=self.create_backup,
|
|
|
|
args=[profile.id],
|
|
|
|
trigger=trigger,
|
|
|
|
id=job_id,
|
|
|
|
misfire_grace_time=180
|
|
|
|
)
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.debug('New job for profile %s was added.', profile.name)
|
2018-11-17 08:51:53 +00:00
|
|
|
elif self.get_job(job_id) is not None and trigger is None:
|
|
|
|
self.remove_job(job_id)
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.debug('Job for profile %s was removed.', profile.name)
|
2018-10-31 16:09:01 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def next_job(self):
|
2018-11-20 04:06:09 +00:00
|
|
|
self.wakeup()
|
|
|
|
self._process_jobs()
|
2018-11-17 08:51:53 +00:00
|
|
|
jobs = []
|
|
|
|
for job in self.get_jobs():
|
|
|
|
jobs.append((job.next_run_time, job.id))
|
|
|
|
|
|
|
|
if jobs:
|
|
|
|
jobs.sort(key=lambda job: job[0])
|
|
|
|
profile = BackupProfileModel.get(id=int(jobs[0][1]))
|
|
|
|
return f"{jobs[0][0].strftime('%H:%M')} ({profile.name})"
|
|
|
|
else:
|
2019-01-20 03:50:10 +00:00
|
|
|
return self.tr('None scheduled')
|
2018-11-17 08:51:53 +00:00
|
|
|
|
|
|
|
def next_job_for_profile(self, profile_id):
|
2018-11-20 04:06:09 +00:00
|
|
|
self.wakeup()
|
2018-11-17 08:51:53 +00:00
|
|
|
job = self.get_job(str(profile_id))
|
2018-11-01 01:10:11 +00:00
|
|
|
if job is None:
|
2019-01-20 03:50:10 +00:00
|
|
|
return self.tr('None scheduled')
|
2018-11-01 01:10:11 +00:00
|
|
|
else:
|
|
|
|
return job.next_run_time.strftime('%Y-%m-%d %H:%M')
|
2018-10-31 16:09:01 +00:00
|
|
|
|
2018-11-17 08:51:53 +00:00
|
|
|
def create_backup(self, profile_id):
|
2019-01-13 01:51:35 +00:00
|
|
|
notifier = VortaNotifications.pick()
|
2018-11-17 08:51:53 +00:00
|
|
|
profile = BackupProfileModel.get(id=profile_id)
|
2019-01-13 01:51:35 +00:00
|
|
|
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.info('Starting background backup for %s', profile.name)
|
2019-01-20 03:50:10 +00:00
|
|
|
notifier.deliver(self.tr('Vorta Backup'),
|
|
|
|
self.tr('Starting background backup for %s.') % profile.name,
|
|
|
|
level='info')
|
2019-01-13 01:51:35 +00:00
|
|
|
|
2018-11-17 08:51:53 +00:00
|
|
|
msg = BorgCreateThread.prepare(profile)
|
2018-10-31 16:09:01 +00:00
|
|
|
if msg['ok']:
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.info('Preparation for backup successful.')
|
2018-11-04 15:37:46 +00:00
|
|
|
thread = BorgCreateThread(msg['cmd'], msg)
|
2018-10-31 16:09:01 +00:00
|
|
|
thread.start()
|
|
|
|
thread.wait()
|
2018-11-21 09:53:11 +00:00
|
|
|
if thread.process.returncode in [0, 1]:
|
2019-01-20 03:50:10 +00:00
|
|
|
notifier.deliver(self.tr('Vorta Backup'),
|
|
|
|
self.tr('Backup successful for %s.') % profile.name,
|
|
|
|
level='info')
|
2019-01-13 01:51:35 +00:00
|
|
|
logger.info('Backup creation successful.')
|
2018-11-17 08:51:53 +00:00
|
|
|
self.post_backup_tasks(profile_id)
|
2018-11-06 05:13:49 +00:00
|
|
|
else:
|
2019-01-20 03:50:10 +00:00
|
|
|
notifier.deliver(self.tr('Vorta Backup'), self.tr('Error during backup creation.'), level='error')
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.error('Error during backup creation.')
|
2018-11-06 05:13:49 +00:00
|
|
|
else:
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.error('Conditions for backup not met. Aborting.')
|
|
|
|
logger.error(msg['message'])
|
2019-01-20 03:50:10 +00:00
|
|
|
notifier.deliver(self.tr('Vorta Backup'), translate('messages', msg['message']), level='error')
|
2018-11-04 15:37:46 +00:00
|
|
|
|
2018-11-17 08:51:53 +00:00
|
|
|
def post_backup_tasks(self, profile_id):
|
2018-11-04 15:37:46 +00:00
|
|
|
"""
|
|
|
|
Pruning and checking after successful backup.
|
|
|
|
"""
|
2018-11-17 08:51:53 +00:00
|
|
|
profile = BackupProfileModel.get(id=profile_id)
|
2018-11-22 03:26:26 +00:00
|
|
|
logger.info('Doing post-backup jobs for %s', profile.name)
|
2018-11-04 15:37:46 +00:00
|
|
|
if profile.prune_on:
|
|
|
|
msg = BorgPruneThread.prepare(profile)
|
|
|
|
if msg['ok']:
|
|
|
|
prune_thread = BorgPruneThread(msg['cmd'], msg)
|
|
|
|
prune_thread.start()
|
|
|
|
prune_thread.wait()
|
|
|
|
|
2019-01-20 03:50:10 +00:00
|
|
|
# Refresh archives
|
2018-11-27 11:33:16 +00:00
|
|
|
msg = BorgListRepoThread.prepare(profile)
|
2018-11-04 15:37:46 +00:00
|
|
|
if msg['ok']:
|
2018-11-27 11:33:16 +00:00
|
|
|
list_thread = BorgListRepoThread(msg['cmd'], msg)
|
2018-11-04 15:37:46 +00:00
|
|
|
list_thread.start()
|
|
|
|
list_thread.wait()
|
2018-11-04 08:23:17 +00:00
|
|
|
|
2018-11-22 00:43:37 +00:00
|
|
|
validation_cutoff = date.today() - timedelta(days=7 * profile.validation_weeks)
|
2018-11-04 15:37:46 +00:00
|
|
|
recent_validations = EventLogModel.select().where(
|
2018-11-22 20:21:52 +00:00
|
|
|
(
|
|
|
|
EventLogModel.subcommand == 'check'
|
|
|
|
) & (
|
|
|
|
EventLogModel.start_time > validation_cutoff
|
|
|
|
) & (
|
|
|
|
EventLogModel.repo_url == profile.repo.url
|
|
|
|
)
|
2018-11-04 15:37:46 +00:00
|
|
|
).count()
|
|
|
|
if profile.validation_on and recent_validations == 0:
|
|
|
|
msg = BorgCheckThread.prepare(profile)
|
|
|
|
if msg['ok']:
|
|
|
|
check_thread = BorgCheckThread(msg['cmd'], msg)
|
|
|
|
check_thread.start()
|
|
|
|
check_thread.wait()
|
2018-11-22 03:26:26 +00:00
|
|
|
|
|
|
|
logger.info('Finished background task for profile %s', profile.name)
|