From 85df40fb134a8d65d9c2811b27311fe2592564d4 Mon Sep 17 00:00:00 2001
From: Boris Pozdena
Date: Fri, 25 Mar 2022 21:44:39 +0100
Subject: [PATCH] Added auto-sync feature on startup #29
---
src/OneDriveGUI-bob-manjaro.py | 1835 +++++++++++++++++
src/OneDriveGUI.py | 6 +
src/ui/profile_settings_page-bob-manjaro.ui | 824 ++++++++
.../ui_profile_settings_page-bob-manjaro.py | 696 +++++++
4 files changed, 3361 insertions(+)
create mode 100644 src/OneDriveGUI-bob-manjaro.py
create mode 100644 src/ui/profile_settings_page-bob-manjaro.ui
create mode 100644 src/ui/ui_profile_settings_page-bob-manjaro.py
diff --git a/src/OneDriveGUI-bob-manjaro.py b/src/OneDriveGUI-bob-manjaro.py
new file mode 100644
index 0000000..c45d09c
--- /dev/null
+++ b/src/OneDriveGUI-bob-manjaro.py
@@ -0,0 +1,1835 @@
+#!/usr/bin/env python3
+
+import os
+import re
+import subprocess
+import sys
+import logging
+import requests
+import copy
+import logging.handlers as handlers
+from configparser import ConfigParser
+
+from PySide6.QtCore import QThread, QTimer, QUrl, Signal, QFileInfo, Qt, QDir
+from PySide6.QtGui import QIcon, QPixmap, QDesktopServices
+from PySide6.QtWidgets import (
+ QWidget,
+ QApplication,
+ QMainWindow,
+ QMenu,
+ QSystemTrayIcon,
+ QListWidget,
+ QListWidgetItem,
+ QFileIconProvider,
+ QStackedLayout,
+ QVBoxLayout,
+ QLabel,
+ QAbstractItemView,
+ QWizard,
+ QWizardPage,
+ QLineEdit,
+ QCheckBox,
+ QPushButton,
+ QFormLayout,
+ QGridLayout,
+ QFileDialog,
+)
+from urllib3 import HTTPSConnectionPool
+
+# TODO: Split into multiple files once all main features are implemented.
+# Import for login window.
+from ui.ui_login import Ui_LoginWindow
+
+# Imports for main window.
+from ui.ui_mainwindow import Ui_MainWindow
+from ui.ui_list_item_widget import Ui_list_item_widget
+from ui.ui_process_status_page import Ui_status_page
+
+
+# Imports for Setting windows.
+from ui.ui_settings import Ui_settings_window
+from ui.ui_profile_settings_page import Ui_profile_settings
+from ui.ui_import_existing_profile import Ui_import_profile
+from ui.ui_create_new_profile import Ui_create_new_profile
+
+
+PROFILES_FILE = os.path.expanduser("~/.config/onedrive-gui/profiles")
+dir_path = os.path.dirname(os.path.realpath(__file__))
+
+# Logging
+# TODO: Implement logging configuration into the GUI.
+log_dir = os.path.expanduser("~/.config/onedrive-gui/")
+log_file = f"{log_dir}onedrivegui.log"
+if not os.path.exists(log_dir):
+ os.makedirs(log_dir)
+timed_handler = handlers.TimedRotatingFileHandler(filename=log_file, when="D", interval=1, backupCount=2)
+stdout_handler = logging.StreamHandler(sys.stdout)
+handlers = [timed_handler, stdout_handler]
+
+logging.basicConfig(
+ format="%(asctime)s - %(levelname)s - %(message)s",
+ handlers=handlers,
+ level=logging.DEBUG,
+)
+
+
+class SetupWizard(QWizard):
+ def __init__(self, parent=None):
+ super(SetupWizard, self).__init__(parent)
+ self.setWindowIcon(QIcon(dir_path + "/resources/images/icons8-clouds-48.png"))
+
+ self.setPage(1, WizardPage_welcome(self))
+ self.setPage(2, wizardPage_version_check(self))
+ self.setPage(3, wizardPage_create_import(self))
+ self.setPage(4, wizardPage_create(self))
+ self.setPage(5, wizardPage_import(self))
+ self.setPage(6, wizardPage_finish(self))
+
+ self.setWindowTitle("OneDriveGUI Setup Wizard")
+ self.resize(640, 480)
+
+ self.currentIdChanged.connect(self.on_page_change)
+
+ def on_page_change(self):
+ if self.currentId() == 2:
+ logging.info("Checking installed OneDrive version")
+ self.page(2).check_onedrive_version()
+
+ def nextId(self):
+ if self.currentPage() == self.page(1):
+ return 2
+ if self.currentPage() == self.page(2):
+ return 3
+ if self.currentPage() == self.page(3):
+ if self.page(3).checkBox_create.isChecked():
+ return 4
+ elif self.page(3).checkBox_import.isChecked():
+ return 5
+ if self.currentPage() == self.page(4):
+ return 6
+ if self.currentPage() == self.page(5):
+ return 6
+ if self.currentPage() == self.page(6):
+ return -1
+
+
+class WizardPage_welcome(QWizardPage):
+ def __init__(self, parent=None):
+ super(WizardPage_welcome, self).__init__(parent)
+
+ self.setTitle("Welcome to OneDriveGUI")
+
+ self.label_2 = QLabel()
+ self.label_2.setText("This wizard will help you with initial OneDrive profile creation/import.")
+
+ layout = QVBoxLayout()
+ layout.addWidget(self.label_2)
+ self.setLayout(layout)
+
+
+class wizardPage_version_check(QWizardPage):
+ def __init__(self, parent=None):
+ super(wizardPage_version_check, self).__init__(parent)
+ self.setTitle("OneDrive version check")
+
+ self.label_4 = QLabel()
+ self.label_4.setText("Installed/Not Installed/ version")
+
+ self.label_5 = QLabel()
+ self.label_5.setWordWrap(True)
+ self.label_5.setStyleSheet("color: red;")
+ self.label_5.setOpenExternalLinks(True)
+ self.label_5.setText(
+ "OneDrive Client for Linux does not seem to be installed. Please install it by following "
+ "instructions for your distro. "
+ )
+
+ layout = QVBoxLayout()
+ # layout.addWidget(self.label_3)
+ layout.addWidget(self.label_4)
+ layout.addWidget(self.label_5)
+ self.setLayout(layout)
+
+ def check_onedrive_version(self):
+ # Check if OneDrive is installed
+ # TODO: Minimim version check not yet enforced
+ try:
+ client_version_check = subprocess.check_output(["onedrive", "--version"], stderr=subprocess.STDOUT)
+ installed_client_version = re.search(r".\s(v[0-9.]+)", str(client_version_check)).group(1)
+ installed_client_version_num = int(installed_client_version.replace("v", "").replace(".", ""))
+ min_supported_version_num = 2415
+
+ if installed_client_version_num < min_supported_version_num:
+ logging.info(f"Unsupported OneDrive {installed_client_version} detected.")
+ self.label_4.setOpenExternalLinks(True)
+ self.label_4.setText(
+ f'OneDrive {installed_client_version} is not supported. Please upgrade it.'
+ )
+ self.label_4.setStyleSheet("color: red;")
+ self.label_5.hide()
+ self.completeChanged.emit()
+
+ return False
+
+ if "onedrive v" in str(client_version_check):
+ logging.info(f"OneDrive {installed_client_version} detected.")
+ self.label_4.setText(f"OneDrive {installed_client_version} detected.")
+ self.label_4.setStyleSheet("color: green;")
+ self.label_5.hide()
+ self.completeChanged.emit()
+
+ return True
+
+ else:
+ self.label_4.setText("OneDrive not detected.")
+ self.label_4.setStyleSheet("color: red;")
+ self.completeChanged.emit()
+ return False
+
+ except FileNotFoundError:
+ self.label_4.setText("OneDrive not detected.")
+ self.label_4.setStyleSheet("color: red;")
+ self.completeChanged.emit()
+ return False
+
+ def isComplete(self):
+ if "not" in self.label_4.text().lower():
+ return False
+ else:
+ return True
+
+
+class wizardPage_create_import(QWizardPage):
+ def __init__(self, parent=None):
+ super(wizardPage_create_import, self).__init__(parent)
+ self.setTitle("Create/Import OneDrive profile")
+
+ self.checkBox_create = QCheckBox()
+ self.checkBox_create.setText("Create new OneDrive profile")
+ self.checkBox_create.stateChanged.connect(self.on_checkbox_change)
+
+ self.checkBox_import = QCheckBox()
+ self.checkBox_import.setText("Import existing OneDrive profile/config")
+ self.checkBox_import.stateChanged.connect(self.on_checkbox_change)
+
+ layout = QVBoxLayout()
+ layout.addWidget(self.checkBox_create)
+ layout.addWidget(self.checkBox_import)
+ self.setLayout(layout)
+
+ def on_checkbox_change(self):
+ if self.checkBox_create.isChecked():
+ logging.info(f"Create new profile is checked")
+ self.checkBox_import.setDisabled(True)
+ self.completeChanged.emit()
+
+ elif self.checkBox_import.isChecked():
+ logging.info(f"Import profile is checked")
+ self.checkBox_create.setDisabled(True)
+ self.completeChanged.emit()
+
+ else:
+ logging.info(f"No option is unchecked")
+ self.checkBox_import.setDisabled(False)
+ self.checkBox_create.setDisabled(False)
+ self.completeChanged.emit()
+
+ def isComplete(self):
+ if self.checkBox_create.isChecked() == False and self.checkBox_import.isChecked() == False:
+ logging.info("not complete")
+ return False
+ else:
+ logging.info("complete")
+ return True
+
+
+class wizardPage_create(QWizardPage):
+ def __init__(self, parent=None):
+ super(wizardPage_create, self).__init__(parent)
+ self.setTitle("Create OneDrive profile")
+
+ self.label_new_profile_name = QLabel()
+ self.label_new_profile_name.setText("New profile name")
+
+ self.lineEdit_new_profile_name = QLineEdit()
+ self.lineEdit_new_profile_name.setPlaceholderText("E.g. john@live.com")
+ self.lineEdit_new_profile_name.textChanged.connect(self.update_sync_dir)
+ self.lineEdit_new_profile_name.textChanged.connect(self.enable_create_button)
+
+ self.label_sync_dir = QLabel()
+ self.label_sync_dir.setText("Sync directory")
+
+ self.lineEdit_sync_dir = QLineEdit()
+ self.lineEdit_sync_dir.setPlaceholderText("E.g. ~/OneDrive_john@live.com/")
+ self.lineEdit_sync_dir.textChanged.connect(self.enable_create_button)
+
+ self.pushButton_browse = QPushButton()
+ self.pushButton_browse.setText("Browse")
+ self.pushButton_browse.clicked.connect(self.get_dir_name)
+
+ self.pushButton_create = QPushButton()
+ self.pushButton_create.setText("Create new profile")
+ self.pushButton_create.setEnabled(False)
+ self.pushButton_create.clicked.connect(self.create_profile)
+
+ layout = QGridLayout()
+ layout.addWidget(self.label_new_profile_name, 0, 0)
+ layout.addWidget(self.lineEdit_new_profile_name, 0, 1)
+ layout.addWidget(self.label_sync_dir, 1, 0)
+ layout.addWidget(self.lineEdit_sync_dir, 1, 1)
+ layout.addWidget(self.pushButton_browse, 1, 2)
+ layout.addWidget(self.pushButton_create, 2, 0, 1, 3)
+ self.setLayout(layout)
+
+ def isComplete(self):
+ if self.pushButton_create.text() == "Done":
+ return True
+ return False
+
+ def get_dir_name(self):
+ self.file_dialog = QFileDialog.getExistingDirectory(dir=os.path.expanduser("~/"))
+
+ dir_name = self.file_dialog
+ logging.info(dir_name)
+ self.lineEdit_sync_dir.setText(dir_name)
+
+ def update_sync_dir(self, text):
+ self.lineEdit_sync_dir.setText(f"~/OneDrive_{text}")
+
+ def enable_create_button(self):
+ # Enable 'Import' button only when profile name and path to config file are not empty.
+ if self.lineEdit_new_profile_name.text() == "".strip() or self.lineEdit_sync_dir == "".strip():
+ self.pushButton_create.setEnabled(False)
+ else:
+ self.pushButton_create.setEnabled(True)
+
+ def create_profile(self):
+ """
+ Creates new profile and loads default settings.
+ TODO: Consolidate with import_profile()
+ """
+ profile_name = self.lineEdit_new_profile_name.text()
+ sync_dir = self.lineEdit_sync_dir.text()
+ config_path = os.path.expanduser(f"~/.config/onedrive/accounts/{profile_name}/config")
+
+ # Load all default values.
+ _default_od_config = read_config(dir_path + "/resources/default_config")
+ default_od_config = _default_od_config._sections
+
+ # Construct dict with user profile settings.
+ new_profile = {
+ profile_name: {"config_file": config_path, "enable_debug": False, "mode": "monitor", "auto_sync": False}
+ }
+
+ # Load existing user profiles and add the new profile.
+ _profiles = ConfigParser()
+ _profiles.read(PROFILES_FILE)
+ _profiles[profile_name] = new_profile[profile_name]
+
+ # Create profile config file if it does not exist.
+ profiles_dir = re.search(r"(.+)/profiles$", PROFILES_FILE).group(1)
+ if not os.path.exists(profiles_dir):
+ os.makedirs(profiles_dir)
+
+ # Save the new profile.
+ with open(PROFILES_FILE, "w") as profilefile:
+ _profiles.write(profilefile)
+
+ # Append default OD config
+ new_profile[profile_name].update(default_od_config)
+
+ # Configure sync directory
+ new_profile[profile_name]["onedrive"]["sync_dir"] = f'"{sync_dir}"'
+
+ # Append new profile into running global profile
+ global_config.update(new_profile)
+
+ # Automatically save global config to prevent loss if user does not press 'Save' button.
+ save_global_config()
+
+ # Add Setting page widget for new profile
+ settings_window.listWidget_profiles.addItem(profile_name)
+ self.setting_page = ProfileSettingsPage(profile_name)
+ settings_window.stackedLayout.addWidget(self.setting_page)
+
+ # Add status page widget for new profile
+ main_window.comboBox.addItem(profile_name)
+ main_window.profile_status_pages[profile_name] = ProfileStatusPage(profile_name)
+ main_window.stackedLayout.addWidget(main_window.profile_status_pages[profile_name])
+
+ # Hide "Create profile" push button from main windows.
+ main_window.pushButton_new_profile.hide()
+
+ logging.info(f"Account {profile_name} has been created")
+ self.pushButton_create.setText("Done")
+ self.pushButton_create.setDisabled(True)
+ self.completeChanged.emit()
+
+
+class wizardPage_import(QWizardPage):
+ def __init__(self, parent=None):
+ super(wizardPage_import, self).__init__(parent)
+ self.setTitle("Import OneDrive profile")
+
+ self.label_profile_name = QLabel()
+ self.label_profile_name.setText("Profile name")
+
+ self.label_config_path = QLabel()
+ self.label_config_path.setText("Config file path")
+
+ self.lineEdit_profile_name = QLineEdit()
+ self.lineEdit_profile_name.setPlaceholderText("E.g. john@live.com")
+ self.lineEdit_profile_name.textChanged.connect(self.enable_import_button)
+
+ self.lineEdit_config_path = QLineEdit()
+ self.lineEdit_config_path.setPlaceholderText("E.g. ~/.config/onedrive/config")
+ self.lineEdit_config_path.textChanged.connect(self.enable_import_button)
+
+ self.pushButton_browse = QPushButton()
+ self.pushButton_browse.setText("Browse")
+ self.pushButton_browse.clicked.connect(self.get_config_name)
+
+ self.pushButton_import = QPushButton()
+ self.pushButton_import.setText("Import")
+ self.pushButton_import.setEnabled(False)
+ self.pushButton_import.clicked.connect(self.import_profile)
+
+ # self.registerField("label_profile_name*",self.label_profile_name)
+
+ layout = QGridLayout()
+ layout.addWidget(self.label_profile_name, 0, 0)
+ layout.addWidget(self.label_config_path, 1, 0)
+ layout.addWidget(self.lineEdit_profile_name, 0, 1)
+ layout.addWidget(self.lineEdit_config_path, 1, 1)
+ layout.addWidget(self.pushButton_browse, 1, 2)
+ layout.addWidget(self.pushButton_import, 2, 0, 2, 3)
+ self.setLayout(layout)
+
+ def isComplete(self):
+ # Enable 'Next' button only when profile config was successfully imported
+ if self.pushButton_import.text() == "Done":
+ return True
+ return False
+
+ def enable_import_button(self):
+ # Enable 'Import' button only when profile name and path to config file are valid.
+ profile_name_filled = self.lineEdit_profile_name.text() != "".strip()
+
+ config_specified = re.search(r"/config$", self.lineEdit_config_path.text().strip()) != None
+ config_path = os.path.expanduser(self.lineEdit_config_path.text().strip())
+ config_exists = os.path.exists(config_path)
+
+ if all([profile_name_filled, config_specified, config_exists]):
+ self.pushButton_import.setEnabled(True)
+ else:
+ self.pushButton_import.setEnabled(False)
+
+ def get_config_name(self):
+ self.file_dialog = QFileDialog.getOpenFileName(self, dir=os.path.expanduser("~/.config/"))
+
+ file_name = self.file_dialog[0]
+
+ logging.info(file_name)
+ self.lineEdit_config_path.setText(file_name)
+
+ def import_profile(self):
+ """
+ Imports pre-existing OneDrive profile.
+ Loads default values first, then overwrite them with user settings.
+ This is to handle cases where imported config contains only some properties.
+ """
+
+ profile_name = self.lineEdit_profile_name.text().strip()
+ config_path = os.path.expanduser(self.lineEdit_config_path.text())
+
+ # Load all default values.
+ _default_od_config = read_config(dir_path + "/resources/default_config")
+ default_od_config = _default_od_config._sections
+ logging.debug("[GUI] default_od_config: " + str(default_od_config))
+
+ # Load user's settings.
+ _new_od_config = read_config(config_path)
+ new_od_config = _new_od_config._sections
+ logging.debug("[GUI] new_od_config: " + str(new_od_config))
+
+ # Construct dict with user profile settings.
+ new_profile = {
+ profile_name: {"config_file": config_path, "enable_debug": False, "mode": "monitor", "auto_sync": False}
+ }
+
+ # Load existing user profiles and add the new profile.
+ _profiles = ConfigParser()
+ _profiles.read(PROFILES_FILE)
+ _profiles[profile_name] = new_profile[profile_name]
+
+ # Create profile config file if it does not exist.
+ profiles_dir = re.search(r"(.+)/profiles$", PROFILES_FILE).group(1)
+ if not os.path.exists(profiles_dir):
+ os.makedirs(profiles_dir)
+
+ # Save the new profile.
+ with open(PROFILES_FILE, "w") as profilefile:
+ _profiles.write(profilefile)
+
+ # Append OD config
+ new_profile[profile_name].update(default_od_config)
+ for key, value in new_od_config["onedrive"].items():
+ new_profile[profile_name]["onedrive"][key] = value
+
+ # new_profile[profile_name].update(new_od_config)
+
+ # Append new profile into running global profile
+ global_config.update(new_profile)
+ logging.debug("[GUI] new_profile: " + str(new_profile))
+ logging.debug("[GUI] global_config: " + str(global_config))
+
+ settings_window.listWidget_profiles.addItem(profile_name)
+ self.setting_page = ProfileSettingsPage(profile_name)
+ settings_window.stackedLayout.addWidget(self.setting_page)
+
+ # Add status page widget for new profile
+ main_window.comboBox.addItem(profile_name)
+ main_window.profile_status_pages[profile_name] = ProfileStatusPage(profile_name)
+ main_window.stackedLayout.addWidget(main_window.profile_status_pages[profile_name])
+
+ # Hide "Create profile" push button from main window.
+ main_window.pushButton_new_profile.hide()
+
+ # Automatically save global config to prevent loss if user does not press 'Save' button.
+ save_global_config()
+
+ logging.info(f"Account {profile_name} has been imported")
+ self.pushButton_import.setText("Done")
+ self.pushButton_import.setDisabled(True)
+ self.completeChanged.emit()
+
+
+class wizardPage_finish(QWizardPage):
+ def __init__(self, parent=None):
+ super(wizardPage_finish, self).__init__(parent)
+ self.setTitle("Finished")
+ self.label_13 = QLabel()
+ self.label_13.setWordWrap(True)
+ self.label_13.setText(
+ "
Your OneDrive profile has been created. "
+ "
You can adjust advanced profile options if needed. "
+ "Once you are happy with the profile settings, start sync with OneDrive. "
+ "You will be automatically presented with a OneDrive login page if you have not logged on previously.
"
+ )
+
+ layout = QFormLayout()
+ layout.addWidget(self.label_13)
+ self.setLayout(layout)
+
+
+class SettingsWindow(QWidget, Ui_settings_window):
+ def __init__(self):
+ super(SettingsWindow, self).__init__()
+
+ self.setupUi(self)
+ self.setWindowIcon(QIcon(dir_path + "/resources/images/icons8-clouds-48.png"))
+
+ self.stackedLayout = QStackedLayout()
+
+ for profile in global_config:
+ logging.info(profile)
+ self.listWidget_profiles.addItem(profile)
+ self.page = ProfileSettingsPage(profile)
+ self.stackedLayout.addWidget(self.page)
+
+ self.horizontalLayout.addLayout(self.stackedLayout)
+ self.listWidget_profiles.itemSelectionChanged.connect(self.switch_account_settings_page)
+
+ self.pushButton_open_create.clicked.connect(self.create_new_profile_window)
+ self.pushButton_open_create.hide()
+ self.pushButton_open_import.clicked.connect(self.import_profile_window)
+ self.pushButton_open_import.hide()
+
+ self.pushButton_remove.clicked.connect(self.remove_profile)
+ self.pushButton_create_import.clicked.connect(self.show_setup_wizard)
+ self.show()
+
+ def closeEvent(self, event):
+ event.ignore()
+ self.hide()
+
+ def switch_account_settings_page(self):
+ self.stackedLayout.setCurrentIndex(self.listWidget_profiles.currentRow())
+
+ def show_setup_wizard(self):
+ self.setup_wizard = SetupWizard()
+ self.setup_wizard.setStartId(3)
+ self.setup_wizard.show()
+
+ def create_new_profile_window(self):
+ # Show profile creation window
+ self.create_window = QWidget()
+ self.create_window.setWindowIcon(QIcon(dir_path + "/resources/images/icons8-clouds-48.png"))
+ self.create_ui = Ui_create_new_profile()
+ self.create_ui.setupUi(self.create_window)
+ self.create_window.show()
+
+ self.create_ui.pushButton_create.clicked.connect(self.create_profile)
+
+ # Hide window once account is created.
+ self.create_ui.pushButton_create.clicked.connect(self.create_window.hide)
+
+ def import_profile_window(self):
+ # Show profile import window
+ self.import_window = QWidget()
+ self.import_window.setWindowIcon(QIcon(dir_path + "/resources/images/icons8-clouds-48.png"))
+ self.import_ui = Ui_import_profile()
+ self.import_ui.setupUi(self.import_window)
+ self.import_window.show()
+
+ self.import_ui.pushButton_import.clicked.connect(self.import_profile)
+
+ # Hide window once account is created
+ self.import_ui.pushButton_import.clicked.connect(self.import_window.hide)
+
+ def remove_profile(self):
+ # Remove profile from settings window.
+ selected_profile_name = self.listWidget_profiles.currentItem().text()
+ selected_profile_index = self.listWidget_profiles.currentRow()
+ selected_profile_widget = self.stackedLayout.currentWidget()
+ self.listWidget_profiles.takeItem(selected_profile_index)
+ self.stackedLayout.removeWidget(selected_profile_widget)
+
+ # Remove profile from main window.
+ combo_box_index = main_window.comboBox.findText(selected_profile_name)
+ main_window.comboBox.removeItem(combo_box_index)
+ main_window.profile_status_pages.pop(selected_profile_name, None)
+ global_config.pop(selected_profile_name, None)
+ logging.info(global_config)
+
+ # Load existing user profiles and remove the new profile.
+ _profiles = ConfigParser()
+ _profiles.read(PROFILES_FILE)
+ _profiles.remove_section(selected_profile_name)
+
+ # Save the new profile.
+ with open(PROFILES_FILE, "w") as profilefile:
+ _profiles.write(profilefile)
+
+ def create_profile(self):
+ """
+ Creates new profile and loads default settings.
+ TODO: Consolidate with import_profile()
+ """
+ profile_name = self.create_ui.lineEdit_new_profile_name.text()
+ sync_dir = self.create_ui.lineEdit_sync_dir.text()
+ config_path = os.path.expanduser(f"~/.config/onedrive/accounts/{profile_name}/config")
+
+ # Load all default values.
+ _default_od_config = read_config(dir_path + "/resources/default_config")
+ default_od_config = _default_od_config._sections
+
+ # Construct dict with user profile settings.
+ new_profile = {
+ profile_name: {"config_file": config_path, "enable_debug": False, "mode": "monitor", "auto_sync": False}
+ }
+
+ # Load existing user profiles and add the new profile.
+ _profiles = ConfigParser()
+ _profiles.read(PROFILES_FILE)
+ _profiles[profile_name] = new_profile[profile_name]
+
+ # Create profile config file if it does not exist.
+ profiles_dir = re.search(r"(.+)/profiles$", PROFILES_FILE).group(1)
+ if not os.path.exists(profiles_dir):
+ os.makedirs(profiles_dir)
+
+ # Save the new profile.
+ with open(PROFILES_FILE, "w") as profilefile:
+ _profiles.write(profilefile)
+
+ # Append default OD config
+ new_profile[profile_name].update(default_od_config)
+
+ # Configure sync directory
+ new_profile[profile_name]["onedrive"]["sync_dir"] = f'"{sync_dir}"'
+
+ # Append new profile into running global profile
+ global_config.update(new_profile)
+
+ # Automatically save global config to prevent loss if user does not press 'Save' button.
+ save_global_config()
+
+ # Add Setting page widget for new profile
+ self.listWidget_profiles.addItem(profile_name)
+ self.page = ProfileSettingsPage(profile_name)
+ self.stackedLayout.addWidget(self.page)
+
+ # Add status page widget for new profile
+ main_window.comboBox.addItem(profile_name)
+ main_window.profile_status_pages[profile_name] = ProfileStatusPage(profile_name)
+ main_window.stackedLayout.addWidget(main_window.profile_status_pages[profile_name])
+
+ # Hide "Create profile" push button from main windows.
+ main_window.pushButton_new_profile.hide()
+
+ def import_profile(self):
+ """
+ Imports pre-existing OneDrive profile.
+ Loads default values first, then overwrite them with user settings.
+ This is to handle cases where imported config contains only some properties.
+ """
+
+ profile_name = self.import_ui.lineEdit_profile_name.text()
+ config_path = os.path.expanduser(self.import_ui.lineEdit_config_path.text())
+
+ # Load all default values.
+ _default_od_config = read_config(dir_path + "/resources/default_config")
+ default_od_config = _default_od_config._sections
+
+ # Load user's settings.
+ _new_od_config = read_config(config_path)
+ new_od_config = _new_od_config._sections
+
+ # Construct dict with user profile settings.
+ new_profile = {
+ profile_name: {"config_file": config_path, "enable_debug": False, "mode": "monitor", "auto_sync": False}
+ }
+
+ # Load existing user profiles and add the new profile.
+ _profiles = ConfigParser()
+ _profiles.read(PROFILES_FILE)
+ _profiles[profile_name] = new_profile[profile_name]
+
+ # Create profile config file if it does not exist.
+ profiles_dir = re.search(r"(.+)/profiles$", PROFILES_FILE).group(1)
+ if not os.path.exists(profiles_dir):
+ os.makedirs(profiles_dir)
+
+ # Save the new profile.
+ with open(PROFILES_FILE, "w") as configfile:
+ _profiles.write(configfile)
+
+ # Append OD config
+ new_profile[profile_name].update(default_od_config)
+ new_profile[profile_name].update(new_od_config)
+
+ # Append new profile into running global profile
+ global_config.update(new_profile)
+ # logging.info(new_profile)
+ # logging.info(global_config)
+
+ self.listWidget_profiles.addItem(profile_name)
+ self.page = ProfileSettingsPage(profile_name)
+ self.stackedLayout.addWidget(self.page)
+
+ # Hide "Create profile" push button from main windows.
+ main_window.pushButton_new_profile.hide()
+
+
+class ProfileStatusPage(QWidget, Ui_status_page):
+ def __init__(self, profile_name):
+ super(ProfileStatusPage, self).__init__()
+
+ self.profile_name = profile_name
+
+ # Set up the user interface from Designer.
+ self.setupUi(self)
+
+ # Temporary start/stop buttons
+ self.toolButton_start.clicked.connect(self.start_monitor)
+ self.toolButton_stop.clicked.connect(self.stop_monitor)
+
+ # Open Sync Dir
+ self.pushButton_open_dir.clicked.connect(self.open_sync_dir)
+
+ # Open Settings window
+ self.pushButton_settings.clicked.connect(lambda: settings_window.show())
+
+ self.pushButton_2.setText("Wizard")
+ self.pushButton_2.clicked.connect(self.show_setup_wizard)
+ self.pushButton_2.hide()
+
+ def open_sync_dir(self):
+ sync_dir = global_config[self.profile_name]["onedrive"]["sync_dir"].strip('"')
+ url = QUrl(os.path.expanduser(sync_dir))
+ QDesktopServices.openUrl(url)
+
+ def show_setup_wizard(self):
+ self.setup_wizard = SetupWizard()
+ self.setup_wizard.show()
+ # self.currentIdChanged.connect(logging.info("print test"))
+
+ def stop_monitor(self):
+ if self.profile_name in main_window.workers:
+ main_window.workers[self.profile_name].stop_worker()
+ self.label_onedrive_status.setText("OneDrive sync has been stopped")
+ logging.info(f"OneDrive sync for profile {self.profile_name} has been stopped.")
+ else:
+ logging.info(f"OneDrive for profile {self.profile_name} is not running.")
+
+ def start_monitor(self):
+ main_window.start_onedrive_monitor(self.profile_name)
+
+
+class ProfileSettingsPage(QWidget, Ui_profile_settings):
+ def __init__(self, profile):
+ super(ProfileSettingsPage, self).__init__()
+
+ self.profile = profile
+
+ # Set up the user interface from Designer.
+ self.setupUi(self)
+
+ temp_global_config = global_config
+ self.temp_profile_config = temp_global_config[self.profile]
+
+ self.label_profile_name.setText(self.profile)
+ self.tabWidget.setCurrentIndex(0)
+
+ #
+ # Monitored files tab
+ #
+ self.lineEdit_sync_dir.setText(self.temp_profile_config["onedrive"]["sync_dir"].strip('"'))
+ self.lineEdit_sync_dir.textChanged.connect(self.set_sync_dir)
+
+ self.checkBox_sync_root_files.setChecked(self.get_check_box_state("sync_root_files"))
+ self.checkBox_sync_root_files.stateChanged.connect(self.set_check_box_state)
+
+ self.pushButton_sync_dir_browse.clicked.connect(self.get_sync_dir_name)
+
+ #
+ # Excluded files tab
+ #
+
+ # Skip_file section
+ self.skip_files = self.temp_profile_config["onedrive"]["skip_file"].strip('"').split("|")
+ self.listWidget_skip_file.addItems(self.skip_files)
+ self.listWidget_skip_file.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.pushButton_add_skip_file.clicked.connect(self.add_skip_file)
+ self.pushButton_add_skip_file.clicked.connect(self.lineEdit_skip_file.clear)
+ self.pushButton_rm_skip_file.clicked.connect(self.remove_skip_file)
+
+ # Skip_dir section
+ self.skip_dirs = self.temp_profile_config["onedrive"]["skip_dir"].strip('"').split("|")
+ self.listWidget_skip_dir.addItems(self.skip_dirs)
+ self.listWidget_skip_dir.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.pushButton_add_skip_dir.clicked.connect(self.add_skip_dir)
+ self.pushButton_add_skip_dir.clicked.connect(self.lineEdit_skip_dir.clear)
+ self.pushButton_rm_skip_dir.clicked.connect(self.remove_skip_dir)
+
+ self.checkBox_skip_dir_strict_match.setChecked(self.get_check_box_state("skip_dir_strict_match"))
+ self.checkBox_skip_dir_strict_match.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_check_nosync.setChecked(self.get_check_box_state("check_nosync"))
+ self.checkBox_check_nosync.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_skip_symlinks.setChecked(self.get_check_box_state("skip_symlinks"))
+ self.checkBox_skip_symlinks.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_skip_dotfiles.setChecked(self.get_check_box_state("skip_dotfiles"))
+ self.checkBox_skip_dotfiles.stateChanged.connect(self.set_check_box_state)
+
+ #
+ # Sync Options tab
+ #
+ self.spinBox_monitor_interval.setValue(
+ int(self.temp_profile_config["onedrive"]["monitor_interval"].strip('"'))
+ )
+ self.spinBox_monitor_interval.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_monitor_fullscan_frequency.setValue(
+ int(self.temp_profile_config["onedrive"]["monitor_fullscan_frequency"].strip('"'))
+ )
+ self.spinBox_monitor_fullscan_frequency.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_classify_as_big_delete.setValue(
+ int(self.temp_profile_config["onedrive"]["classify_as_big_delete"].strip('"'))
+ )
+ self.spinBox_classify_as_big_delete.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_sync_dir_permissions.setValue(
+ int(self.temp_profile_config["onedrive"]["sync_dir_permissions"].strip('"'))
+ )
+ self.spinBox_sync_dir_permissions.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_sync_file_permissions.setValue(
+ int(self.temp_profile_config["onedrive"]["sync_file_permissions"].strip('"'))
+ )
+ self.spinBox_sync_file_permissions.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_operation_timeout.setValue(
+ int(self.temp_profile_config["onedrive"]["operation_timeout"].strip('"'))
+ )
+ self.spinBox_operation_timeout.valueChanged.connect(self.set_spin_box_value)
+
+ self.checkBox_download_only.setChecked(self.get_check_box_state("download_only"))
+ self.checkBox_download_only.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_upload_only.setChecked(self.get_check_box_state("upload_only"))
+ self.checkBox_upload_only.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_force_http_2.setChecked(self.get_check_box_state("force_http_2"))
+ self.checkBox_force_http_2.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_disable_upload_validation.setChecked(self.get_check_box_state("disable_upload_validation"))
+ self.checkBox_disable_upload_validation.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_check_nomount.setChecked(self.get_check_box_state("check_nomount"))
+ self.checkBox_check_nomount.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_local_first.setChecked(self.get_check_box_state("local_first"))
+ self.checkBox_local_first.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_no_remote_delete.setChecked(self.get_check_box_state("no_remote_delete"))
+ self.checkBox_no_remote_delete.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_sync_business_shared_folders.setChecked(self.get_check_box_state("sync_business_shared_folders"))
+ self.checkBox_sync_business_shared_folders.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_dry_run.setChecked(self.get_check_box_state("dry_run"))
+ self.checkBox_dry_run.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_remove_source_files.setChecked(self.get_check_box_state("remove_source_files"))
+ self.checkBox_remove_source_files.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_resync.setChecked(self.get_check_box_state("resync"))
+ self.checkBox_resync.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_bypass_data_preservation.setChecked(self.get_check_box_state("bypass_data_preservation"))
+ self.checkBox_bypass_data_preservation.stateChanged.connect(self.set_check_box_state)
+
+ self.lineEdit_user_agent.setText(self.temp_profile_config["onedrive"]["user_agent"].strip('"'))
+ self.lineEdit_user_agent.textChanged.connect(self.set_line_edit_value)
+
+ self.lineEdit_azure_ad_endpoint.setText(self.temp_profile_config["onedrive"]["azure_ad_endpoint"].strip('"'))
+ self.lineEdit_azure_ad_endpoint.textChanged.connect(self.set_line_edit_value)
+
+ self.lineEdit_azure_tenant_id.setText(self.temp_profile_config["onedrive"]["azure_tenant_id"].strip('"'))
+ self.lineEdit_azure_tenant_id.textChanged.connect(self.set_line_edit_value)
+
+ # Rate limit
+ self.spinBox_rate_limit.setValue(int(self.temp_profile_config["onedrive"]["rate_limit"].strip('"')))
+ self.horizontalSlider_rate_limit.setValue(int(self.temp_profile_config["onedrive"]["rate_limit"].strip('"')))
+ self.label_rate_limit_mbps.setText(
+ str(round(self.spinBox_rate_limit.value() * 8 / 1000 / 1000, 2)) + " Mbit/s"
+ )
+ self.spinBox_rate_limit.valueChanged.connect(self.set_spin_box_value)
+ self.spinBox_rate_limit.valueChanged.connect(self.horizontalSlider_rate_limit.setValue)
+ self.spinBox_rate_limit.valueChanged.connect(
+ lambda: self.label_rate_limit_mbps.setText(
+ str(round(self.spinBox_rate_limit.value() * 8 / 1000 / 1000, 2)) + " Mbit/s"
+ )
+ )
+ self.horizontalSlider_rate_limit.valueChanged.connect(self.spinBox_rate_limit.setValue)
+
+ #
+ # Webhooks tab
+ #
+ self.checkBox_webhook_enabled.setChecked(self.get_check_box_state("webhook_enabled"))
+ self.checkBox_webhook_enabled.stateChanged.connect(self.set_check_box_state)
+
+ self.spinBox_webhook_expiration_interval.setValue(
+ int(self.temp_profile_config["onedrive"]["webhook_expiration_interval"].strip('"'))
+ )
+ self.spinBox_webhook_expiration_interval.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_webhook_renewal_interval.setValue(
+ int(self.temp_profile_config["onedrive"]["webhook_renewal_interval"].strip('"'))
+ )
+ self.spinBox_webhook_renewal_interval.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_webhook_listening_port.setValue(
+ int(self.temp_profile_config["onedrive"]["webhook_listening_port"].strip('"'))
+ )
+ self.spinBox_webhook_listening_port.valueChanged.connect(self.set_spin_box_value)
+
+ self.lineEdit_webhook_public_url.setText(self.temp_profile_config["onedrive"]["webhook_public_url"].strip('"'))
+ self.lineEdit_webhook_public_url.textChanged.connect(self.set_line_edit_value)
+
+ self.lineEdit_webhook_listening_host.setText(
+ self.temp_profile_config["onedrive"]["webhook_listening_host"].strip('"')
+ )
+ self.lineEdit_webhook_listening_host.textChanged.connect(self.set_line_edit_value)
+
+ #
+ # Logging tab
+ #
+ self.lineEdit_log_dir.setText(self.temp_profile_config["onedrive"]["log_dir"].strip('"'))
+ self.lineEdit_log_dir.textChanged.connect(self.set_log_dir)
+
+ self.pushButton_log_dir_browse.clicked.connect(self.get_log_dir_name)
+
+ self.checkBox_enable_logging.setChecked(self.get_check_box_state("enable_logging"))
+ self.checkBox_enable_logging.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_debug_https.setChecked(self.get_check_box_state("debug_https"))
+ self.checkBox_debug_https.stateChanged.connect(self.set_check_box_state)
+
+ self.checkBox_disable_notifications.setChecked(self.get_check_box_state("disable_notifications"))
+ self.checkBox_disable_notifications.stateChanged.connect(self.set_check_box_state)
+
+ self.spinBox_monitor_log_frequency.setValue(
+ int(self.temp_profile_config["onedrive"]["monitor_log_frequency"].strip('"'))
+ )
+ self.spinBox_monitor_log_frequency.valueChanged.connect(self.set_spin_box_value)
+
+ self.spinBox_min_notify_changes.setValue(
+ int(self.temp_profile_config["onedrive"]["min_notify_changes"].strip('"'))
+ )
+ self.spinBox_min_notify_changes.valueChanged.connect(self.set_spin_box_value)
+
+ #
+ # Account tab
+ #
+ self.config_file = global_config[self.profile]["config_file"].strip('"')
+ self.config_dir = re.search(r"(.+)/.+$", self.config_file).group(1)
+
+ self.pushButton_login.clicked.connect(lambda: main_window.show_login(self.profile))
+ self.pushButton_login.hide()
+ self.pushButton_logout.clicked.connect(self.logout)
+
+ self.checkBox_auto_sync.setChecked(self.get_check_box_state_profile("auto_sync"))
+ self.checkBox_auto_sync.stateChanged.connect(self.set_check_box_state_profile)
+
+ #
+ # Buttons
+ #
+ self.pushButton_discart.hide()
+ # TODO: How to discart unsaved changes and refresh the widgets or close window?
+ # self.pushButton_discart.clicked.connect(self.discart_changes)
+ self.pushButton_save.clicked.connect(self.save_profile_settings)
+
+ def str2bool(self, value):
+ return value.lower() in "true"
+
+ def logout(self):
+ os.system(f"onedrive --confdir='{self.config_dir}' --logout")
+ logging.info(f"Profile {self.profile} has been logged out.")
+ # self.profile_status["status_message"] = "You have been logged out"
+
+ main_window.profile_status_pages[self.profile].stop_monitor()
+ # main_window.profile_status_pages[self.profile]["status_message"] = "You have been logged out"
+ if self.profile in main_window.workers:
+ main_window.workers[self.profile].stop_worker()
+ main_window.profile_status_pages[self.profile].label_onedrive_status.setText(
+ "OneDrive sync has been stopped"
+ )
+ logging.info(f"OneDrive sync for profile {self.profile} has been stopped.")
+ else:
+ logging.info(f"OneDrive for profile {self.profile} is not running.")
+
+ main_window.profile_status_pages[self.profile].label_onedrive_status.setText("You have been logged out")
+
+ def get_sync_dir_name(self):
+ self.file_dialog = QFileDialog.getExistingDirectory(dir=os.path.expanduser("~/"))
+
+ sync_dir = self.file_dialog
+ logging.info(sync_dir)
+ self.lineEdit_sync_dir.setText(sync_dir)
+
+ def get_log_dir_name(self):
+ self.file_dialog = QFileDialog.getExistingDirectory(dir=os.path.expanduser("~/"))
+
+ log_dir = self.file_dialog
+ logging.info(log_dir)
+ self.lineEdit_log_dir.setText(log_dir)
+
+ def set_line_edit_value(self, value):
+ _property = self.sender().objectName()
+ property = re.search(r"lineEdit_(.+)", _property).group(1)
+ self.temp_profile_config["onedrive"][f"{property}"] = f'"{value}"'
+
+ def set_spin_box_value(self, value):
+ _property = self.sender().objectName()
+ property = re.search(r"spinBox_(.+)", _property).group(1)
+ self.temp_profile_config["onedrive"][f"{property}"] = f'"{value}"'
+
+ def set_check_box_state(self, state):
+ _property = self.sender().objectName()
+ property = re.search(r"checkBox_(.+)", _property).group(1)
+ logging.info(property)
+ if state == Qt.Checked:
+ logging.info("is checked")
+ self.temp_profile_config["onedrive"][f"{property}"] = '"true"'
+ else:
+ logging.info("is unchecked")
+ self.temp_profile_config["onedrive"][f"{property}"] = '"false"'
+
+ def set_check_box_state_profile(self, state):
+ _property = self.sender().objectName()
+ property = re.search(r"checkBox_(.+)", _property).group(1)
+ logging.info(property)
+ if state == Qt.Checked:
+ logging.info("is checked")
+ self.temp_profile_config[f"{property}"] = True
+ else:
+ logging.info("is unchecked")
+ self.temp_profile_config[f"{property}"] = False
+
+ def get_check_box_state(self, property):
+ return self.temp_profile_config["onedrive"][f"{property}"].strip('"') in "true"
+
+ def get_check_box_state_profile(self, property):
+ return self.temp_profile_config[f"{property}"] == "True"
+
+ def add_skip_file(self):
+ self.add_item_to_qlist(self.lineEdit_skip_file, self.listWidget_skip_file, self.skip_files)
+ self.temp_profile_config["onedrive"]["skip_file"] = '"' + "|".join(self.skip_files) + '"'
+
+ def remove_skip_file(self):
+ self.remove_item_from_qlist(self.listWidget_skip_file, self.skip_files)
+ self.temp_profile_config["onedrive"]["skip_file"] = '"' + "|".join(self.skip_files) + '"'
+
+ def add_skip_dir(self):
+ self.add_item_to_qlist(self.lineEdit_skip_dir, self.listWidget_skip_dir, self.skip_dirs)
+ self.temp_profile_config["onedrive"]["skip_dir"] = '"' + "|".join(self.skip_dirs) + '"'
+
+ def remove_skip_dir(self):
+ self.remove_item_from_qlist(self.listWidget_skip_dir, self.skip_dirs)
+ self.temp_profile_config["onedrive"]["skip_dir"] = '"' + "|".join(self.skip_dirs) + '"'
+
+ def set_rate_limit(self):
+ self.temp_profile_config["onedrive"]["rate_limit"] = f'"{self.lineEdit_rate_limit.text()}"'
+ self.label_rate_limit_mbps.setText(
+ str(round(int(self.lineEdit_rate_limit.text()) * 8 / 1000 / 1000, 2)) + " Mbit/s"
+ )
+
+ def set_sync_dir(self):
+ self.temp_profile_config["onedrive"]["sync_dir"] = f'"{self.lineEdit_sync_dir.text()}"'
+
+ def set_log_dir(self):
+ self.temp_profile_config["onedrive"]["log_dir"] = f'"{self.lineEdit_log_dir.text()}"'
+
+ def add_item_to_qlist(self, source_widget, destination_widget, list):
+ if source_widget.text() == "":
+ logging.info("Ignoring empty value.")
+ elif source_widget.text() in list:
+ logging.info("Item already in exemption list.")
+ else:
+ list.append(source_widget.text())
+ destination_widget.addItem(source_widget.text())
+
+ def remove_item_from_qlist(self, qlistwidget_name, list):
+ for item in qlistwidget_name.selectedItems():
+ logging.info("Removing: " + item.text())
+ # items.remove(item.text())
+ qlistwidget_name.takeItem(qlistwidget_name.row(item))
+ logging.info(list)
+ list.remove(item.text())
+
+ def save_profile_settings(self):
+ global_config[self.profile].update(self.temp_profile_config)
+ logging.debug("save_profile_settings" + "self.temp_profile_config" + str(self.temp_profile_config))
+ logging.debug("save_profile_settings" + "global_config" + str(global_config))
+ save_global_config()
+
+ def discart_changes(self):
+ self.temp_profile_config = None
+ self.close()
+
+
+class TaskList(QWidget, Ui_list_item_widget):
+ def __init__(self):
+ super(TaskList, self).__init__()
+
+ # Set up the user interface from Designer.
+ self.setupUi(self)
+
+ def set_icon(self, file_path):
+ self.fileInfo = QFileInfo(file_path)
+ self.iconProvider = QFileIconProvider()
+ self.icon = self.iconProvider.icon(self.fileInfo)
+
+ # icon = QIcon(icon_file)
+ self.toolButton.setIcon(self.icon)
+
+ def set_file_name(self, file_path):
+ self.ls_label_file_name.setText(file_path)
+
+ def get_file_name(self):
+ return self.ls_label_file_name.text()
+
+ def set_progress(self, percentage):
+ self.ls_progressBar.setValue(percentage)
+
+ def set_label_1(self, text):
+ self.ls_label_1.setOpenExternalLinks(True)
+ self.ls_label_1.setText(text)
+
+ def set_label_2(self, text):
+ self.ls_label_2.setText(text)
+
+ def hide_progress_bar(self, transfer_status: bool):
+ # pass
+ if transfer_status:
+ self.ls_progressBar.hide()
+ else:
+ self.ls_progressBar.show()
+
+
+class WorkerThread(QThread):
+ """
+ Constructs a thread, which can start, monitor and stop OneDrive process.
+ """
+
+ update_credentials = Signal(str)
+ update_progress_new = Signal(dict, str)
+ update_profile_status = Signal(dict, str)
+ trigger_resync = Signal()
+
+ def __init__(self, profile):
+ super(WorkerThread, self).__init__()
+ logging.info(f"[GUI] Starting worker for profile {profile}")
+
+ self.config_file = global_config[profile]["config_file"]
+ self.config_dir = re.search(r"(.+)/.+$", self.config_file)
+ logging.debug(f"[GUI] OneDrive config file: {self.config_file}")
+ logging.debug(f"[GUI] OneDrive config dir: {self.config_dir}")
+ self._command = f"onedrive --confdir='{self.config_dir.group(1)}' --monitor -v"
+ logging.debug(f"[GUI] Monitoring command: '{self._command}'")
+ self.profile_name = profile
+
+ def stop_worker(self):
+ logging.info(f"[{self.profile_name}] Waiting for worker to finish...")
+ while self.onedrive_process.poll() is None:
+ self.onedrive_process.kill()
+
+ logging.info(f"[{self.profile_name}] Quiting thread")
+ self.quit()
+ self.wait()
+ logging.info(f"[{self.profile_name}] Removing thread info")
+
+ main_window.workers.pop(self.profile_name, None)
+ logging.info(f"[GUI] Remaining running workers: {main_window.workers}")
+
+ def run(self, resync=False):
+ """
+ Starts OneDrive and sends signals to GUI based on parsed information.
+ """
+
+ file_name = None
+
+ tasks = [
+ "Downloading file",
+ "Downloading new file",
+ "Uploading file",
+ "Uploading new file",
+ "Uploading modified file",
+ "Downloading modified file",
+ "Deleting item",
+ ]
+
+ self.profile_status = {"status_message": "", "free_space": "", "account_type": ""}
+
+ self.onedrive_process = subprocess.Popen(
+ self._command + "--resync" if resync else self._command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=True,
+ universal_newlines=True,
+ )
+
+ while self.onedrive_process.poll() is None:
+ if self.onedrive_process.stdout:
+ stdout = self.onedrive_process.stdout.readline()
+ if stdout == "":
+ continue
+
+ # logging.info(bytes(stdout, 'utf-8'))
+ logging.info(f"[{self.profile_name}] " + stdout.strip())
+
+ if "Authorize this app visiting" in stdout:
+ self.onedrive_process.kill()
+ self.profile_status["status_message"] = "OneDrive login is required"
+ self.update_profile_status.emit(self.profile_status, self.profile_name)
+ self.update_credentials.emit(self.profile_name)
+
+ elif "Sync with OneDrive is complete" in stdout:
+ self.profile_status["status_message"] = "OneDrive sync is complete"
+ self.update_profile_status.emit(self.profile_status, self.profile_name)
+
+ elif "Remaining Free Space" in stdout:
+ self.free_space_bytes = re.search(r"([0-9]+)", stdout).group(1)
+ self.free_space_human = str(humanize_file_size(int(self.free_space_bytes)))
+
+ logging.info(f"[{self.profile_name}] Free Space: {self.free_space_human}")
+ self.profile_status["free_space"] = f"Free Space: {self.free_space_human}"
+ # self.profile_status["account_type"] = f"{self.account_type.capitalize()} [{self.free_space_human}]"
+ self.update_profile_status.emit(self.profile_status, self.profile_name)
+
+ elif "Account Type" in stdout:
+ self.account_type = re.search(r"\s(\w+)$", stdout).group(1)
+ self.profile_status["account_type"] = self.account_type.capitalize()
+ logging.info(f"[{self.profile_name}] Account type: {self.account_type}")
+ self.update_profile_status.emit(self.profile_status, self.profile_name)
+
+ elif "Initializing the OneDrive API" in stdout:
+ self.profile_status["status_message"] = "Initializing the OneDrive API"
+ self.update_profile_status.emit(self.profile_status, self.profile_name)
+
+ elif "Processing" in stdout:
+ items_left = re.match(r"^Processing\s([0-9]+)", stdout)
+ if items_left != None:
+ self.profile_status[
+ "status_message"
+ ] = f"OneDrive is processing {items_left.group(1)} items..."
+ else:
+ self.profile_status["status_message"] = "OneDrive is processing items..."
+ self.update_profile_status.emit(self.profile_status, self.profile_name)
+
+ elif any(_ in stdout for _ in tasks):
+ # Capture information about file that is being uploaded/downloaded/deleted by OneDrive.
+ file_operation = re.search(r"\b([Uploading|Downloading|Deleting]+)*", stdout).group(1)
+
+ if file_operation == "Deleting":
+ file_name = re.search(r".*/(.+)$", stdout)
+ file_path = re.search(r"\b[item|OneDrive:]+\s(.+)$", stdout)
+
+ else:
+ file_name = re.search(r".*/(.+)\s+\.+", stdout)
+ file_path = re.search(r"\b[file]+\s(.+)\s+\.\.\.", stdout)
+
+ transfer_complete = any(["done" in stdout, "Deleting" in stdout])
+ progress = "0"
+
+ transfer_progress_new = {
+ "file_operation": file_operation,
+ "file_path": "unknown file name" if file_path is None else file_path.group(1),
+ "progress": progress,
+ "transfer_complete": transfer_complete,
+ }
+
+ # Update file transfer list
+ logging.info(transfer_progress_new)
+ self.update_progress_new.emit(transfer_progress_new, self.profile_name)
+
+ # Update profile status message.
+ if transfer_complete:
+ self.profile_status["status_message"] = "OneDrive sync is complete"
+
+ elif "% |" in stdout and file_name is not None:
+ # Capture upload/download progress status
+
+ file_operation = re.search(r"\b([Uploading|Downloading]+)*", stdout).group(1)
+ progress = re.search(r"\s([0-9]+)%", stdout).group(1)
+ transfer_complete = progress == "100"
+
+ transfer_progress_new = {
+ "file_operation": file_operation,
+ "file_path": "unknown file name" if file_path is None else file_path.group(1),
+ "progress": progress,
+ "transfer_complete": transfer_complete,
+ }
+
+ logging.info(transfer_progress_new)
+ self.update_progress_new.emit(transfer_progress_new, self.profile_name)
+
+ if transfer_complete:
+ self.profile_status["status_message"] = "OneDrive sync is complete"
+ else:
+ self.profile_status["status_message"] = "OneDrive sync in progress..."
+ self.update_profile_status.emit(self.profile_status, self.profile_name)
+
+ else:
+ pass
+
+ if self.onedrive_process.stderr:
+ # Capture stderr from OneDrive process.
+
+ stderr = self.onedrive_process.stderr.readline()
+ if stderr != "":
+ if "command not found" in stderr:
+ logging.info(
+ """Onedrive does not seem to be installed. Please install it as per instruction at
+ https://github.com/abraunegg/onedrive/blob/master/docs/INSTALL.md """
+ )
+
+ elif "--resync is required" in stderr:
+ logging.info(f"[{self.profile_name}] {str(stderr)} Starting resync.")
+ self.trigger_resync.emit()
+
+ self.run(resync=True)
+ else:
+ logging.info("@ERROR " + stderr)
+
+
+class MainWindow(QMainWindow, Ui_MainWindow):
+ def __init__(self):
+ self.workers = {}
+
+ super(MainWindow, self).__init__()
+ self.setupUi(self)
+ self.setWindowIcon(QIcon(dir_path + "/resources/images/icons8-clouds-48.png"))
+
+ if len(global_config) == 0:
+ # self.pushButton_new_profile.clicked.connect(self.show_settings_window)
+ # self.pushButton_new_profile.show()
+ # self.show_settings_window()
+ self.show_setup_wizard()
+
+ # else:
+ self.pushButton_new_profile.hide()
+
+ #
+ # Menu
+ self.menubar.hide()
+ # Update OneDrive Status
+ self.actionRefresh_Service_Status.triggered.connect(lambda: self.onedrive_process_status())
+
+ # Start OneDrive service
+ self.actionStart_Service.triggered.connect(lambda: os.system("systemctl --user start onedrive"))
+
+ # Stop OneDrive service
+ self.actionStop_Service.triggered.connect(lambda: os.system("systemctl --user stop onedrive"))
+
+ # Restart OneDrive service
+ self.actionRestart_Service.triggered.connect(lambda: os.system("systemctl --user restart onedrive"))
+
+ # Start OneDrive monitoring
+ self.actionStart_Monitor.triggered.connect(lambda: self.start_onedrive_monitor(""))
+
+ # Stop OneDrive monitoring
+ self.actionStop_Monitor.triggered.connect(lambda: os.system("pkill onedrive"))
+
+ # Refresh Sync Status
+ # self.actionObtain_Sync_Status.triggered.connect(lambda: self.label_4.setText("Retreiving status..."))
+ # self.actionObtain_Sync_Status.triggered.connect(lambda: self.onedrive_sync_status())
+
+ # Start second account
+ self.actionstart.triggered.connect(lambda: self.start_onedrive_monitor(""))
+
+ self.comboBox.activated.connect(self.switch_account_status_page)
+ self.stackedLayout = QStackedLayout()
+
+ self.profile_status_pages = {}
+ for profile in global_config:
+ self.comboBox.addItem(profile)
+ self.profile_status_pages[profile] = ProfileStatusPage(profile)
+ self.stackedLayout.addWidget(self.profile_status_pages[profile])
+
+ self.verticalLayout_2.addLayout(self.stackedLayout)
+
+ # System Tray
+ self.tray = QSystemTrayIcon()
+ if self.tray.isSystemTrayAvailable():
+
+ icon = QIcon(dir_path + "/resources/images/icons8-clouds-48.png")
+ menu = QMenu()
+
+ actionshow = menu.addAction("Show/Hide")
+ actionshow.triggered.connect(lambda: self.hide() if self.isVisible() else self.show())
+ setting_action = menu.addAction("Settings")
+ setting_action.triggered.connect(self.show_settings_window)
+ quit_action = menu.addAction("Quit")
+ quit_action.triggered.connect(sys.exit)
+
+ self.tray.activated.connect(lambda: self.hide() if self.isVisible() else self.show())
+ self.tray.setIcon(icon)
+ self.tray.setContextMenu(menu)
+ self.tray.show()
+ self.tray.setToolTip("OneDriveGUI")
+
+ else:
+ self.tray = None
+
+ self.refresh_process_status = QTimer()
+ self.refresh_process_status.setSingleShot(False)
+ self.refresh_process_status.timeout.connect(lambda: self.onedrive_process_status())
+ self.refresh_process_status.start(500)
+
+ self.check_client_version = QTimer()
+ self.check_client_version.setSingleShot(True)
+ self.check_client_version.timeout.connect(self.client_version_check)
+ self.check_client_version.start(500)
+
+ self.auto_sync = QTimer()
+ self.auto_sync.setSingleShot(True)
+ self.auto_sync.timeout.connect(self.autostart_monitor)
+ self.auto_sync.start(1000)
+
+ def closeEvent(self, event):
+ event.ignore()
+ self.hide()
+
+ def show_setup_wizard(self):
+ self.setup_wizard = SetupWizard()
+ self.setup_wizard.show()
+
+ def show_settings_window(self):
+ # self.settings_window = SettingsWindow()
+ settings_window.show()
+
+ def switch_account_status_page(self):
+ self.stackedLayout.setCurrentIndex(self.comboBox.currentIndex())
+
+ def client_version_check(self):
+ pixmap_warning = QPixmap(dir_path + "/resources/images/warning.png").scaled(20, 20, Qt.KeepAspectRatio)
+ s = requests.Session()
+ version_label_text = ""
+ version_tooltip_text = ""
+
+ try:
+ latest_url = "https://api.github.com/repos/abraunegg/onedrive/releases/latest"
+ latest_client_version = s.get(latest_url).json()["tag_name"]
+ client_version_check = subprocess.check_output(["onedrive", "--version"], stderr=subprocess.STDOUT)
+ installed_client_version = re.search(r".\s(v[0-9.]+)", str(client_version_check)).group(1)
+ installed_client_version_num = int(installed_client_version.replace("v", "").replace(".", ""))
+ min_supported_version_num = 2415
+ min_requirements_met = True
+
+ result = {
+ "latest_client_version": latest_client_version,
+ "installed_client_version": installed_client_version,
+ }
+ logging.debug(f"[GUI] Client version check: {result}")
+
+ if latest_client_version not in installed_client_version:
+ version_label_text = "OneDrive client is out of date!"
+ version_tooltip_text = f"OneDrive client is out of date! \n Installed: {installed_client_version} \n Latest: {latest_client_version}"
+
+ if installed_client_version_num < min_supported_version_num:
+ version_label_text = 'Unsupported OneDrive client! Please upgrade it.'
+ version_tooltip_text = f"OneDrive Client version not supported! Please upgrade it. \n Installed: {installed_client_version} \n Latest: {latest_client_version}"
+ min_requirements_met = False
+
+ except FileNotFoundError as e:
+ logging.error(f"OneDrive Client not found: {e}")
+ version_label_text = 'OneDrive Client not found! Please install it.'
+ version_tooltip_text = f"OneDrive Client not found! Please install it."
+ min_requirements_met = False
+
+ except Exception as e:
+ logging.error(f"Client version check failed: {e}")
+
+ finally:
+ for profile_name in global_config:
+ self.profile_status_pages[profile_name].label_onedrive_status.setOpenExternalLinks(True)
+ self.profile_status_pages[profile_name].label_onedrive_status.setText(version_label_text)
+
+ if not min_requirements_met:
+ self.profile_status_pages[profile_name].toolButton_start.setEnabled(False)
+ self.profile_status_pages[profile_name].pushButton_settings.setEnabled(False)
+ self.profile_status_pages[profile_name].label_version_check.setToolTip(version_tooltip_text)
+ self.profile_status_pages[profile_name].label_version_check.setPixmap(pixmap_warning)
+
+ def onedrive_process_status(self):
+ # Check OneDrive status
+ # logging.info(self.workers)
+ pixmap_running = QPixmap(dir_path + "/resources/images/icons8-green-circle-48.png").scaled(
+ 20, 20, Qt.KeepAspectRatio
+ )
+ pixmap_stopped = QPixmap(dir_path + "/resources/images/icons8-red-circle-48.png").scaled(
+ 20, 20, Qt.KeepAspectRatio
+ )
+
+ for profile_name in global_config:
+ # logging.info(profile_name)
+ if profile_name not in self.workers:
+ self.profile_status_pages[profile_name].label_status.setText("stopped")
+ self.profile_status_pages[profile_name].label_status.setPixmap(pixmap_stopped)
+ # logging.info(f"not running {profile_name}")
+
+ else:
+ if self.workers[profile_name].isRunning():
+ self.profile_status_pages[profile_name].label_status.setText("running")
+ self.profile_status_pages[profile_name].label_status.setPixmap(pixmap_running)
+ # logging.info(f"running worker {profile_name}")
+
+ def autostart_monitor(self):
+ # Auto-start sync if compatible version of OneDrive client is installed.
+
+ for profile_name in global_config:
+ logging.debug(
+ f"[{profile_name}] Compatible client version found: {self.profile_status_pages[profile_name].toolButton_start.isEnabled()}"
+ )
+ logging.debug(
+ f"[{profile_name}] Auto-sync enabled for profile: {global_config[profile_name]['auto_sync']}"
+ )
+
+ if (
+ self.profile_status_pages[profile_name].toolButton_start.isEnabled()
+ and global_config[profile_name]["auto_sync"] == "True"
+ ):
+ self.start_onedrive_monitor(profile_name)
+
+ def start_onedrive_monitor(self, profile_name):
+ # for profile in global_config:
+
+ if profile_name not in self.workers:
+ self.workers[profile_name] = WorkerThread(profile_name)
+ self.workers[profile_name].start()
+ else:
+ logging.info(f"Worker for profile {profile_name} is already running. Please stop it first.")
+ logging.info(f"Running workers: {main_window.workers}")
+
+ # self.worker = WorkerThread()
+ # self.worker.start()
+ self.workers[profile_name].update_credentials.connect(self.show_login)
+ # self.worker.update_progress.connect(self.event_update_progress)
+ # self.worker.trigger_resync.connect(self.show_login)
+ self.workers[profile_name].update_progress_new.connect(self.event_update_progress_new)
+ self.workers[profile_name].update_profile_status.connect(self.event_update_profile_status)
+ self.workers[profile_name].started.connect(lambda: logging.info(f"started worker {profile_name}"))
+ self.workers[profile_name].finished.connect(lambda: logging.info(f"finished worker {profile_name}"))
+
+ def event_update_profile_status(self, data, profile):
+ self.profile_status_pages[profile].label_onedrive_status.setText(data["status_message"])
+ self.profile_status_pages[profile].label_free_space.setText(data["free_space"])
+ self.profile_status_pages[profile].label_account_type.setText(data["account_type"])
+
+ def event_update_progress_new(self, data, profile):
+ """
+ data:
+ transfer_progress = {
+ "file_operation": file_operation.group(1),
+ "file_path": file_path.group(1),
+ "progress": progress.group(1),
+ "transfer_complete": transfer_complete
+ }
+ """
+ _sync_dir = os.path.expanduser(global_config[profile]["onedrive"]["sync_dir"].strip('"'))
+ # profile_status_page = self.profile_status_pages[profile]
+
+ logging.info(data)
+ file_path = f"{_sync_dir}" + "/" + data["file_path"]
+ absolute_path = QFileInfo(file_path).absolutePath().replace(" ", "%20")
+ parent_dir = re.search(r".+/([^/]+)/.+$", file_path).group(1)
+ file_size = QFileInfo(file_path).size()
+ file_size_human = humanize_file_size(file_size)
+ file_name = QFileInfo(file_path).fileName()
+ file_path2 = QFileInfo(file_path).filePath()
+ progress = data["progress"]
+ progress_data = file_size / 100 * int(progress)
+ progress_data_human = humanize_file_size(progress_data)
+ file_operation = data["file_operation"]
+ transfer_complete = data["transfer_complete"]
+
+ logging.info("absolute path " + absolute_path)
+
+ logging.info("parent dir " + parent_dir)
+ logging.info("progress: " + progress)
+ logging.info("progress data: " + humanize_file_size(progress_data))
+ logging.info("file path: " + file_path)
+ logging.info("file size: " + humanize_file_size(file_size))
+ logging.info("file name: " + file_name)
+ logging.info("file path2: " + file_path2)
+
+ # Delete last item list if it has the same file name.
+ if self.profile_status_pages[profile].listWidget.item(0) != None:
+ last_item = self.profile_status_pages[profile].listWidget.item(0)
+ last_item_widget = self.profile_status_pages[profile].listWidget.itemWidget(last_item)
+ last_file_name = last_item_widget.get_file_name()
+ logging.info(f"The last list item's file name is : {last_file_name}")
+
+ if file_name == last_file_name:
+ logging.info("Deleting last list item")
+ self.profile_status_pages[profile].listWidget.takeItem(0)
+
+ myQCustomQWidget = TaskList()
+ myQCustomQWidget.set_file_name(file_name)
+ myQCustomQWidget.set_progress(int(progress))
+ myQCustomQWidget.set_icon(file_path)
+ myQCustomQWidget.hide_progress_bar(transfer_complete)
+
+ if file_operation == "Deleting":
+ myQCustomQWidget.set_label_1(f"Deleted from {parent_dir}")
+ myQCustomQWidget.set_label_2(f"")
+
+ elif transfer_complete:
+ myQCustomQWidget.set_label_1(f"Available in {parent_dir}")
+ myQCustomQWidget.set_label_2(f"{file_size_human}")
+ else:
+ myQCustomQWidget.set_label_1(file_operation)
+ myQCustomQWidget.set_label_2(f"{progress_data_human} of {file_size_human}")
+
+ # Create QListWidgetItem
+ myQListWidgetItem = QListWidgetItem()
+
+ # Set size hint
+ myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint())
+
+ # Add QListWidgetItem into QListWidget
+ self.profile_status_pages[profile].listWidget.insertItem(0, myQListWidgetItem)
+ self.profile_status_pages[profile].listWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget)
+
+ def show_login(self, profile):
+ # Show login window
+ self.window1 = QWidget()
+ self.window1.setWindowIcon(QIcon(dir_path + "/resources/images/icons8-clouds-48.png"))
+ self.lw = Ui_LoginWindow()
+ self.lw.setupUi(self.window1)
+ self.window1.show()
+ self.window1.setWindowTitle(f"OneDrive login for profile {profile}")
+
+ self.config_file = global_config[profile]["config_file"].strip('"')
+ self.config_dir = re.search(r"(.+)/.+$", self.config_file).group(1)
+
+ # use static URL for now. TODO: use auth files in the future
+ url = (
+ "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=d50ca740-c83f-4d1b-b616"
+ "-12c519384f0c&scope=Files.ReadWrite%20Files.ReadWrite.all%20Sites.Read.All%20Sites.ReadWrite.All"
+ "%20offline_access&response_type=code&prompt=login&redirect_uri=https://login.microsoftonline.com"
+ "/common/oauth2/nativeclient"
+ )
+ self.lw.loginFrame.setUrl(QUrl(url))
+
+ # Wait for user to login and obtain response URL
+ self.lw.loginFrame.urlChanged.connect(
+ lambda: self.get_response_url(self.lw.loginFrame.url().toString(), self.config_dir, profile)
+ )
+
+ def get_response_url(self, response_url, config_dir, profile):
+ # Get response URL from OneDrive OAuth2
+ if "nativeclient?code=" in response_url:
+ logging.info(f'onedrive --confdir="{config_dir}" --auth-response "{response_url}"')
+ os.system(f'onedrive --confdir="{config_dir}" --auth-response "{response_url}"')
+ logging.info("Login performed")
+ self.window1.hide()
+ main_window.workers[profile].stop_worker()
+ main_window.profile_status_pages[profile].label_onedrive_status.setText(
+ "You have been logged in. Start sync manually."
+ )
+ else:
+ pass
+
+
+def humanize_file_size(num, suffix="B"):
+ for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
+ if abs(num) < 1024.0:
+ return f"{num:3.1f}{unit}{suffix}"
+ num /= 1024.0
+ return f"{num:.1f}Yi{suffix}"
+
+
+def read_config(config_file):
+ with open(config_file, "r") as f:
+ config_string = "[onedrive]\n" + f.read()
+
+ config = ConfigParser()
+ config.read_string(config_string)
+
+ return config
+
+
+def create_global_config():
+ """
+ Creates dict which is used as running global config.
+ EXAMPLE:
+
+ {
+ "bob@live.com": {
+ "config_file": "/home/bob/.config/onedrive/accounts/bob@live.com/config",
+ "enable_debug": "True",
+ "mode": "monitor",
+ "auto_sync": False,
+ "onedrive": {
+ "sync_dir": '"~/OneDrive"',
+ "skip_file": '"~*|.~*|*.tmp|*.txt|*.exe|.testfile"',
+ "monitor_interval": '"15"',
+ ...},
+ "john@live.com": {
+ "config_file": "/home/bob/.config/onedrive/accounts/john@live.com/config",
+ "enable_debug": "True",
+ "mode": "monitor",
+ "auto_sync": False,
+ "onedrive": {
+ "sync_dir": '"~/OneDrive2"',
+ "skip_file": '"~*|.~*|*.tmp|*.txt|*.exe"',
+ "monitor_interval": '"15"', ...}
+ """
+
+ # Load all default values. Needed for cases when imported config does not contain all properties.
+ _default_od_config = read_config(dir_path + "/resources/default_config")
+ _default_profile_config = {"auto_sync": False}
+ default_od_config = _default_od_config._sections
+
+ # Load existing user profiles.
+ _profiles = ConfigParser()
+ _profiles.read(PROFILES_FILE)
+ profiles = _profiles._sections
+
+ for profile in profiles:
+ profile_config_file = profiles[profile]["config_file"]
+ _od_config = read_config(profile_config_file)
+ od_config = _od_config._sections
+
+ if "auto_sync" not in profiles[profile]: # add 'auto_sync' value if from older versions
+ profiles[profile].update(_default_profile_config)
+ profiles[profile].update(default_od_config)
+ profiles[profile].update(od_config)
+
+ logging.debug(profiles)
+ return profiles
+
+
+def save_global_config():
+ # Save all OneDrive config files after configuration change.
+ #
+ # Save GUI profile file changes
+ #
+ _profile_config = copy.deepcopy(global_config)
+ logging.debug(f"[save_global_config]:[1]{_profile_config}")
+
+ for profile in _profile_config:
+ _profile_config[profile].pop("onedrive", None)
+
+ logging.debug(f"[save_global_config]:[2]{_profile_config}")
+ logging.debug(f"[save_global_config]:[3]{global_config}")
+
+ profile_config = ConfigParser()
+ profile_config.read_dict(_profile_config)
+
+ # Create profile config file if it does not exist.
+ profiles_dir = re.search(r"(.+)/profiles$", PROFILES_FILE).group(1)
+ if not os.path.exists(profiles_dir):
+ os.makedirs(profiles_dir)
+
+ # Save the new profile.
+ with open(PROFILES_FILE, "w") as profilefile:
+ profile_config.write(profilefile)
+
+ for profile in global_config:
+
+ #
+ # Save OneDrive config changes
+ #
+ od_config_file = os.path.expanduser(global_config[profile]["config_file"].strip('"'))
+
+ _od_config = {}
+ _od_config["onedrive"] = global_config[profile]["onedrive"]
+
+ od_config = ConfigParser()
+ od_config.read_dict(_od_config)
+
+ # Backup last config
+ os.system(f"cp {od_config_file} {od_config_file}_backup")
+
+ # Save OD config changes.
+ directory = re.search(r"(.+)/.+$", od_config_file).group(1)
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ with open(od_config_file, "w") as f:
+ od_config.write(f)
+
+ # Remove first line (section) from config file so that OneDrive can read it.
+ with open(od_config_file, "r") as input:
+ data = input.read().splitlines(True)
+ with open(od_config_file, "w") as output:
+ output.writelines(data[1:])
+
+ logging.info(f"{profile} config saved")
+
+ logging.info("All configs saved")
+ logging.debug(global_config)
+
+
+if __name__ == "__main__":
+ global_config = create_global_config()
+
+ app = QApplication(sys.argv)
+ app.setApplicationName("OneDriveGUI")
+ app.setWindowIcon(QIcon(dir_path + "/resources/images/icons8-clouds-48.png"))
+
+ main_window = MainWindow()
+ settings_window = SettingsWindow()
+ settings_window.hide()
+
+ main_window.show()
+ app.exec()
diff --git a/src/OneDriveGUI.py b/src/OneDriveGUI.py
index 9795510..8dc8437 100644
--- a/src/OneDriveGUI.py
+++ b/src/OneDriveGUI.py
@@ -1396,6 +1396,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.check_client_version.timeout.connect(self.client_version_check)
self.check_client_version.start(500)
+ self.auto_sync = QTimer()
+ self.auto_sync.setSingleShot(True)
+ for profile in global_config:
+ self.auto_sync.timeout.connect(lambda: self.start_onedrive_monitor(profile))
+ self.auto_sync.start(1000)
+
def closeEvent(self, event):
event.ignore()
self.hide()
diff --git a/src/ui/profile_settings_page-bob-manjaro.ui b/src/ui/profile_settings_page-bob-manjaro.ui
new file mode 100644
index 0000000..a681f35
--- /dev/null
+++ b/src/ui/profile_settings_page-bob-manjaro.ui
@@ -0,0 +1,824 @@
+
+
+ profile_settings
+
+
+
+ 0
+ 0
+ 664
+ 723
+
+
+
+ OneDriveGUI - Profile Settings
+
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 12
+ 75
+ true
+
+
+
+ Profile name
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
-
+
+
+
+ 650
+ 600
+
+
+
+ 5
+
+
+
+ Monitored Files
+
+
+
-
+
+
+ Monitored directory
+
+
+
-
+
+
-
+
+
+ Browse
+
+
+
+ -
+
+
+ Sync Folder:
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Sync root files
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Excluded files
+
+
+ -
+
+
+ Excluded files
+
+
+
-
+
+
-
+
+
+ -
+
+
+ Add
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Remove
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+ Excluded directories
+
+
+
-
+
+
+ Strict match
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Remove
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Add
+
+
+
+
+
+
+
+
+ -
+
+
+ Others
+
+
+
-
+
+
+ Exclude symlinks
+
+
+
+ -
+
+
+ Check for .nosync
+
+
+
+ -
+
+
+ Exclude dotfiles (hidden files)
+
+
+
+
+
+
+
+
+
+
+ Sync Options
+
+
+ -
+
+
+ Account Rate Limit
+
+
+
-
+
+
+ 125000
+
+
+ 125000000
+
+
+ 12500
+
+
+
+ -
+
+
+ Rate Limit [B/s]
+
+
+
+ -
+
+
+ 125000
+
+
+ 125000000
+
+
+ 12500
+
+
+ 12500
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ (Mibit/s)
+
+
+
+
+
+
+ -
+
+
+ Advanced Properties
+
+
+
-
+
+
+ Monitor interval
+
+
+
+ -
+
+
+ 1000000
+
+
+
+ -
+
+
+ Monitor full-scan frequency
+
+
+
+ -
+
+
+ 1000000
+
+
+
+ -
+
+
+ Clasify as big delete
+
+
+
+ -
+
+
+ 1000000
+
+
+
+ -
+
+
+ User-agent
+
+
+
+ -
+
+
+
+ 200
+ 16777215
+
+
+
+ 32767
+
+
+
+ -
+
+
+ Azure AD endpoint
+
+
+
+ -
+
+
+
+ 200
+ 16777215
+
+
+
+
+ -
+
+
+ Azure tenant ID
+
+
+
+ -
+
+
+
+ 200
+ 16777215
+
+
+
+
+ -
+
+
+ Sync dir permissions
+
+
+
+ -
+
+
+ 777
+
+
+
+ -
+
+
+ Sync file permissions
+
+
+
+ -
+
+
+ 777
+
+
+
+ -
+
+
+ Operation timeout
+
+
+
+ -
+
+
+ 1000000
+
+
+
+
+
+
+ -
+
+
+ Advanced options
+
+
+
-
+
+
+ Force HTTP2
+
+
+
+ -
+
+
+ Check for .nomount
+
+
+
+ -
+
+
+ Full re-sync
+
+
+
+ -
+
+
+ Download only
+
+
+
+ -
+
+
+ Local first
+
+
+
+ -
+
+
+ Dry run
+
+
+
+ -
+
+
+ Disable upload validation
+
+
+
+ -
+
+
+ Bypass data preservation
+
+
+
+ -
+
+
+ No remote delete
+
+
+
+ -
+
+
+ Remove source files
+
+
+
+ -
+
+
+ Upload only
+
+
+
+ -
+
+
+ Sync business shared folders
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Logging
+
+
+ -
+
+
+ Logging
+
+
+
-
+
+
-
+
+
+ Enable Logging
+
+
+
+ -
+
+
+ Log location:
+
+
+
+ -
+
+
+ Debug HTTPS
+
+
+
+ -
+
+
+ -
+
+
+ Browse
+
+
+
+ -
+
+
+ Monitor log frequency
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+ Notifications
+
+
+
-
+
+
+ Minimum notify changes
+
+
+
+ -
+
+
+ -
+
+
+ Disable notifications
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Webhooks
+
+
+ -
+
+
+ Webhook settings
+
+
+
-
+
+
+ Public URL
+
+
+
+ -
+
+
+ -
+
+
+ Listening host
+
+
+
+ -
+
+
+ -
+
+
+ Listening port
+
+
+
+ -
+
+
+ 1
+
+
+ 65353
+
+
+
+ -
+
+
+ Expiration interval
+
+
+
+ -
+
+
+ 1000000
+
+
+
+ -
+
+
+ Renewal interval
+
+
+
+ -
+
+
+ 1000000
+
+
+
+ -
+
+
+ Enable webhook
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Account
+
+
+ -
+
+
+ Auto-sync on startup
+
+
+
+ -
+
+
+ Login
+
+
+
+ -
+
+
+ Logout
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Discard changes
+
+
+
+ -
+
+
+ Save
+
+
+
+
+
+
+
+
diff --git a/src/ui/ui_profile_settings_page-bob-manjaro.py b/src/ui/ui_profile_settings_page-bob-manjaro.py
new file mode 100644
index 0000000..53c8117
--- /dev/null
+++ b/src/ui/ui_profile_settings_page-bob-manjaro.py
@@ -0,0 +1,696 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'profile_settings_page.ui'
+##
+## Created by: Qt User Interface Compiler version 6.2.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QCheckBox, QFormLayout, QGridLayout,
+ QGroupBox, QHBoxLayout, QLabel, QLineEdit,
+ QListWidget, QListWidgetItem, QPushButton, QSizePolicy,
+ QSlider, QSpacerItem, QSpinBox, QTabWidget,
+ QVBoxLayout, QWidget)
+
+class Ui_profile_settings(object):
+ def setupUi(self, profile_settings):
+ if not profile_settings.objectName():
+ profile_settings.setObjectName(u"profile_settings")
+ profile_settings.resize(664, 723)
+ self.verticalLayout_2 = QVBoxLayout(profile_settings)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(-1, 0, -1, 0)
+ self.label_profile_name = QLabel(profile_settings)
+ self.label_profile_name.setObjectName(u"label_profile_name")
+ font = QFont()
+ font.setPointSize(12)
+ font.setBold(True)
+ self.label_profile_name.setFont(font)
+ self.label_profile_name.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout_2.addWidget(self.label_profile_name)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.tabWidget = QTabWidget(profile_settings)
+ self.tabWidget.setObjectName(u"tabWidget")
+ self.tabWidget.setMinimumSize(QSize(650, 600))
+ self.exemptions_tab_2 = QWidget()
+ self.exemptions_tab_2.setObjectName(u"exemptions_tab_2")
+ self.verticalLayout_5 = QVBoxLayout(self.exemptions_tab_2)
+ self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+ self.groupBox_8 = QGroupBox(self.exemptions_tab_2)
+ self.groupBox_8.setObjectName(u"groupBox_8")
+ self.gridLayout_9 = QGridLayout(self.groupBox_8)
+ self.gridLayout_9.setObjectName(u"gridLayout_9")
+ self.gridLayout_5 = QGridLayout()
+ self.gridLayout_5.setObjectName(u"gridLayout_5")
+ self.pushButton_sync_dir_browse = QPushButton(self.groupBox_8)
+ self.pushButton_sync_dir_browse.setObjectName(u"pushButton_sync_dir_browse")
+
+ self.gridLayout_5.addWidget(self.pushButton_sync_dir_browse, 0, 2, 1, 1)
+
+ self.label_sync_dir = QLabel(self.groupBox_8)
+ self.label_sync_dir.setObjectName(u"label_sync_dir")
+
+ self.gridLayout_5.addWidget(self.label_sync_dir, 0, 0, 1, 1)
+
+ self.lineEdit_sync_dir = QLineEdit(self.groupBox_8)
+ self.lineEdit_sync_dir.setObjectName(u"lineEdit_sync_dir")
+
+ self.gridLayout_5.addWidget(self.lineEdit_sync_dir, 0, 1, 1, 1)
+
+
+ self.gridLayout_9.addLayout(self.gridLayout_5, 1, 0, 1, 1)
+
+ self.checkBox_sync_root_files = QCheckBox(self.groupBox_8)
+ self.checkBox_sync_root_files.setObjectName(u"checkBox_sync_root_files")
+
+ self.gridLayout_9.addWidget(self.checkBox_sync_root_files, 2, 0, 1, 1)
+
+
+ self.verticalLayout_5.addWidget(self.groupBox_8)
+
+ self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_5.addItem(self.verticalSpacer_3)
+
+ self.tabWidget.addTab(self.exemptions_tab_2, "")
+ self.tab = QWidget()
+ self.tab.setObjectName(u"tab")
+ self.verticalLayout_7 = QVBoxLayout(self.tab)
+ self.verticalLayout_7.setObjectName(u"verticalLayout_7")
+ self.groupBox = QGroupBox(self.tab)
+ self.groupBox.setObjectName(u"groupBox")
+ self.verticalLayout_3 = QVBoxLayout(self.groupBox)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.gridLayout_6 = QGridLayout()
+ self.gridLayout_6.setObjectName(u"gridLayout_6")
+ self.lineEdit_skip_file = QLineEdit(self.groupBox)
+ self.lineEdit_skip_file.setObjectName(u"lineEdit_skip_file")
+
+ self.gridLayout_6.addWidget(self.lineEdit_skip_file, 1, 0, 1, 1)
+
+ self.pushButton_add_skip_file = QPushButton(self.groupBox)
+ self.pushButton_add_skip_file.setObjectName(u"pushButton_add_skip_file")
+
+ self.gridLayout_6.addWidget(self.pushButton_add_skip_file, 1, 1, 1, 1)
+
+ self.pushButton_rm_skip_file = QPushButton(self.groupBox)
+ self.pushButton_rm_skip_file.setObjectName(u"pushButton_rm_skip_file")
+ sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.pushButton_rm_skip_file.sizePolicy().hasHeightForWidth())
+ self.pushButton_rm_skip_file.setSizePolicy(sizePolicy)
+
+ self.gridLayout_6.addWidget(self.pushButton_rm_skip_file, 0, 1, 1, 1)
+
+ self.listWidget_skip_file = QListWidget(self.groupBox)
+ self.listWidget_skip_file.setObjectName(u"listWidget_skip_file")
+
+ self.gridLayout_6.addWidget(self.listWidget_skip_file, 0, 0, 1, 1)
+
+
+ self.verticalLayout_3.addLayout(self.gridLayout_6)
+
+
+ self.verticalLayout_7.addWidget(self.groupBox)
+
+ self.groupBox_2 = QGroupBox(self.tab)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ self.verticalLayout_4 = QVBoxLayout(self.groupBox_2)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.checkBox_skip_dir_strict_match = QCheckBox(self.groupBox_2)
+ self.checkBox_skip_dir_strict_match.setObjectName(u"checkBox_skip_dir_strict_match")
+
+ self.verticalLayout_4.addWidget(self.checkBox_skip_dir_strict_match)
+
+ self.gridLayout_7 = QGridLayout()
+ self.gridLayout_7.setObjectName(u"gridLayout_7")
+ self.pushButton_rm_skip_dir = QPushButton(self.groupBox_2)
+ self.pushButton_rm_skip_dir.setObjectName(u"pushButton_rm_skip_dir")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Minimum)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.pushButton_rm_skip_dir.sizePolicy().hasHeightForWidth())
+ self.pushButton_rm_skip_dir.setSizePolicy(sizePolicy1)
+
+ self.gridLayout_7.addWidget(self.pushButton_rm_skip_dir, 0, 1, 1, 1)
+
+ self.listWidget_skip_dir = QListWidget(self.groupBox_2)
+ self.listWidget_skip_dir.setObjectName(u"listWidget_skip_dir")
+ sizePolicy2 = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding)
+ sizePolicy2.setHorizontalStretch(0)
+ sizePolicy2.setVerticalStretch(0)
+ sizePolicy2.setHeightForWidth(self.listWidget_skip_dir.sizePolicy().hasHeightForWidth())
+ self.listWidget_skip_dir.setSizePolicy(sizePolicy2)
+
+ self.gridLayout_7.addWidget(self.listWidget_skip_dir, 0, 0, 1, 1)
+
+ self.lineEdit_skip_dir = QLineEdit(self.groupBox_2)
+ self.lineEdit_skip_dir.setObjectName(u"lineEdit_skip_dir")
+
+ self.gridLayout_7.addWidget(self.lineEdit_skip_dir, 1, 0, 1, 1)
+
+ self.pushButton_add_skip_dir = QPushButton(self.groupBox_2)
+ self.pushButton_add_skip_dir.setObjectName(u"pushButton_add_skip_dir")
+ sizePolicy3 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+ sizePolicy3.setHorizontalStretch(0)
+ sizePolicy3.setVerticalStretch(0)
+ sizePolicy3.setHeightForWidth(self.pushButton_add_skip_dir.sizePolicy().hasHeightForWidth())
+ self.pushButton_add_skip_dir.setSizePolicy(sizePolicy3)
+
+ self.gridLayout_7.addWidget(self.pushButton_add_skip_dir, 1, 1, 1, 1)
+
+
+ self.verticalLayout_4.addLayout(self.gridLayout_7)
+
+
+ self.verticalLayout_7.addWidget(self.groupBox_2)
+
+ self.groupBox_3 = QGroupBox(self.tab)
+ self.groupBox_3.setObjectName(u"groupBox_3")
+ self.gridLayout_2 = QGridLayout(self.groupBox_3)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.checkBox_skip_symlinks = QCheckBox(self.groupBox_3)
+ self.checkBox_skip_symlinks.setObjectName(u"checkBox_skip_symlinks")
+
+ self.gridLayout_2.addWidget(self.checkBox_skip_symlinks, 0, 1, 1, 1)
+
+ self.checkBox_check_nosync = QCheckBox(self.groupBox_3)
+ self.checkBox_check_nosync.setObjectName(u"checkBox_check_nosync")
+
+ self.gridLayout_2.addWidget(self.checkBox_check_nosync, 0, 0, 1, 1)
+
+ self.checkBox_skip_dotfiles = QCheckBox(self.groupBox_3)
+ self.checkBox_skip_dotfiles.setObjectName(u"checkBox_skip_dotfiles")
+
+ self.gridLayout_2.addWidget(self.checkBox_skip_dotfiles, 1, 0, 1, 1)
+
+
+ self.verticalLayout_7.addWidget(self.groupBox_3)
+
+ self.tabWidget.addTab(self.tab, "")
+ self.tab_2 = QWidget()
+ self.tab_2.setObjectName(u"tab_2")
+ self.verticalLayout_8 = QVBoxLayout(self.tab_2)
+ self.verticalLayout_8.setObjectName(u"verticalLayout_8")
+ self.groupBox_10 = QGroupBox(self.tab_2)
+ self.groupBox_10.setObjectName(u"groupBox_10")
+ self.gridLayout_11 = QGridLayout(self.groupBox_10)
+ self.gridLayout_11.setObjectName(u"gridLayout_11")
+ self.spinBox_rate_limit = QSpinBox(self.groupBox_10)
+ self.spinBox_rate_limit.setObjectName(u"spinBox_rate_limit")
+ self.spinBox_rate_limit.setMinimum(125000)
+ self.spinBox_rate_limit.setMaximum(125000000)
+ self.spinBox_rate_limit.setSingleStep(12500)
+
+ self.gridLayout_11.addWidget(self.spinBox_rate_limit, 0, 1, 1, 1)
+
+ self.label_rate_limit = QLabel(self.groupBox_10)
+ self.label_rate_limit.setObjectName(u"label_rate_limit")
+
+ self.gridLayout_11.addWidget(self.label_rate_limit, 0, 0, 1, 1)
+
+ self.horizontalSlider_rate_limit = QSlider(self.groupBox_10)
+ self.horizontalSlider_rate_limit.setObjectName(u"horizontalSlider_rate_limit")
+ self.horizontalSlider_rate_limit.setMinimum(125000)
+ self.horizontalSlider_rate_limit.setMaximum(125000000)
+ self.horizontalSlider_rate_limit.setSingleStep(12500)
+ self.horizontalSlider_rate_limit.setPageStep(12500)
+ self.horizontalSlider_rate_limit.setOrientation(Qt.Horizontal)
+
+ self.gridLayout_11.addWidget(self.horizontalSlider_rate_limit, 1, 0, 1, 4)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.gridLayout_11.addItem(self.horizontalSpacer, 0, 3, 1, 1)
+
+ self.label_rate_limit_mbps = QLabel(self.groupBox_10)
+ self.label_rate_limit_mbps.setObjectName(u"label_rate_limit_mbps")
+
+ self.gridLayout_11.addWidget(self.label_rate_limit_mbps, 0, 2, 1, 1)
+
+
+ self.verticalLayout_8.addWidget(self.groupBox_10)
+
+ self.groupBox_4 = QGroupBox(self.tab_2)
+ self.groupBox_4.setObjectName(u"groupBox_4")
+ self.formLayout = QFormLayout(self.groupBox_4)
+ self.formLayout.setObjectName(u"formLayout")
+ self.label_monitor_interval = QLabel(self.groupBox_4)
+ self.label_monitor_interval.setObjectName(u"label_monitor_interval")
+
+ self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label_monitor_interval)
+
+ self.spinBox_monitor_interval = QSpinBox(self.groupBox_4)
+ self.spinBox_monitor_interval.setObjectName(u"spinBox_monitor_interval")
+ self.spinBox_monitor_interval.setMaximum(1000000)
+
+ self.formLayout.setWidget(0, QFormLayout.FieldRole, self.spinBox_monitor_interval)
+
+ self.label_monitor_fullscan_frequency = QLabel(self.groupBox_4)
+ self.label_monitor_fullscan_frequency.setObjectName(u"label_monitor_fullscan_frequency")
+
+ self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_monitor_fullscan_frequency)
+
+ self.spinBox_monitor_fullscan_frequency = QSpinBox(self.groupBox_4)
+ self.spinBox_monitor_fullscan_frequency.setObjectName(u"spinBox_monitor_fullscan_frequency")
+ self.spinBox_monitor_fullscan_frequency.setMaximum(1000000)
+
+ self.formLayout.setWidget(1, QFormLayout.FieldRole, self.spinBox_monitor_fullscan_frequency)
+
+ self.label_classify_as_big_delete = QLabel(self.groupBox_4)
+ self.label_classify_as_big_delete.setObjectName(u"label_classify_as_big_delete")
+
+ self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_classify_as_big_delete)
+
+ self.spinBox_classify_as_big_delete = QSpinBox(self.groupBox_4)
+ self.spinBox_classify_as_big_delete.setObjectName(u"spinBox_classify_as_big_delete")
+ self.spinBox_classify_as_big_delete.setMaximum(1000000)
+
+ self.formLayout.setWidget(2, QFormLayout.FieldRole, self.spinBox_classify_as_big_delete)
+
+ self.label_user_agent = QLabel(self.groupBox_4)
+ self.label_user_agent.setObjectName(u"label_user_agent")
+
+ self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_user_agent)
+
+ self.lineEdit_user_agent = QLineEdit(self.groupBox_4)
+ self.lineEdit_user_agent.setObjectName(u"lineEdit_user_agent")
+ self.lineEdit_user_agent.setMaximumSize(QSize(200, 16777215))
+ self.lineEdit_user_agent.setMaxLength(32767)
+
+ self.formLayout.setWidget(3, QFormLayout.FieldRole, self.lineEdit_user_agent)
+
+ self.label_azure_ad_endpoint = QLabel(self.groupBox_4)
+ self.label_azure_ad_endpoint.setObjectName(u"label_azure_ad_endpoint")
+
+ self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_azure_ad_endpoint)
+
+ self.lineEdit_azure_ad_endpoint = QLineEdit(self.groupBox_4)
+ self.lineEdit_azure_ad_endpoint.setObjectName(u"lineEdit_azure_ad_endpoint")
+ self.lineEdit_azure_ad_endpoint.setMaximumSize(QSize(200, 16777215))
+
+ self.formLayout.setWidget(4, QFormLayout.FieldRole, self.lineEdit_azure_ad_endpoint)
+
+ self.label_azure_tenant_id = QLabel(self.groupBox_4)
+ self.label_azure_tenant_id.setObjectName(u"label_azure_tenant_id")
+
+ self.formLayout.setWidget(5, QFormLayout.LabelRole, self.label_azure_tenant_id)
+
+ self.lineEdit_azure_tenant_id = QLineEdit(self.groupBox_4)
+ self.lineEdit_azure_tenant_id.setObjectName(u"lineEdit_azure_tenant_id")
+ self.lineEdit_azure_tenant_id.setMaximumSize(QSize(200, 16777215))
+
+ self.formLayout.setWidget(5, QFormLayout.FieldRole, self.lineEdit_azure_tenant_id)
+
+ self.label_sync_dir_permissions = QLabel(self.groupBox_4)
+ self.label_sync_dir_permissions.setObjectName(u"label_sync_dir_permissions")
+
+ self.formLayout.setWidget(6, QFormLayout.LabelRole, self.label_sync_dir_permissions)
+
+ self.spinBox_sync_dir_permissions = QSpinBox(self.groupBox_4)
+ self.spinBox_sync_dir_permissions.setObjectName(u"spinBox_sync_dir_permissions")
+ self.spinBox_sync_dir_permissions.setMaximum(777)
+
+ self.formLayout.setWidget(6, QFormLayout.FieldRole, self.spinBox_sync_dir_permissions)
+
+ self.label_sync_file_permissions = QLabel(self.groupBox_4)
+ self.label_sync_file_permissions.setObjectName(u"label_sync_file_permissions")
+
+ self.formLayout.setWidget(7, QFormLayout.LabelRole, self.label_sync_file_permissions)
+
+ self.spinBox_sync_file_permissions = QSpinBox(self.groupBox_4)
+ self.spinBox_sync_file_permissions.setObjectName(u"spinBox_sync_file_permissions")
+ self.spinBox_sync_file_permissions.setMaximum(777)
+
+ self.formLayout.setWidget(7, QFormLayout.FieldRole, self.spinBox_sync_file_permissions)
+
+ self.label_operation_timeout = QLabel(self.groupBox_4)
+ self.label_operation_timeout.setObjectName(u"label_operation_timeout")
+
+ self.formLayout.setWidget(8, QFormLayout.LabelRole, self.label_operation_timeout)
+
+ self.spinBox_operation_timeout = QSpinBox(self.groupBox_4)
+ self.spinBox_operation_timeout.setObjectName(u"spinBox_operation_timeout")
+ self.spinBox_operation_timeout.setMaximum(1000000)
+
+ self.formLayout.setWidget(8, QFormLayout.FieldRole, self.spinBox_operation_timeout)
+
+
+ self.verticalLayout_8.addWidget(self.groupBox_4)
+
+ self.groupBox_5 = QGroupBox(self.tab_2)
+ self.groupBox_5.setObjectName(u"groupBox_5")
+ self.gridLayout_4 = QGridLayout(self.groupBox_5)
+ self.gridLayout_4.setObjectName(u"gridLayout_4")
+ self.checkBox_force_http_2 = QCheckBox(self.groupBox_5)
+ self.checkBox_force_http_2.setObjectName(u"checkBox_force_http_2")
+
+ self.gridLayout_4.addWidget(self.checkBox_force_http_2, 2, 1, 1, 1)
+
+ self.checkBox_check_nomount = QCheckBox(self.groupBox_5)
+ self.checkBox_check_nomount.setObjectName(u"checkBox_check_nomount")
+
+ self.gridLayout_4.addWidget(self.checkBox_check_nomount, 4, 1, 1, 1)
+
+ self.checkBox_resync = QCheckBox(self.groupBox_5)
+ self.checkBox_resync.setObjectName(u"checkBox_resync")
+
+ self.gridLayout_4.addWidget(self.checkBox_resync, 4, 2, 1, 1)
+
+ self.checkBox_download_only = QCheckBox(self.groupBox_5)
+ self.checkBox_download_only.setObjectName(u"checkBox_download_only")
+
+ self.gridLayout_4.addWidget(self.checkBox_download_only, 0, 1, 1, 1)
+
+ self.checkBox_local_first = QCheckBox(self.groupBox_5)
+ self.checkBox_local_first.setObjectName(u"checkBox_local_first")
+
+ self.gridLayout_4.addWidget(self.checkBox_local_first, 5, 1, 1, 1)
+
+ self.checkBox_dry_run = QCheckBox(self.groupBox_5)
+ self.checkBox_dry_run.setObjectName(u"checkBox_dry_run")
+
+ self.gridLayout_4.addWidget(self.checkBox_dry_run, 2, 2, 1, 1)
+
+ self.checkBox_disable_upload_validation = QCheckBox(self.groupBox_5)
+ self.checkBox_disable_upload_validation.setObjectName(u"checkBox_disable_upload_validation")
+
+ self.gridLayout_4.addWidget(self.checkBox_disable_upload_validation, 3, 1, 1, 1)
+
+ self.checkBox_bypass_data_preservation = QCheckBox(self.groupBox_5)
+ self.checkBox_bypass_data_preservation.setObjectName(u"checkBox_bypass_data_preservation")
+
+ self.gridLayout_4.addWidget(self.checkBox_bypass_data_preservation, 5, 2, 1, 1)
+
+ self.checkBox_no_remote_delete = QCheckBox(self.groupBox_5)
+ self.checkBox_no_remote_delete.setObjectName(u"checkBox_no_remote_delete")
+
+ self.gridLayout_4.addWidget(self.checkBox_no_remote_delete, 0, 2, 1, 1)
+
+ self.checkBox_remove_source_files = QCheckBox(self.groupBox_5)
+ self.checkBox_remove_source_files.setObjectName(u"checkBox_remove_source_files")
+
+ self.gridLayout_4.addWidget(self.checkBox_remove_source_files, 3, 2, 1, 1)
+
+ self.checkBox_upload_only = QCheckBox(self.groupBox_5)
+ self.checkBox_upload_only.setObjectName(u"checkBox_upload_only")
+
+ self.gridLayout_4.addWidget(self.checkBox_upload_only, 1, 1, 1, 1)
+
+ self.checkBox_sync_business_shared_folders = QCheckBox(self.groupBox_5)
+ self.checkBox_sync_business_shared_folders.setObjectName(u"checkBox_sync_business_shared_folders")
+
+ self.gridLayout_4.addWidget(self.checkBox_sync_business_shared_folders, 1, 2, 1, 1)
+
+
+ self.verticalLayout_8.addWidget(self.groupBox_5)
+
+ self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_8.addItem(self.verticalSpacer_4)
+
+ self.tabWidget.addTab(self.tab_2, "")
+ self.tab_logging = QWidget()
+ self.tab_logging.setObjectName(u"tab_logging")
+ self.verticalLayout_6 = QVBoxLayout(self.tab_logging)
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.groupBox_7 = QGroupBox(self.tab_logging)
+ self.groupBox_7.setObjectName(u"groupBox_7")
+ self.verticalLayout_9 = QVBoxLayout(self.groupBox_7)
+ self.verticalLayout_9.setObjectName(u"verticalLayout_9")
+ self.gridLayout_8 = QGridLayout()
+ self.gridLayout_8.setObjectName(u"gridLayout_8")
+ self.checkBox_enable_logging = QCheckBox(self.groupBox_7)
+ self.checkBox_enable_logging.setObjectName(u"checkBox_enable_logging")
+
+ self.gridLayout_8.addWidget(self.checkBox_enable_logging, 0, 0, 1, 1)
+
+ self.label_log_dir = QLabel(self.groupBox_7)
+ self.label_log_dir.setObjectName(u"label_log_dir")
+
+ self.gridLayout_8.addWidget(self.label_log_dir, 1, 0, 1, 1)
+
+ self.checkBox_debug_https = QCheckBox(self.groupBox_7)
+ self.checkBox_debug_https.setObjectName(u"checkBox_debug_https")
+
+ self.gridLayout_8.addWidget(self.checkBox_debug_https, 4, 0, 1, 1)
+
+ self.lineEdit_log_dir = QLineEdit(self.groupBox_7)
+ self.lineEdit_log_dir.setObjectName(u"lineEdit_log_dir")
+
+ self.gridLayout_8.addWidget(self.lineEdit_log_dir, 1, 1, 1, 1)
+
+ self.pushButton_log_dir_browse = QPushButton(self.groupBox_7)
+ self.pushButton_log_dir_browse.setObjectName(u"pushButton_log_dir_browse")
+
+ self.gridLayout_8.addWidget(self.pushButton_log_dir_browse, 1, 2, 1, 1)
+
+ self.label_monitor_log_frequency = QLabel(self.groupBox_7)
+ self.label_monitor_log_frequency.setObjectName(u"label_monitor_log_frequency")
+
+ self.gridLayout_8.addWidget(self.label_monitor_log_frequency, 3, 0, 1, 1)
+
+ self.spinBox_monitor_log_frequency = QSpinBox(self.groupBox_7)
+ self.spinBox_monitor_log_frequency.setObjectName(u"spinBox_monitor_log_frequency")
+
+ self.gridLayout_8.addWidget(self.spinBox_monitor_log_frequency, 3, 1, 1, 1)
+
+
+ self.verticalLayout_9.addLayout(self.gridLayout_8)
+
+
+ self.verticalLayout_6.addWidget(self.groupBox_7)
+
+ self.groupBox_6 = QGroupBox(self.tab_logging)
+ self.groupBox_6.setObjectName(u"groupBox_6")
+ self.formLayout_3 = QFormLayout(self.groupBox_6)
+ self.formLayout_3.setObjectName(u"formLayout_3")
+ self.label_min_notify_changes = QLabel(self.groupBox_6)
+ self.label_min_notify_changes.setObjectName(u"label_min_notify_changes")
+
+ self.formLayout_3.setWidget(1, QFormLayout.LabelRole, self.label_min_notify_changes)
+
+ self.spinBox_min_notify_changes = QSpinBox(self.groupBox_6)
+ self.spinBox_min_notify_changes.setObjectName(u"spinBox_min_notify_changes")
+
+ self.formLayout_3.setWidget(1, QFormLayout.FieldRole, self.spinBox_min_notify_changes)
+
+ self.checkBox_disable_notifications = QCheckBox(self.groupBox_6)
+ self.checkBox_disable_notifications.setObjectName(u"checkBox_disable_notifications")
+
+ self.formLayout_3.setWidget(0, QFormLayout.LabelRole, self.checkBox_disable_notifications)
+
+
+ self.verticalLayout_6.addWidget(self.groupBox_6)
+
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_6.addItem(self.verticalSpacer_2)
+
+ self.tabWidget.addTab(self.tab_logging, "")
+ self.tab_5 = QWidget()
+ self.tab_5.setObjectName(u"tab_5")
+ self.gridLayout = QGridLayout(self.tab_5)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.groupBox_9 = QGroupBox(self.tab_5)
+ self.groupBox_9.setObjectName(u"groupBox_9")
+ self.formLayout_2 = QFormLayout(self.groupBox_9)
+ self.formLayout_2.setObjectName(u"formLayout_2")
+ self.label_webhook_public_url = QLabel(self.groupBox_9)
+ self.label_webhook_public_url.setObjectName(u"label_webhook_public_url")
+
+ self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.label_webhook_public_url)
+
+ self.lineEdit_webhook_public_url = QLineEdit(self.groupBox_9)
+ self.lineEdit_webhook_public_url.setObjectName(u"lineEdit_webhook_public_url")
+
+ self.formLayout_2.setWidget(1, QFormLayout.FieldRole, self.lineEdit_webhook_public_url)
+
+ self.label_webhook_listening_host = QLabel(self.groupBox_9)
+ self.label_webhook_listening_host.setObjectName(u"label_webhook_listening_host")
+
+ self.formLayout_2.setWidget(2, QFormLayout.LabelRole, self.label_webhook_listening_host)
+
+ self.lineEdit_webhook_listening_host = QLineEdit(self.groupBox_9)
+ self.lineEdit_webhook_listening_host.setObjectName(u"lineEdit_webhook_listening_host")
+
+ self.formLayout_2.setWidget(2, QFormLayout.FieldRole, self.lineEdit_webhook_listening_host)
+
+ self.label_webhook_listening_port = QLabel(self.groupBox_9)
+ self.label_webhook_listening_port.setObjectName(u"label_webhook_listening_port")
+
+ self.formLayout_2.setWidget(3, QFormLayout.LabelRole, self.label_webhook_listening_port)
+
+ self.spinBox_webhook_listening_port = QSpinBox(self.groupBox_9)
+ self.spinBox_webhook_listening_port.setObjectName(u"spinBox_webhook_listening_port")
+ self.spinBox_webhook_listening_port.setMinimum(1)
+ self.spinBox_webhook_listening_port.setMaximum(65353)
+
+ self.formLayout_2.setWidget(3, QFormLayout.FieldRole, self.spinBox_webhook_listening_port)
+
+ self.label_webhook_expiration_interval = QLabel(self.groupBox_9)
+ self.label_webhook_expiration_interval.setObjectName(u"label_webhook_expiration_interval")
+
+ self.formLayout_2.setWidget(4, QFormLayout.LabelRole, self.label_webhook_expiration_interval)
+
+ self.spinBox_webhook_expiration_interval = QSpinBox(self.groupBox_9)
+ self.spinBox_webhook_expiration_interval.setObjectName(u"spinBox_webhook_expiration_interval")
+ self.spinBox_webhook_expiration_interval.setMaximum(1000000)
+
+ self.formLayout_2.setWidget(4, QFormLayout.FieldRole, self.spinBox_webhook_expiration_interval)
+
+ self.label_webhook_renewal_interval = QLabel(self.groupBox_9)
+ self.label_webhook_renewal_interval.setObjectName(u"label_webhook_renewal_interval")
+
+ self.formLayout_2.setWidget(5, QFormLayout.LabelRole, self.label_webhook_renewal_interval)
+
+ self.spinBox_webhook_renewal_interval = QSpinBox(self.groupBox_9)
+ self.spinBox_webhook_renewal_interval.setObjectName(u"spinBox_webhook_renewal_interval")
+ self.spinBox_webhook_renewal_interval.setMaximum(1000000)
+
+ self.formLayout_2.setWidget(5, QFormLayout.FieldRole, self.spinBox_webhook_renewal_interval)
+
+ self.checkBox_webhook_enabled = QCheckBox(self.groupBox_9)
+ self.checkBox_webhook_enabled.setObjectName(u"checkBox_webhook_enabled")
+
+ self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.checkBox_webhook_enabled)
+
+
+ self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.gridLayout.addItem(self.verticalSpacer, 1, 0, 1, 1)
+
+ self.tabWidget.addTab(self.tab_5, "")
+ self.tab_6 = QWidget()
+ self.tab_6.setObjectName(u"tab_6")
+ self.formLayout_4 = QFormLayout(self.tab_6)
+ self.formLayout_4.setObjectName(u"formLayout_4")
+ self.checkBox_auto_sync = QCheckBox(self.tab_6)
+ self.checkBox_auto_sync.setObjectName(u"checkBox_auto_sync")
+
+ self.formLayout_4.setWidget(0, QFormLayout.LabelRole, self.checkBox_auto_sync)
+
+ self.pushButton_login = QPushButton(self.tab_6)
+ self.pushButton_login.setObjectName(u"pushButton_login")
+
+ self.formLayout_4.setWidget(1, QFormLayout.LabelRole, self.pushButton_login)
+
+ self.pushButton_logout = QPushButton(self.tab_6)
+ self.pushButton_logout.setObjectName(u"pushButton_logout")
+
+ self.formLayout_4.setWidget(1, QFormLayout.FieldRole, self.pushButton_logout)
+
+ self.tabWidget.addTab(self.tab_6, "")
+
+ self.horizontalLayout.addWidget(self.tabWidget)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout)
+
+ self.pushButton_discart = QPushButton(profile_settings)
+ self.pushButton_discart.setObjectName(u"pushButton_discart")
+
+ self.verticalLayout_2.addWidget(self.pushButton_discart)
+
+ self.pushButton_save = QPushButton(profile_settings)
+ self.pushButton_save.setObjectName(u"pushButton_save")
+
+ self.verticalLayout_2.addWidget(self.pushButton_save)
+
+
+ self.retranslateUi(profile_settings)
+
+ self.tabWidget.setCurrentIndex(5)
+
+
+ QMetaObject.connectSlotsByName(profile_settings)
+ # setupUi
+
+ def retranslateUi(self, profile_settings):
+ profile_settings.setWindowTitle(QCoreApplication.translate("profile_settings", u"OneDriveGUI - Profile Settings", None))
+ self.label_profile_name.setText(QCoreApplication.translate("profile_settings", u"Profile name", None))
+ self.groupBox_8.setTitle(QCoreApplication.translate("profile_settings", u"Monitored directory", None))
+ self.pushButton_sync_dir_browse.setText(QCoreApplication.translate("profile_settings", u"Browse", None))
+ self.label_sync_dir.setText(QCoreApplication.translate("profile_settings", u"Sync Folder:", None))
+ self.checkBox_sync_root_files.setText(QCoreApplication.translate("profile_settings", u"Sync root files", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.exemptions_tab_2), QCoreApplication.translate("profile_settings", u"Monitored Files", None))
+ self.groupBox.setTitle(QCoreApplication.translate("profile_settings", u"Excluded files", None))
+ self.pushButton_add_skip_file.setText(QCoreApplication.translate("profile_settings", u"Add", None))
+ self.pushButton_rm_skip_file.setText(QCoreApplication.translate("profile_settings", u"Remove", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("profile_settings", u"Excluded directories", None))
+ self.checkBox_skip_dir_strict_match.setText(QCoreApplication.translate("profile_settings", u"Strict match", None))
+ self.pushButton_rm_skip_dir.setText(QCoreApplication.translate("profile_settings", u"Remove", None))
+ self.pushButton_add_skip_dir.setText(QCoreApplication.translate("profile_settings", u"Add", None))
+ self.groupBox_3.setTitle(QCoreApplication.translate("profile_settings", u"Others", None))
+ self.checkBox_skip_symlinks.setText(QCoreApplication.translate("profile_settings", u"Exclude symlinks", None))
+ self.checkBox_check_nosync.setText(QCoreApplication.translate("profile_settings", u"Check for .nosync", None))
+ self.checkBox_skip_dotfiles.setText(QCoreApplication.translate("profile_settings", u"Exclude dotfiles (hidden files)", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QCoreApplication.translate("profile_settings", u"Excluded files", None))
+ self.groupBox_10.setTitle(QCoreApplication.translate("profile_settings", u"Account Rate Limit", None))
+ self.label_rate_limit.setText(QCoreApplication.translate("profile_settings", u"Rate Limit [B/s]", None))
+ self.label_rate_limit_mbps.setText(QCoreApplication.translate("profile_settings", u"(Mibit/s)", None))
+ self.groupBox_4.setTitle(QCoreApplication.translate("profile_settings", u"Advanced Properties", None))
+ self.label_monitor_interval.setText(QCoreApplication.translate("profile_settings", u"Monitor interval", None))
+ self.label_monitor_fullscan_frequency.setText(QCoreApplication.translate("profile_settings", u"Monitor full-scan frequency", None))
+ self.label_classify_as_big_delete.setText(QCoreApplication.translate("profile_settings", u"Clasify as big delete", None))
+ self.label_user_agent.setText(QCoreApplication.translate("profile_settings", u"User-agent", None))
+ self.label_azure_ad_endpoint.setText(QCoreApplication.translate("profile_settings", u"Azure AD endpoint", None))
+ self.label_azure_tenant_id.setText(QCoreApplication.translate("profile_settings", u"Azure tenant ID", None))
+ self.label_sync_dir_permissions.setText(QCoreApplication.translate("profile_settings", u"Sync dir permissions", None))
+ self.label_sync_file_permissions.setText(QCoreApplication.translate("profile_settings", u"Sync file permissions", None))
+ self.label_operation_timeout.setText(QCoreApplication.translate("profile_settings", u"Operation timeout", None))
+ self.groupBox_5.setTitle(QCoreApplication.translate("profile_settings", u"Advanced options", None))
+ self.checkBox_force_http_2.setText(QCoreApplication.translate("profile_settings", u"Force HTTP2", None))
+ self.checkBox_check_nomount.setText(QCoreApplication.translate("profile_settings", u"Check for .nomount", None))
+ self.checkBox_resync.setText(QCoreApplication.translate("profile_settings", u"Full re-sync", None))
+ self.checkBox_download_only.setText(QCoreApplication.translate("profile_settings", u"Download only", None))
+ self.checkBox_local_first.setText(QCoreApplication.translate("profile_settings", u"Local first", None))
+ self.checkBox_dry_run.setText(QCoreApplication.translate("profile_settings", u"Dry run", None))
+ self.checkBox_disable_upload_validation.setText(QCoreApplication.translate("profile_settings", u"Disable upload validation", None))
+ self.checkBox_bypass_data_preservation.setText(QCoreApplication.translate("profile_settings", u"Bypass data preservation", None))
+ self.checkBox_no_remote_delete.setText(QCoreApplication.translate("profile_settings", u"No remote delete", None))
+ self.checkBox_remove_source_files.setText(QCoreApplication.translate("profile_settings", u"Remove source files", None))
+ self.checkBox_upload_only.setText(QCoreApplication.translate("profile_settings", u"Upload only", None))
+ self.checkBox_sync_business_shared_folders.setText(QCoreApplication.translate("profile_settings", u"Sync business shared folders", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("profile_settings", u"Sync Options", None))
+ self.groupBox_7.setTitle(QCoreApplication.translate("profile_settings", u"Logging", None))
+ self.checkBox_enable_logging.setText(QCoreApplication.translate("profile_settings", u"Enable Logging", None))
+ self.label_log_dir.setText(QCoreApplication.translate("profile_settings", u"Log location:", None))
+ self.checkBox_debug_https.setText(QCoreApplication.translate("profile_settings", u"Debug HTTPS", None))
+ self.pushButton_log_dir_browse.setText(QCoreApplication.translate("profile_settings", u"Browse", None))
+ self.label_monitor_log_frequency.setText(QCoreApplication.translate("profile_settings", u"Monitor log frequency", None))
+ self.groupBox_6.setTitle(QCoreApplication.translate("profile_settings", u"Notifications", None))
+ self.label_min_notify_changes.setText(QCoreApplication.translate("profile_settings", u"Minimum notify changes", None))
+ self.checkBox_disable_notifications.setText(QCoreApplication.translate("profile_settings", u"Disable notifications", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_logging), QCoreApplication.translate("profile_settings", u"Logging", None))
+ self.groupBox_9.setTitle(QCoreApplication.translate("profile_settings", u"Webhook settings", None))
+ self.label_webhook_public_url.setText(QCoreApplication.translate("profile_settings", u"Public URL", None))
+ self.label_webhook_listening_host.setText(QCoreApplication.translate("profile_settings", u"Listening host", None))
+ self.label_webhook_listening_port.setText(QCoreApplication.translate("profile_settings", u"Listening port", None))
+ self.label_webhook_expiration_interval.setText(QCoreApplication.translate("profile_settings", u"Expiration interval", None))
+ self.label_webhook_renewal_interval.setText(QCoreApplication.translate("profile_settings", u"Renewal interval", None))
+ self.checkBox_webhook_enabled.setText(QCoreApplication.translate("profile_settings", u"Enable webhook", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), QCoreApplication.translate("profile_settings", u"Webhooks", None))
+ self.checkBox_auto_sync.setText(QCoreApplication.translate("profile_settings", u"Auto-sync on startup", None))
+ self.pushButton_login.setText(QCoreApplication.translate("profile_settings", u"Login", None))
+ self.pushButton_logout.setText(QCoreApplication.translate("profile_settings", u"Logout", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_6), QCoreApplication.translate("profile_settings", u"Account", None))
+ self.pushButton_discart.setText(QCoreApplication.translate("profile_settings", u"Discard changes", None))
+ self.pushButton_save.setText(QCoreApplication.translate("profile_settings", u"Save", None))
+ # retranslateUi
+