1
0
mirror of https://github.com/bpozdena/OneDriveGUI.git synced 2026-10-24 04:59:17 +03:00

Another attempt to restructure the code. All seems to work now. Also added option to rename a profile and prompt user before deleting a profile.

This commit is contained in:
Boris Pozdena
2025-04-27 01:17:50 +02:00
parent 733acdb97e
commit 94090c3621
5 changed files with 340 additions and 65 deletions

View File

@@ -48,15 +48,11 @@ workers = {}
if __name__ == "__main__":
# gui_settings = GuiSettings(GUI_SETTINGS_FILE)
logging.info(f"Starting OneDriveGUI v{version}")
if len(global_config) > 0:
save_global_config(global_config)
# setup_wizard = SetupWizard(profile_settings_window=profile_settings_window)
main_window = MainWindow()
main_window_start_state()

View File

@@ -48,18 +48,28 @@ import logging
# from logger import logger
from global_config import DIR_PATH, PROFILES_FILE
# try:
# from ui.ui_login import Ui_LoginWindow
# except ImportError:
# logging.warning("Failed to import ui_login. This is expected if you are running AppImage version.")
try:
from ui.ui_login import Ui_LoginWindow
except ImportError:
logging.warning("Failed to import ui_login. This is expected if you are running AppImage version.")
# Define a dummy class to avoid errors when this UI component isn't available
class Ui_LoginWindow:
def setupUi(self, window):
logging.error("LoginWindow UI is not available in this environment")
window.hide()
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
# self.setup_wizard = setup_wizard
self.setup_wizard.show_main_window_signal.connect(self.show)
# Access setup_wizard from wizard module to avoid circular imports
import wizard
wizard.setup_wizard.show_main_window_signal.connect(self.show)
wizard.setup_wizard.add_profile_signal.connect(self.add_profile)
profile_settings_window.remove_profile_signal.connect(self.remove_profile)
profile_settings_window.rename_profile_signal.connect(self.rename_profile_in_main_window)
super(MainWindow, self).__init__()
self.setupUi(self)
@@ -357,6 +367,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
)
for profile_name in global_config:
# Check if profile exists in profile_status_pages, if not add it
if profile_name not in self.profile_status_pages:
logging.info(f"Profile {profile_name} found in global_config but not in UI, adding it now")
self.add_profile(profile_name)
profile_status_page = self.profile_status_pages[profile_name]
if profile_name not in workers:
@@ -693,7 +708,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
response_dialog = QMessageBox.critical(
self,
"Login failed",
f"Login failed! Please verify your response URL and try again." f"<br><br> {response_reason}",
f"Login failed! Please verify your response URL and try again.<br><br> {response_reason}",
buttons=QMessageBox.Ok,
defaultButton=QMessageBox.Ok,
)
@@ -715,11 +730,97 @@ class MainWindow(QMainWindow, Ui_MainWindow):
workers.pop(profile_name, None)
logging.info(f"[GUI] Remaining running workers: {workers}")
def add_profile(self, profile_name):
"""
Adds a new profile to the main window UI.
Called when a new profile is created through the wizard.
"""
logging.info(f"[MAIN_WINDOW] Adding profile {profile_name} to main window UI")
# Check if profile already exists in UI
if profile_name in self.profile_status_pages:
logging.info(f"[MAIN_WINDOW] Profile {profile_name} already exists in UI, skipping add")
return
# Create a profile status page and add it to the UI
self.comboBox.addItem(profile_name)
self.profile_status_pages[profile_name] = ProfileStatusPage(profile_name)
self.profile_status_pages[profile_name].start_sync_signal.connect(self.start_onedrive_monitor)
self.profile_status_pages[profile_name].stop_sync_signal.connect(self.stop_onedrive_monitor)
self.profile_status_pages[profile_name].quit_gui_signal.connect(self.graceful_shutdown)
self.stackedLayout.addWidget(self.profile_status_pages[profile_name])
# Select the newly added profile
index = self.comboBox.findText(profile_name)
if index >= 0:
self.comboBox.setCurrentIndex(index)
self.stackedLayout.setCurrentIndex(index)
logging.info(f"[MAIN_WINDOW] Selected profile {profile_name} at index {index}")
# Show comboBox if more than one profile exists
if len(self.profile_status_pages) > 1:
self.comboBox.show()
# Force update the UI
self.comboBox.update()
self.update()
# Make the profile visible and force a layout refresh
self.comboBox.show()
self.stackedLayout.update()
self.verticalLayout_2.update()
# Process events to ensure UI updates immediately
from PySide6.QtCore import QCoreApplication
QCoreApplication.processEvents()
@Slot(str, str)
def rename_profile_in_main_window(self, old_name, new_name):
"""
Updates the profile name in the main window UI elements.
"""
logging.info(f"[MAIN_WINDOW] Renaming profile '{old_name}' to '{new_name}' in main window UI.")
# 1. Update the item text in the comboBox.
index = self.comboBox.findText(old_name)
if index >= 0:
self.comboBox.setItemText(index, new_name)
logging.debug(f"[MAIN_WINDOW] Updated comboBox item at index {index} from '{old_name}' to '{new_name}'.")
else:
logging.warning(f"[MAIN_WINDOW] Profile '{old_name}' not found in comboBox.")
# 2. Update the key in the self.profile_status_pages dictionary.
if old_name in self.profile_status_pages:
self.profile_status_pages[new_name] = self.profile_status_pages.pop(old_name)
# Also update the profile_name attribute within the ProfileStatusPage instance
self.profile_status_pages[new_name].profile_name = new_name
logging.debug(f"[MAIN_WINDOW] Updated profile_status_pages key from '{old_name}' to '{new_name}'.")
else:
logging.warning(f"[MAIN_WINDOW] Profile '{old_name}' not found in profile_status_pages.")
# 3. Update the key in the workers dictionary if a worker exists.
if old_name in workers:
workers[new_name] = workers.pop(old_name)
# Also update the profile_name attribute within the WorkerThread instance
workers[new_name].profile_name = new_name
logging.debug(f"[MAIN_WINDOW] Updated workers key from '{old_name}' to '{new_name}'.")
else:
logging.debug(f"[MAIN_WINDOW] No worker found for profile '{old_name}'.")
# Force update the UI
self.comboBox.update()
self.update()
# Process events to ensure UI updates immediately
from PySide6.QtCore import QCoreApplication
QCoreApplication.processEvents()
def remove_profile(self, profile_name):
"""
Removes onedrive profile from the GUI.
"""
print("AAAAA")
combo_box_index = self.comboBox.findText(profile_name)
self.comboBox.removeItem(combo_box_index)
self.stackedLayout.setCurrentIndex(0)

View File

@@ -7,7 +7,7 @@ from settings.gui_settings import gui_settings
client_bin_path = gui_settings.get("frameless_window")
version = "1.1.1"
version = "1.2.0"
client_bin_path = config_client_bin_path()
client_version = get_installed_client_version(client_bin_path)
global_config = create_global_config()

View File

@@ -8,6 +8,7 @@ from PySide6.QtWidgets import (
QMessageBox,
QStyledItemDelegate,
QStyleOptionViewItem,
QInputDialog,
)
# Imports for Profiles windows.
@@ -19,13 +20,16 @@ import os
import copy
from configparser import ConfigParser
from wizard import setup_wizard
# Import setup_wizard after importing wizard, keeping it at the bottom to avoid circular imports
import wizard
from global_config import save_global_config
# from main_window import main_window
# Import main_window (lazy import to avoid circular references)
import main_window
from options import client_bin_path, global_config
from options import temp_global_config # , profile_settings_window
from options import temp_global_config
from workers import workers
import logging
@@ -35,13 +39,14 @@ from global_config import DIR_PATH, PROFILES_FILE
class ProfileSettingsWindow(QWidget, Ui_profile_settings_window):
remove_profile_signal = Signal(str)
rename_profile_signal = Signal(str, str) # Signal to emit old_name, new_name
def __init__(self):
super(ProfileSettingsWindow, self).__init__()
# Listen for Signals from wizard
# self.setup_wizard = setup_wizard
setup_wizard.add_profile_signal.connect(self.add_profile)
# Access setup_wizard through the wizard module to avoid circular imports
wizard.setup_wizard.add_profile_signal.connect(self.add_profile)
self.unsaved_profiles = []
@@ -64,6 +69,7 @@ class ProfileSettingsWindow(QWidget, Ui_profile_settings_window):
self.listWidget_profiles.itemSelectionChanged.connect(self.switch_account_settings_page)
self.pushButton_remove.clicked.connect(self.remove_profile)
self.pushButton_rename.clicked.connect(self.rename_profile)
self.pushButton_create_import.clicked.connect(self.show_setup_wizard)
def add_profile(self, profile):
@@ -120,36 +126,124 @@ class ProfileSettingsWindow(QWidget, Ui_profile_settings_window):
self.stackedLayout.setCurrentIndex(self.listWidget_profiles.currentRow())
def show_setup_wizard(self):
# self.setup_wizard = SetupWizard()
setup_wizard.setStartId(3)
setup_wizard.show()
# Access setup_wizard through the wizard module to avoid circular imports
wizard.setup_wizard.setStartId(3)
wizard.setup_wizard.show()
def remove_profile(self):
# Stop checking for unsaved changes while new profile is being removed.
self.stop_unsaved_changes_timer()
# 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.
print(f"emit removal of profile {selected_profile_name}")
self.remove_profile_signal.emit(selected_profile_name)
# Add confirmation dialog
confirm_removal = QMessageBox.question(
self,
"Confirm Profile Removal",
f"Are you sure you want to remove the profile <b>{selected_profile_name}</b>?",
buttons=QMessageBox.Yes | QMessageBox.No,
defaultButton=QMessageBox.No,
)
# Load existing user profiles and remove the new profile.
_profiles = ConfigParser()
_profiles.read(PROFILES_FILE)
_profiles.remove_section(selected_profile_name)
if confirm_removal == QMessageBox.Yes:
logging.info(f"[GUI] Removing profile: {selected_profile_name}")
# Remove profile from settings window.
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)
# Save the new profiles file.
with open(PROFILES_FILE, "w") as profilefile:
_profiles.write(profilefile)
# Remove profile from main window.
print(f"emit removal of profile {selected_profile_name}")
self.remove_profile_signal.emit(selected_profile_name)
# Start checking for unsaved changes again after profile has been removed.
self.start_unsaved_changes_timer()
# Load existing user profiles and remove the new profile.
_profiles = ConfigParser()
_profiles.read(PROFILES_FILE)
_profiles.remove_section(selected_profile_name)
# Save the new profiles file.
with open(PROFILES_FILE, "w") as profilefile:
_profiles.write(profilefile)
# Start checking for unsaved changes again after profile has been removed.
self.start_unsaved_changes_timer()
else:
logging.info(f"[GUI] Profile removal cancelled for: {selected_profile_name}")
# If removal is cancelled, restart the timer as no changes were made
self.start_unsaved_changes_timer()
def rename_profile(self):
selected_item = self.listWidget_profiles.currentItem()
if not selected_item:
QMessageBox.warning(self, "Rename Profile", "Please select a profile to rename.")
return
old_name = selected_item.text()
new_name, ok = QInputDialog.getText(
self, "Rename Profile", f"Enter new name for profile '{old_name}':", text=old_name
)
if ok and new_name:
new_name = new_name.strip()
if not new_name:
QMessageBox.warning(self, "Rename Profile", "Profile name cannot be empty.")
return
if new_name == old_name:
logging.info("[GUI] Rename cancelled: New name is the same as the old name.")
return
if new_name in global_config:
QMessageBox.warning(self, "Rename Profile", f"Profile '{new_name}' already exists.")
return
logging.info(f"[GUI] Renaming profile '{old_name}' to '{new_name}'.")
# Stop checking for unsaved changes during rename
self.stop_unsaved_changes_timer()
# 4. Update global_config and temp_global_config.
global_config[new_name] = global_config.pop(old_name)
temp_global_config[new_name] = temp_global_config.pop(old_name)
# 5. Rename section in PROFILES_FILE.
_profiles = ConfigParser()
_profiles.read(PROFILES_FILE)
if _profiles.has_section(old_name):
items = _profiles.items(old_name)
_profiles.add_section(new_name)
for key, value in items:
_profiles.set(new_name, key, value)
_profiles.remove_section(old_name)
with open(PROFILES_FILE, "w") as profilefile:
_profiles.write(profilefile)
else:
logging.warning(f"[GUI] Section '{old_name}' not found in {PROFILES_FILE}.")
# 6. Update listWidget_profiles item.
selected_item.setText(new_name)
# 7. Update label_profile_name in the corresponding ProfileSettingsPage.
current_index = self.listWidget_profiles.currentRow()
page_widget = self.stackedLayout.widget(current_index)
if isinstance(page_widget, ProfileSettingsPage):
page_widget.profile = new_name # Update the profile name stored in the page widget
page_widget.label_profile_name.setText(new_name)
# Update the temp_profile_config reference in the page widget
page_widget.temp_profile_config = temp_global_config[new_name]
# 8. Signal main_window to update profile name display.
self.rename_profile_signal.emit(old_name, new_name)
# Save the updated global config
save_global_config(global_config)
# Restart checking for unsaved changes
self.start_unsaved_changes_timer()
elif not ok:
logging.info("[GUI] Rename profile dialog cancelled.")
# If ok is True but new_name is empty, the validation message box is shown.
class ListItemDelegate(QStyledItemDelegate):
@@ -203,7 +297,19 @@ class ProfileSettingsPage(QWidget, Ui_profile_settings):
config_changed = False
sync_list_changed = False
pixmap_warning = QPixmap(DIR_PATH + "/resources/images/warning.png").scaled(20, 20, Qt.KeepAspectRatio)
unsaved_profile = profile_settings_window.listWidget_profiles.findItems(self.profile, Qt.MatchExactly)[0]
# Check if profile exists in listWidget
matching_items = profile_settings_window.listWidget_profiles.findItems(self.profile, Qt.MatchExactly)
if not matching_items:
logging.warning(f"[PROFILE_SETTINGS] Profile {self.profile} not found in UI list, skipping check")
return
unsaved_profile = matching_items[0]
# Check if profile exists in global_config
if self.profile not in global_config:
logging.warning(f"[PROFILE_SETTINGS] Profile {self.profile} not found in global_config, skipping check")
return
# Unsaved changes to config file?
if global_config[self.profile] != self.temp_profile_config:
@@ -328,7 +434,7 @@ class ProfileSettingsPage(QWidget, Ui_profile_settings):
self.checkBox_disable_notifications.stateChanged.connect(self.validate_checkbox_input)
# Account tab
self.pushButton_login.clicked.connect(lambda: main_window.show_login(self.profile))
self.pushButton_login.clicked.connect(lambda: main_window.main_window_instance.show_login(self.profile))
self.pushButton_logout.clicked.connect(self.logout)
self.checkBox_auto_sync.stateChanged.connect(self.set_check_box_state_profile)
@@ -516,17 +622,19 @@ class ProfileSettingsPage(QWidget, Ui_profile_settings):
os.system(f"{client_bin_path} --confdir='{self.config_dir}' --logout")
logging.info(f"Profile {self.profile} has been logged out.")
main_window.profile_status_pages[self.profile].stop_monitor()
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(
main_window.main_window_instance.profile_status_pages[self.profile].stop_monitor()
if self.profile in workers:
workers[self.profile].stop_worker()
main_window.main_window_instance.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")
main_window.main_window_instance.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("~/"))

