Skip to content

Commit

Permalink
Merge pull request #13 from dsj7419/issue-1-fix-hot-swaping-projects
Browse files Browse the repository at this point in the history
Fix #1: Implement seamless project switching without restart
  • Loading branch information
dsj7419 authored Oct 27, 2024
2 parents c0ba216 + 9cb4fe5 commit 5eb7089
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 54 deletions.
6 changes: 3 additions & 3 deletions src/components/UI/AutoExcludeUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ def __init__(self, auto_exclude_manager, settings_manager, formatted_recommendat
self.project_context = project_context
self.theme_manager = ThemeManager.getInstance()

self.folder_icon = QIcon(get_resource_path("assets/images/folder_icon.png"))
self.file_icon = QIcon(get_resource_path("assets/images/file_icon.png"))
self.folder_icon = QIcon(get_resource_path("../assets/images/folder_icon.png"))
self.file_icon = QIcon(get_resource_path("../assets/images/file_icon.png"))

self.setWindowTitle('Auto-Exclude Recommendations')
self.setWindowIcon(QIcon(get_resource_path('assets/images/GynTree_logo.ico')))
self.setWindowIcon(QIcon(get_resource_path('../assets/images/GynTree_logo.ico')))

self.init_ui()

Expand Down
4 changes: 2 additions & 2 deletions src/components/UI/DashboardUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, controller):

def initUI(self):
self.setWindowTitle('GynTree Dashboard')
self.setWindowIcon(QIcon(get_resource_path('assets/images/GynTree_logo.ico')))
self.setWindowIcon(QIcon(get_resource_path('../assets/images/GynTree_logo.ico')))

central_widget = QWidget(self)
self.setCentralWidget(central_widget)
Expand All @@ -43,7 +43,7 @@ def initUI(self):
main_layout.setSpacing(20)

logo_label = QLabel()
logo_path = get_resource_path('assets/images/gyntree_logo.png')
logo_path = get_resource_path('../assets/images/gyntree_logo.png')
if os.path.exists(logo_path):
logo_pixmap = QPixmap(logo_path)
logo_label.setPixmap(logo_pixmap.scaled(128, 128, Qt.KeepAspectRatio, Qt.SmoothTransformation))
Expand Down
4 changes: 2 additions & 2 deletions src/components/UI/DirectoryTreeUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def __init__(self, controller, theme_manager: ThemeManager):
self.controller = controller
self.theme_manager = theme_manager
self.directory_structure = None
self.folder_icon = QIcon(get_resource_path("assets/images/folder_icon.png"))
self.file_icon = QIcon(get_resource_path("assets/images/file_icon.png"))
self.folder_icon = QIcon(get_resource_path("../assets/images/folder_icon.png"))
self.file_icon = QIcon(get_resource_path("../assets/images/file_icon.png"))
self.tree_widget = None
self.tree_exporter = None
self.init_ui()
Expand Down
2 changes: 1 addition & 1 deletion src/components/UI/ExclusionsManagerUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, controller, theme_manager: ThemeManager, settings_manager):
self.root_tree = None

self.setWindowTitle('Exclusions Manager')
self.setWindowIcon(QIcon(get_resource_path('assets/images/GynTree_logo.ico')))
self.setWindowIcon(QIcon(get_resource_path('../assets/images/GynTree_logo.ico')))

self.init_ui()
self.theme_manager.themeChanged.connect(self.apply_theme)
Expand Down
2 changes: 1 addition & 1 deletion src/components/UI/ProjectUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, controller):

def init_ui(self):
self.setWindowTitle('Project Manager')
self.setWindowIcon(QIcon(get_resource_path('assets/images/GynTree_logo.ico')))
self.setWindowIcon(QIcon(get_resource_path('../assets/images/GynTree_logo.ico')))

layout = QVBoxLayout()
layout.setContentsMargins(30, 30, 30, 30)
Expand Down
2 changes: 1 addition & 1 deletion src/components/UI/ResultUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, controller, theme_manager: ThemeManager, directory_analyzer):

def init_ui(self):
self.setWindowTitle('Analysis Results')
self.setWindowIcon(QIcon(get_resource_path('assets/images/GynTree_logo.ico')))
self.setWindowIcon(QIcon(get_resource_path('../assets/images/GynTree_logo.ico')))

