def profile_select_action(self, index): self.current_profile = BackupProfileModel.get(id=self.profileSelector.currentData()) 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()
def notifications_suppressed(self, level): """Decide if notification is sent or not based on settings and level.""" if not SettingsModel.get(key='enable_notifications').value: logger.debug('notifications suppressed') return True if level == 'info' and not SettingsModel.get( key='enable_notifications_success').value: logger.debug('success notifications suppressed') return True logger.debug('notification not suppressed') return False
def main(): args = parse_args() frozen_binary = getattr(sys, 'frozen', False) # Don't fork if user specifies it or when running from onedir app bundle on macOS. if hasattr(args, 'foreground') or (frozen_binary and sys.platform == 'darwin'): pass else: print('Forking to background (see system tray).') if os.fork(): sys.exit() # Init database sqlite_db = peewee.SqliteDatabase(os.path.join(SETTINGS_DIR, 'settings.db')) init_db(sqlite_db) # Send crashes to Sentry. if SettingsModel.get(key='send_sentry_reports').value: vorta.sentry.init() app = VortaApp(sys.argv, single_app=True) app.updater = get_updater() sys.exit(app.exec_())
def __init__(self, args_raw, single_app=False): super().__init__(APP_ID, args_raw) if self.isRunning() and single_app: self.sendMessage("open main window") print('An instance of Vorta is already running. Opening main window.') sys.exit() init_translations(self) self.setQuitOnLastWindowClosed(False) self.scheduler = VortaScheduler(self) # Prepare system tray icon self.tray = TrayMenu(self) args = parse_args() if getattr(args, 'daemonize', False): pass elif SettingsModel.get(key='foreground').value: self.open_main_window_action() self.backup_started_event.connect(self.backup_started_event_response) self.backup_finished_event.connect(self.backup_finished_event_response) self.backup_cancelled_event.connect(self.backup_cancelled_event_response) self.message_received_event.connect(self.message_received_event_response) self.set_borg_details_action() self.installEventFilter(self)
def save_setting(self, key, new_value): setting = SettingsModel.get(key=key) setting.value = bool(new_value) setting.save() if key == 'autostart': open_app_at_startup(new_value)
def closeEvent(self, event): # Save window state in SettingsModel SettingsModel.update({SettingsModel.str_value: str(self.width())})\ .where(SettingsModel.key == 'previous_window_width')\ .execute() SettingsModel.update({SettingsModel.str_value: str(self.height())})\ .where(SettingsModel.key == 'previous_window_height')\ .execute() if not is_system_tray_available(): run_in_background = QMessageBox.question( self, trans_late("MainWindow QMessagebox", "Quit"), trans_late("MainWindow QMessagebox", "Should Vorta continue to run in the background?"), QMessageBox.Yes | QMessageBox.No) if run_in_background == QMessageBox.No: self.app.quit() event.accept()
def deliver(self, title, text): if SettingsModel.get(key='enable_notifications').value: from Foundation import NSUserNotification from Foundation import NSUserNotificationCenter notification = NSUserNotification.alloc().init() notification.setTitle_(title) notification.setInformativeText_(text) center = NSUserNotificationCenter.defaultUserNotificationCenter() if center is not None: # Only works when run from app bundle. center.deliverNotification_(notification)
def closeEvent(self, event): # Save window state in SettingsModel SettingsModel.update({SettingsModel.str_value: str(self.width())})\ .where(SettingsModel.key == 'previous_window_width')\ .execute() SettingsModel.update({SettingsModel.str_value: str(self.height())})\ .where(SettingsModel.key == 'previous_window_height')\ .execute() if not is_system_tray_available(): if SettingsModel.get(key="enable_background_question").value: msg = QMessageBox() msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg.setParent(self, QtCore.Qt.Sheet) msg.setText( self.tr("Should Vorta continue to run in the background?")) msg.button(QMessageBox.Yes).clicked.connect( lambda: self.miscTab.save_setting( "disable_background_state", True)) msg.button(QMessageBox.No).clicked.connect( lambda: (self.miscTab.save_setting( "disable_background_state", False), self.app.quit())) msg.setWindowTitle(self.tr("Quit")) dont_show_box = QCheckBox(self.tr("Don't show this again")) dont_show_box.clicked.connect( lambda x: self.miscTab.save_setting( "enable_background_question", not x)) dont_show_box.setTristate(False) msg.setCheckBox(dont_show_box) msg.exec() elif not SettingsModel.get(key="disable_background_state").value: self.app.quit() event.accept()
def get_updater(): if sys.platform == 'darwin' and getattr(sys, 'frozen', False): """ Use Sparkle framework on macOS. Settings: https://sparkle-project.org/documentation/customization/ Examples: https://programtalk.com/python-examples/objc.loadBundle/ To debug: $ defaults read com.borgbase.client.macos """ import objc import Cocoa bundle_path = os.path.join(os.path.dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework') objc.loadBundle('Sparkle', globals(), bundle_path) sparkle = SUUpdater.sharedUpdater() # noqa: F821 # A default Appcast URL is set in vorta.spec, when setting it here it's saved to defaults, # so we need both cases. if SettingsModel.get(key='updates_include_beta').value: appcast_nsurl = Cocoa.NSURL.URLWithString_( 'https://borgbase.github.io/vorta/appcast-pre.xml') else: appcast_nsurl = Cocoa.NSURL.URLWithString_( 'https://borgbase.github.io/vorta/appcast.xml') sparkle.setFeedURL_(appcast_nsurl) if SettingsModel.get(key='check_for_updates').value: sparkle.setAutomaticallyChecksForUpdates_(True) sparkle.checkForUpdatesInBackground() sparkle.setAutomaticallyDownloadsUpdates_(False) return sparkle else: # TODO: implement for Linux return None
def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) self.versionLabel.setText(__version__) for setting in SettingsModel.select().where( SettingsModel.type == 'checkbox'): b = QCheckBox(setting.label) b.setCheckState(setting.value) b.setTristate(False) b.stateChanged.connect( lambda v, key=setting.key: self.save_setting(key, v)) self.checkboxLayout.addWidget(b)
def get_theme_class(): """ Choose a package to import collection_rc from. light = white icons, dark = black icons. Defaults to dark icons (light theme) if DB isn't initialized yet. """ if SettingsModel._meta.database.obj is None: return 'vorta.views.dark' else: use_light_icon = SettingsModel.get(key='use_dark_theme').value return 'vorta.views.light' if use_light_icon else 'vorta.views.dark'
def __init__(self, args_raw, single_app=False): super().__init__(APP_ID, args_raw) args = parse_args() if self.isRunning(): if single_app: self.sendMessage("open main window") print( 'An instance of Vorta is already running. Opening main window.' ) sys.exit() elif args.profile: self.sendMessage(f"create {args.profile}") print('Creating backup using existing Vorta instance.') sys.exit() elif args.profile: sys.exit('Vorta must already be running for --create to work') init_translations(self) self.setQuitOnLastWindowClosed(False) self.scheduler = VortaScheduler(self) self.setApplicationName("Vorta") # Import profile from ~/.vorta-init.json or add empty "Default" profile. self.bootstrap_profile() # Prepare tray and main window self.tray = TrayMenu(self) self.main_window = MainWindow(self) if getattr(args, 'daemonize', False): pass elif SettingsModel.get(key='foreground').value: self.open_main_window_action() self.backup_started_event.connect(self.backup_started_event_response) self.backup_finished_event.connect(self.backup_finished_event_response) self.backup_cancelled_event.connect( self.backup_cancelled_event_response) self.message_received_event.connect( self.message_received_event_response) self.backup_log_event.connect(self.react_to_log) self.aboutToQuit.connect(self.quit_app_action) self.set_borg_details_action() if sys.platform == 'darwin': self.check_darwin_permissions() self.installEventFilter(self)
def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) self.versionLabel.setText(__version__) self.logLink.setText(f'<a href="file://{LOG_DIR}"><span style="text-decoration:' 'underline; color:#0984e3;">Log</span></a>') for setting in SettingsModel.select().where(SettingsModel.type == 'checkbox'): x = filter(lambda s: s['key'] == setting.key, get_misc_settings()) if not list(x): # Skip settings that aren't specified in vorta.models. continue b = QCheckBox(translate('settings', setting.label)) b.setCheckState(setting.value) b.setTristate(False) b.stateChanged.connect(lambda v, key=setting.key: self.save_setting(key, v)) self.checkboxLayout.addWidget(b)
def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) self.versionLabel.setText(__version__) for setting in SettingsModel.select().where( SettingsModel.type == 'checkbox'): x = filter(lambda s: s['key'] == setting.key, get_misc_settings()) if not list( x): # Skip settings that aren't specified in vorta.models. continue b = QCheckBox(translate('settings', setting.label)) b.setCheckState(setting.value) b.setTristate(False) b.stateChanged.connect( lambda v, key=setting.key: self.save_setting(key, v)) self.checkboxLayout.addWidget(b)
def populate(self): # clear layout while self.checkboxLayout.count(): child = self.checkboxLayout.takeAt(0) if child.widget(): child.widget().deleteLater() # dynamically add widgets for settings for setting in SettingsModel.select().where( SettingsModel.type == 'checkbox'): x = filter(lambda s: s['key'] == setting.key, get_misc_settings()) if not list( x): # Skip settings that aren't specified in vorta.models. continue b = QCheckBox(translate('settings', setting.label)) b.setCheckState(setting.value) b.setTristate(False) b.stateChanged.connect( lambda v, key=setting.key: self.save_setting(key, v)) self.checkboxLayout.addWidget(b)
def prepare(cls, profile): ret = super().prepare(profile) if not ret['ok']: return ret else: ret['ok'] = False # Set back to false, so we can do our own checks here. cmd = ['borg', '--log-json', 'mount'] # Try to override existing permissions when mounting an archive. May help to read # files that come from a different system, like a restrictive NAS. override_mount_permissions = SettingsModel.get(key='override_mount_permissions').value if override_mount_permissions: cmd += ['-o', f"umask=0277,uid={os.getuid()}"] cmd += [f"{profile.repo.url}"] ret['ok'] = True ret['cmd'] = cmd return ret
def __init__(self, parent=None): super().__init__() self.setupUi(self) self.setWindowTitle('Vorta for Borg Backup') self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) self.app = parent 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) 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_added.connect(self.archiveTab.list_action) self.tabWidget.currentChanged.connect(self.scheduleTab._draw_next_scheduled_backup) 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 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) self.profileSelector.currentIndexChanged.connect(self.profile_select_action) self.profileRenameButton.clicked.connect(self.profile_rename_action) self.profileDeleteButton.clicked.connect(self.profile_delete_action) 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 BorgThread.is_running(): self.createStartBtn.setEnabled(False) self.createStartBtn.start() self.cancelButton.setEnabled(True) self.set_icons()
def __init__(self, parent=None): super().__init__() self.setupUi(self) self.setWindowTitle('Vorta for Borg Backup') self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) self.app = parent self.current_profile = BackupProfileModel.select().order_by( 'id').first() self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint) # Temporary fix for QT Darkstyle dropdown issue. # See https://github.com/ColinDuquesnoy/QDarkStyleSheet/issues/200 if SettingsModel.get(key='use_dark_theme').value: self.setStyleSheet(""" QComboBox::item:checked { height: 12px; border: 1px solid #32414B; margin-top: 0px; margin-bottom: 0px; padding: 4px; padding-left: 0px; } """) # Load tab models self.repoTab = RepoTab(self.repoTabSlot) self.sourceTab = SourceTab(self.sourceTabSlot) self.archiveTab = ArchiveTab(self.archiveTabSlot) 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_added.connect(self.archiveTab.list_action) self.tabWidget.currentChanged.connect( self.scheduleTab._draw_next_scheduled_backup) 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_status) self.app.backup_cancelled_event.connect(self.backup_cancelled_event) # Init profile list for profile in BackupProfileModel.select(): self.profileSelector.addItem(profile.name, profile.id) self.profileSelector.setCurrentIndex(0) self.profileSelector.currentIndexChanged.connect( self.profile_select_action) self.profileRenameButton.clicked.connect(self.profile_rename_action) self.profileDeleteButton.clicked.connect(self.profile_delete_action) self.profileAddButton.clicked.connect(self.profile_add_action) # OS-specific startup options: if sys.platform != 'darwin': # 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 BorgThread.is_running(): self.createStartBtn.setEnabled(False) self.cancelButton.setEnabled(True) self.set_status(self.tr('Backup in progress.'), progress_max=0)
def get_priority(cls): return 1 if SettingsModel.get(key='use_system_keyring').value else 10
def set_tray_icon(tray, active=False): from vorta.models import SettingsModel use_light_style = SettingsModel.get(key='use_light_icon').value icon_name = f"icons/hdd-o{'-active' if active else ''}-{'light' if use_light_style else 'dark'}.png" icon = QIcon(get_asset(icon_name)) tray.setIcon(icon)
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