View File

@@ -14,9 +14,6 @@ from PySide6.QtWidgets import (
QComboBox,
)
# from profile_settings_window import profile_settings_page
# from main_window import ProfileStatusPage
import re
import os
import sys
@@ -25,11 +22,7 @@ import requests
import subprocess
from configparser import ConfigParser
# from wizard import SetupWizard
from global_config import save_global_config, read_config
# from profile_settings_window import profile_settings_window, ProfileSettingsPage
# from main_window import main_window
from options import (
global_config,
temp_global_config,
@@ -38,26 +31,17 @@ from options import (
version,
)
from workers import MaintenanceWorker
import logging
# from logger import logger
from global_config import DIR_PATH, PROFILES_FILE
class SetupWizard(QWizard):
show_main_window_signal = Signal()
add_profile_signal = Signal(str)
def __init__(self, parent=None):
super(SetupWizard, self).__init__(parent)
# Lazy import
from profile_settings_window import ProfileSettingsWindow
self.profile_settings_window = ProfileSettingsWindow()
self.setWindowIcon(QIcon(DIR_PATH + "/resources/images/icons8-clouds-80-dark-edge.png"))
self.setPage(1, WizardPage_welcome(self))
@@ -74,6 +58,45 @@ class SetupWizard(QWizard):
self.currentIdChanged.connect(self.on_page_change)
# self.profile_settings_window = profile_settings_window
def showEvent(self, event):
"""Reset form fields when wizard is shown"""
super(SetupWizard, self).showEvent(event)
# Always restart at the first page (Welcome)
self.restart()
# Reset the create profile page
create_page = self.page(4)
if hasattr(create_page, "lineEdit_new_profile_name"):
create_page.lineEdit_new_profile_name.setText("")
create_page.lineEdit_sync_dir.setText("")
create_page.pushButton_create.setText("Create new profile")
create_page.pushButton_create.setEnabled(False)
create_page.lineEdit_new_profile_name.setEnabled(True)
create_page.lineEdit_sync_dir.setEnabled(True)
create_page.pushButton_browse.setEnabled(True)
# Reset the import profile page
import_page = self.page(5)
if hasattr(import_page, "lineEdit_profile_name"):
import_page.lineEdit_profile_name.setText("")
import_page.lineEdit_config_path.setText("")
import_page.pushButton_import.setText("Import")
import_page.pushButton_import.setEnabled(False)
import_page.lineEdit_profile_name.setEnabled(True)
import_page.lineEdit_config_path.setEnabled(True)
import_page.pushButton_browse.setEnabled(True)
# Reset the create/import selection page
selection_page = self.page(3)
if hasattr(selection_page, "checkBox_create"):
selection_page.checkBox_create.setChecked(False)
selection_page.checkBox_import.setChecked(False)
selection_page.checkBox_sharepoint_library.setChecked(False)
selection_page.checkBox_create.setEnabled(True)
selection_page.checkBox_import.setEnabled(True)
selection_page.checkBox_sharepoint_library.setEnabled(True)
def on_page_change(self):
if self.currentId() == 2:
logging.info("Checking installed OneDrive version")
@@ -348,12 +371,22 @@ class wizardPage_create_shared_library(QWizardPage):
logging.info(f"[GUI] Starting maintenance worker to obtain SharePoint Library List for profile {profile_name}.")
# Create and configure the worker before connecting signals
self.obtain_sharepoint_site_list = MaintenanceWorker(profile_name, options)
self.obtain_sharepoint_site_list.start()
# Connect signals before starting the worker
self.obtain_sharepoint_site_list.update_sharepoint_site_list.connect(
self.populate_comboBox_sharepoint_site_list
)
# Process events to ensure UI stays responsive
from PySide6.QtCore import QCoreApplication
QCoreApplication.processEvents()
# Start the worker after all connections are set up
self.obtain_sharepoint_site_list.start()
def populate_comboBox_sharepoint_site_list(self, sharepoint_site_list):
"""
Populates comboBox_sharepoint_site_list with a list of emitted SharePoint Sites.
@@ -443,7 +476,7 @@ class wizardPage_create_shared_library(QWizardPage):
_site_name = self.comboBox_sharepoint_site_list.currentText()
_library_name = self.comboBox_sharepoint_library_list.currentText()
_library_id = self.library_dict[_library_name]
profile_name = f'SharePoint_{_site_name.replace(" ", "_")}_{_library_name.replace(" ", "_")}'
profile_name = f"SharePoint_{_site_name.replace(' ', '_')}_{_library_name.replace(' ', '_')}"
sync_dir = os.path.expanduser(f"~/{profile_name}")
config_path = os.path.expanduser(f"~/.config/onedrive/accounts/{profile_name}/config")
@@ -517,6 +550,16 @@ class wizardPage_create_shared_library(QWizardPage):
self.pushButton_create_profile.setDisabled(True)
self.comboBox_sharepoint_library_list.setDisabled(True)
# Emit signal to add the profile to profile settings window and main window
logging.info(f"[WIZARD] Emitting add_profile_signal for {profile_name}")
wizard_instance = self.wizard()
logging.info(f"[WIZARD] wizard_instance: {wizard_instance}")
if wizard_instance:
wizard_instance.add_profile_signal.emit(profile_name)
logging.info(f"[WIZARD] Signal emitted successfully")
else:
logging.error(f"[WIZARD] Failed to get wizard instance!")
self.completeChanged.emit()
@@ -658,7 +701,15 @@ class wizardPage_create(QWizardPage):
save_global_config(global_config)
# Add Setting page widget for new profile
self.add_profile_signal.emit(profile_name)
# Emit signal to add the profile to profile settings window and main window
logging.info(f"[WIZARD] Emitting add_profile_signal for {profile_name}")
wizard_instance = self.wizard()
logging.info(f"[WIZARD] wizard_instance: {wizard_instance}")
if wizard_instance:
wizard_instance.add_profile_signal.emit(profile_name)
logging.info(f"[WIZARD] Signal emitted successfully")
else:
logging.error(f"[WIZARD] Failed to get wizard instance!")
# self.profile_settings_window.listWidget_profiles.addItem(profile_name)
# # self.setting_page = ProfileSettingsPage(profile_name)
# self.profile_settings_window.stackedLayout.addWidget(self.setting_page)
@@ -684,7 +735,16 @@ class wizardPage_create(QWizardPage):
self.lineEdit_new_profile_name.setDisabled(True)
self.lineEdit_sync_dir.setDisabled(True)
self.pushButton_browse.setDisabled(True)
# Force UI update immediately
from PySide6.QtCore import QCoreApplication
QCoreApplication.processEvents()
# Signal that this page is complete so the wizard can move to the next page
self.completeChanged.emit()
# Force the wizard to advance to the next page
self.wizard().next()
class wizardPage_import(QWizardPage):
@@ -837,6 +897,15 @@ class wizardPage_import(QWizardPage):
# Automatically save global config to prevent loss if user does not press 'Save' button.
save_global_config(global_config)
# Emit signal to add the profile to main window and profile settings window
logging.info(f"[WIZARD] Emitting add_profile_signal for imported profile {profile_name}")
wizard_instance = self.wizard()
if wizard_instance:
wizard_instance.add_profile_signal.emit(profile_name)
logging.info(f"[WIZARD] Signal emitted successfully for imported profile")
else:
logging.error(f"[WIZARD] Failed to get wizard instance!")
# Start checking for unsaved changes again after new profile has being created.
# self.profile_settings_window.start_unsaved_changes_timer()
@@ -867,4 +936,5 @@ class wizardPage_finish(QWizardPage):
self.setLayout(layout)
# Create a single instance of the wizard
setup_wizard = SetupWizard()