feat: add Windows GUI application for Evernote Backup#148
Open
duffdeal wants to merge 10 commits intovzhd1701:masterfrom
Open
feat: add Windows GUI application for Evernote Backup#148duffdeal wants to merge 10 commits intovzhd1701:masterfrom
duffdeal wants to merge 10 commits intovzhd1701:masterfrom
Conversation
This commit adds a complete Windows GUI application built with tkinter, providing a user-friendly interface for all evernote-backup functionality. Features: - Setup tab for database initialization with OAuth/password auth - Sync tab for downloading and syncing notes from Evernote - Export tab for exporting notes to ENEX format - Tools tab for database management, reauth, checking, and listing - Progress indicators and real-time log output - Threading to prevent UI freezing during long operations New files: - evernote_backup/gui.py: Main GUI application with tabbed interface - evernote-backup-gui.spec: PyInstaller configuration for building .exe - build_gui.bat: Windows build script - build_gui.sh: Linux/macOS build script - GUI_README.md: Complete documentation for GUI version - requirements-build.txt: Build dependencies (PyInstaller) Changes: - pyproject.toml: Added GUI entry points (evernote-backup-gui) To build the Windows executable: 1. Run build_gui.bat on Windows 2. Executable will be created as dist/EvernoteBackup.exe To run from source: python -m evernote_backup.gui or evernote-backup-gui
Fixed all cli_app function calls to use the correct parameter names: - init_db: use auth_oauth_port/auth_oauth_host instead of oauth_port/oauth_host - init_db: add missing parameters (auth_token, use_system_ssl_ca, custom_api_data) - sync: use download_cache_memory_limit instead of download_cache_memory_limit_mb - sync: use include_tasks instead of sync_tasks, add token parameter - export: use output_path instead of target, include_trash instead of export_trash - export: add missing parameters (add_guid, add_metadata, notebooks, tags) - reauth: add all missing parameters matching init_db signature - manage_ping: rename from ping() and use correct parameters This fixes the TypeError that prevented database initialization and other operations.
When running as a GUI application on Windows (especially when built with PyInstaller), sys.stdout and sys.stderr are None because there's no console attached. This causes AttributeError when the code tries to call sys.stdout.isatty() in cli_app_util.is_output_to_terminal(). Solution: Initialize sys.stdout and sys.stderr to io.StringIO() at the start of the GUI module if they are None. StringIO provides the isatty() method (returns False), which allows the code to work correctly in GUI mode. This fixes the AttributeError during database initialization with OAuth.
The OAuth authentication flow checks if output is going to a terminal (isatty()) and refuses to run in non-interactive mode. This prevented OAuth from working in the GUI. Solution: Created a FakeTTY class that extends io.StringIO and overrides isatty() to return True. This makes the OAuth code think it's running in an interactive terminal, allowing the browser-based OAuth flow to proceed. The OAuth flow itself works fine in GUI mode: - Browser opens automatically (or URL can be copied) - Local HTTP server receives the OAuth callback - Token is saved to database This also enables any other interactive features (like 2FA prompts) to work in GUI mode.
Major improvements to make operations visible and debuggable in GUI mode: 1. Enhanced FakeTTY class: - Added set_log_widget() method to connect stdout to GUI log widget - Added custom write() method that writes to both buffer and log widget - Now captures click.echo() and print() statements in the GUI log 2. Improved logging setup for all operations: - Changed from module logger to root logger to capture all evernote_backup logs - Added root_logger.setLevel(logging.INFO) to ensure all messages are captured - Connected stdout to log widget for each operation - Properly disconnect stdout after operation completes 3. Added informative status messages: - OAuth flow now shows helpful messages before starting - Users can see when browser should open and what port is being used 4. Fixed handler cleanup: - Changed logger.removeHandler() to root_logger.removeHandler() - Added FakeTTY disconnection in all operation cleanups This fixes the issue where database initialization appeared to hang with no visible activity. Now users will see: - "Starting OAuth authentication..." - "Your browser will open for authorization." - All click.echo() messages from the OAuth flow - All log messages from evernote_backup modules - Progress and status updates throughout the operation
The cli_app functions expect to run within a Click context to access parameters like 'verbose' and 'quiet'. When running from GUI mode, there is no Click context, causing RuntimeError: "There is no active click context." This error occurred during sync operations when the code tried to call: - click.get_current_context() in get_progress_output() - click.get_current_context() in is_console_interactive() Solution: 1. Created create_click_context() function that creates a minimal Click context with 'verbose' and 'quiet' parameters 2. Wrapped all cli_app function calls with this context using 'with ctx:' 3. Applied to all operations: - init_db (database initialization) - sync (note synchronization) - export (ENEX export) - reauth (token refresh) - check_database (integrity check) - list_notebooks (list contents) - test_connection (ping servers) The context is set to quiet mode (--quiet) which disables progress bars that would interfere with GUI logging, while still allowing all operations to complete successfully. This fixes the RuntimeError during sync and ensures all GUI operations work properly.
Added extensive logging throughout the OAuth authentication flow to help diagnose the "No client verifier has been set" error. Changes to evernote_client_oauth.py: 1. Added logging import and logger instance 2. Added INFO-level logging in EvernoteOAuthCallbackHandler: - Logs callback URL - Logs when waiting for callback - Logs when callback is received - Logs success/failure of token retrieval 3. Added DEBUG-level logging in EvernoteOAuthClient: - Logs OAuth session creation - Logs request token fetch - Logs authorization URL generation - Logs callback response parsing - Logs access token fetch 4. Added detailed error logging with exc_info for all exceptions Changes to gui.py: 1. Set root logger to DEBUG level for init operations (was INFO) 2. Added helpful user instructions for OAuth process 3. Instructions explain to wait for "You can close this tab now..." message This enhanced logging will help identify exactly where the OAuth flow is failing: - If session creation fails - If request token fetch fails - If callback isn't received properly - If verifier is missing from callback - If access token exchange fails The detailed logs will be visible in the GUI log widget, making it much easier to diagnose OAuth issues.
The sync was failing with EOFError when the connection was interrupted
or the server closed the connection unexpectedly during data transfer.
This is a common network error that should be retried automatically.
Changes:
1. Added EOFError to DEFAULT_RETRY_EXCEPTIONS
- Already had: HTTPException, ConnectionError
- Now also retries: EOFError (connection closed prematurely)
2. Added logging import and logger instance
3. Enhanced retry wrapper with informative logging:
- WARNING on each retry attempt with:
* Current attempt number
* Total attempts
* Exception type and message
* Retry delay time
- ERROR after final failed attempt with full details
Benefits:
- EOFError during sync will now automatically retry (up to 3 times)
- Users will see retry attempts in the GUI log
- Exponential backoff prevents overwhelming the server
- Clear feedback about network issues vs permanent failures
Example log output:
"Network error on attempt 1/4: EOFError. Retrying in 0.5s..."
"Network error on attempt 2/4: EOFError. Retrying in 1.0s..."
This should resolve the sync failure and make the sync process more
resilient to temporary network issues.
Previously, all logs were only displayed in the GUI widgets and lost when the application closed or crashed. This made debugging issues difficult, especially for long-running sync operations. Changes: 1. Added persistent log files in user's home directory: - Location: ~/.evernote-backup/logs/ - Format: gui_YYYYMMDD_HHMMSS.log - Each GUI session creates a new timestamped log file 2. Enhanced logging configuration: - Logs to both file and GUI widgets simultaneously - UTF-8 encoding for international characters - Includes module names in log format for better debugging - INFO level by default, DEBUG for init operations 3. Added log file cleanup: - Automatically deletes log files older than 30 days - Prevents disk space issues from accumulating logs - Cleanup runs silently at startup 4. Added user-friendly log folder access: - Clickable blue link at bottom of window showing log location - Click to open logs folder in file explorer - Works on Windows, macOS, and Linux - Shows informative startup banner in logs 5. Startup log banner shows: - Session start time - Log file path - Log directory location Benefits: - Debug crashes and errors even after app closes - Review long sync operations after completion - Share log files for troubleshooting - Track application behavior over time - Easy access to logs via GUI Example log location on Windows: C:\Users\YourName\.evernote-backup\logs\gui_20251106_143000.log
Evernote API has rate limits that stop sync operations when you download too many notes too quickly. Previously, sync would fail with EDAMSystemException when hitting rate limits. Changes: 1. Import EDAMSystemException and EDAMErrorCode from Evernote SDK 2. Added special exception handling for RATE_LIMIT_REACHED errors 3. Automatically waits the exact duration specified by Evernote API 4. Shows countdown progress every 60 seconds (and final 10 seconds) 5. Automatically retries after waiting How it works: - When rate limit is hit, extracts rateLimitDuration (e.g., 1483 seconds) - Logs: "Evernote API rate limit reached. Waiting 1483 seconds..." - Counts down: "Rate limit: 1380 seconds remaining..." - After wait completes: "Rate limit wait complete, retrying..." - Automatically continues sync from where it left off Benefits: - No manual intervention needed - Sync continues automatically after rate limit expires - Clear feedback about wait time - No need to restart sync manually - Progress updates keep user informed Example log output: "Evernote API rate limit reached. Waiting 1483 seconds before retrying..." "Rate limit: 1440 seconds remaining..." "Rate limit: 1380 seconds remaining..." ... "Rate limit: 60 seconds remaining..." "Rate limit: 10 seconds remaining..." "Rate limit wait complete, retrying..." This allows syncing large notebooks (30k+ notes) to complete successfully even when hitting API rate limits.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit adds a complete Windows GUI application built with tkinter, providing a user-friendly interface for all evernote-backup functionality.
Features:
New files:
Changes:
To build the Windows executable:
To run from source:
python -m evernote_backup.gui
or
evernote-backup-gui