central_widget = QWidget()
self.setCentralWidget(central_widget)
Expand Down
93 changes: 72 additions & 21 deletions src/controllers/ProjectController.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""
GynTree: ProjectController manages the loading, saving, and setting of projects.
This controller handles the main project-related operations, ensuring that the
current project is properly set up and context is established. It interacts with
the ProjectManager and ProjectContext services to manage the lifecycle of the
project within the application.
GynTree: ProjectController manages loading, saving, setting projects.
This controller handles main project-related operations, ensuring current project properly set
and context established. Interacts with ProjectManager and ProjectContext services to
manage the lifecycle of a project within the application.
Responsibilities:
- Load and save projects from the ProjectManager.
- Set the current project and initialize project context.
- Provide project-related information to the main UI.
- Load and save projects via ProjectManager.
- Set current project and initialize project context.
- Handle project transitions and cleanup.
- Provide project-related information to main UI.
"""

import logging
Expand All @@ -28,7 +28,7 @@ def __init__(self, app_controller):
def create_project(self, project: Project) -> bool:
try:
self.project_manager.save_project(project)
self._set_current_project(project)
self._transition_to_project(project)
return True
except Exception as e:
logger.error(f"Failed to create project: {str(e)}")
Expand All @@ -38,18 +38,68 @@ def load_project(self, project_name: str) -> Project:
try:
project = self.project_manager.load_project(project_name)
if project:
self._set_current_project(project)
return project
self._transition_to_project(project)
return project
except Exception as e:
logger.error(f"Failed to load project: {str(e)}")
return None
return None

def _set_current_project(self, project: Project):
if self.project_context:
self.project_context.close()
self.project_context = ProjectContext(project)
self.current_project = project
logger.debug(f"Project '{project.name}' set as active.")
def _transition_to_project(self, project: Project):
"""Handle the transition from current project to new project"""
try:
# Clean up existing project if it exists
if self.project_context:
logger.debug(f"Cleaning up existing project: {self.current_project.name}")
self._cleanup_current_project()

# Initialize new project context
logger.debug(f"Transitioning to project: {project.name}")
self.project_context = ProjectContext(project)
self.current_project = project

# Initialize new project resources
self._initialize_project_resources()

logger.debug(f"Project '{project.name}' set as active.")
except Exception as e:
logger.error(f"Failed to transition to project: {str(e)}")
raise

def _cleanup_current_project(self):
"""Clean up resources for current project"""
try:
if self.project_context:
# Save current project state
self.project_context.save_settings()

# Clean up project context
self.project_context.close()

# Clean up UI components
self.app_controller.ui_controller.reset_ui()

self.project_context = None
self.current_project = None

logger.debug("Project cleanup completed successfully")
except Exception as e:
logger.error(f"Error during project cleanup: {str(e)}")
raise

def _initialize_project_resources(self):
"""Initialize resources for new project"""
try:
if self.project_context:
# Initialize project analysis
self.project_context.initialize()

# Update UI with new project
self.app_controller.ui_controller.update_project_info(self.current_project)

logger.debug("Project resources initialized successfully")
except Exception as e:
logger.error(f"Error initializing project resources: {str(e)}")
raise

def get_theme_preference(self):
return self.project_context.get_theme_preference() if self.project_context else 'light'
Expand All @@ -61,15 +111,16 @@ def set_theme_preference(self, theme: str):
def analyze_directory(self):
"""Trigger directory analysis"""
if self.project_context:
result_ui = self.app_controller.ui_controller.show_result(self.project_context.directory_analyzer)
result_ui = self.app_controller.ui_controller.show_result(
self.project_context.directory_analyzer)
result_ui.update_result()
else:
logger.error("Cannot analyze directory: project_context is None.")

def view_directory_tree(self):
"""Trigger view of the directory structure"""
"""Trigger view directory structure"""
if self.project_context:
result = self.project_context.get_directory_tree()
self.app_controller.ui_controller.view_directory_tree(result)
else:
logger.error("Cannot view directory tree: project_context is None.")
logger.error("Cannot view directory tree: project_context is None.")
88 changes: 65 additions & 23 deletions src/services/ProjectContext.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,61 @@ def __init__(self, project: Project):
self.project_types = set()
self.detected_types = {}
self.project_type_detector = None
self.initialize()
self._is_active = False

@handle_exception
def initialize(self):
"""Initialize project context and resources"""
try:
if self._is_active:
logger.warning("Attempting to initialize already active project context")
return

logger.debug(f"Initializing project context for {self.project.name}")
self.settings_manager = SettingsManager(self.project)
self.project_type_detector = ProjectTypeDetector(self.project.start_directory)

self.detect_project_types()
self.initialize_root_exclusions()
self.initialize_auto_exclude_manager()
self.initialize_directory_analyzer()

self.settings_manager.save_settings()
self._is_active = True

logger.debug(f"Project context initialized successfully for {self.project.name}")
except Exception as e:
logger.error(f"Failed to initialize ProjectContext: {str(e)}")
self._is_active = False
raise

def detect_project_types(self):
"""Detect and set project types"""
self.detected_types = self.project_type_detector.detect_project_types()
self.project_types = {ptype for ptype, detected in self.detected_types.items() if detected}
self.project_types = {
ptype for ptype, detected in self.detected_types.items() if detected
}
logger.debug(f"Detected project types: {self.project_types}")

def initialize_root_exclusions(self):
"""Initialize and update root exclusions"""
default_root_exclusions = self.root_exclusion_manager.get_root_exclusions(
self.detected_types, self.project.start_directory
self.detected_types,
self.project.start_directory
)
current_root_exclusions = set(self.settings_manager.get_root_exclusions())

updated_root_exclusions = self.root_exclusion_manager.merge_with_existing_exclusions(
current_root_exclusions, default_root_exclusions
current_root_exclusions,
default_root_exclusions
)

if updated_root_exclusions != current_root_exclusions:
logger.info(f"Updating root exclusions: {updated_root_exclusions}")
self.settings_manager.update_settings({'root_exclusions': list(updated_root_exclusions)})

def initialize_auto_exclude_manager(self):
"""Initialize auto-exclude manager"""
try:
if not self.auto_exclude_manager:
self.auto_exclude_manager = AutoExcludeManager(
Expand All @@ -68,6 +89,7 @@ def initialize_auto_exclude_manager(self):
self.auto_exclude_manager = None

def initialize_directory_analyzer(self):
"""Initialize directory analyzer"""
self.directory_analyzer = DirectoryAnalyzer(
self.project.start_directory,
self.settings_manager
Expand All @@ -76,68 +98,88 @@ def initialize_directory_analyzer(self):

@handle_exception
def stop_analysis(self):
"""Stop ongoing analysis operations"""
if self.directory_analyzer:
self.directory_analyzer.stop()

def reinitialize_directory_analyzer(self):
"""Reinitialize directory analyzer"""
self.initialize_directory_analyzer()

@handle_exception
def trigger_auto_exclude(self) -> str:
"""Trigger auto-exclude analysis"""
if not self.auto_exclude_manager:
logger.warning("AutoExcludeManager not initialized. Attempting to reinitialize.")
self.initialize_auto_exclude_manager()

if not self.auto_exclude_manager:
logger.error("Failed to initialize AutoExcludeManager. Cannot perform auto-exclude.")
return "Auto-exclude manager initialization failed."

if not self.settings_manager:
logger.error("SettingsManager not initialized. Cannot perform auto-exclude.")
return "Settings manager missing."

try:
new_recommendations = self.auto_exclude_manager.get_recommendations()
return self.auto_exclude_manager.get_formatted_recommendations()
except Exception as e:
logger.error(f"Failed to trigger auto-exclude: {str(e)}")
return "Error during auto-exclude process."
return "Error in auto-exclude process."

def get_directory_tree(self):
"""Get directory tree structure"""
return self.directory_analyzer.analyze_directory()

def save_settings(self):
"""Save current settings"""
if self.settings_manager:
self.settings_manager.save_settings()

def get_theme_preference(self) -> str:
"""Get theme preference"""
return self.settings_manager.get_theme_preference() if self.settings_manager else 'light'

def set_theme_preference(self, theme: str):
"""Set theme preference"""
if self.settings_manager:
self.settings_manager.set_theme_preference(theme)
self.save_settings()
self.save_settings()

@property
def is_initialized(self) -> bool:
return self.settings_manager is not None and self.directory_analyzer is not None
"""Check if context is properly initialized"""
return self._is_active and self.settings_manager is not None and self.directory_analyzer is not None

@handle_exception
def close(self):
"""Close and cleanup project context"""
logger.debug(f"Closing project context for project: {self.project.name}")
self.stop_analysis()
if self.settings_manager:
self.settings_manager.save_settings()
self.settings_manager = None
if self.directory_analyzer:
self.directory_analyzer.stop()
self.directory_analyzer = None
if self.auto_exclude_manager:
self.auto_exclude_manager = None
self.project_types.clear()
self.detected_types.clear()
self.project_type_detector = None
logger.debug(f"Project context closed for project: {self.project.name}")
try:
self.stop_analysis()

if self.settings_manager:
self.settings_manager.save_settings()
self.settings_manager = None

if self.directory_analyzer:
self.directory_analyzer.stop()
self.directory_analyzer = None

if self.auto_exclude_manager:
self.auto_exclude_manager = None

self.project_types.clear()
self.detected_types.clear()
self.project_type_detector = None
self._is_active = False

logger.debug(f"Project context closed for project: {self.project.name}")
except Exception as e:
logger.error(f"Error during project context cleanup: {str(e)}")
raise

def __del__(self):
"""Destructor to ensure cleanup"""
self.close()

0 comments on commit 5eb7089

Please sign in to comment.