Skip to content

feat: add Windows GUI application for Evernote Backup#148

Open
duffdeal wants to merge 10 commits intovzhd1701:masterfrom
duffdeal:claude/convert-to-windows-gui-011CUrYiWqGULXbkaMx6fyhQ
Open

feat: add Windows GUI application for Evernote Backup#148
duffdeal wants to merge 10 commits intovzhd1701:masterfrom
duffdeal:claude/convert-to-windows-gui-011CUrYiWqGULXbkaMx6fyhQ

Conversation

@duffdeal
Copy link
Copy Markdown

@duffdeal duffdeal commented Nov 6, 2025

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

claude added 10 commits November 6, 2025 11:28
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants