Skip to content

Commit b12dc66

Browse files
committed
Update to v1.1.1 - fix login and session
1 parent 909fe6d commit b12dc66

6 files changed

Lines changed: 131 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,23 @@ From **v1.0.1** onwards, the app includes a "Smart Migration" feature to handle
1515
>
1616
> **Upgrading from v1.0.2 or earlier:** The old Startup-folder shortcut (`GarminUploader.lnk`) and XOR-encrypted password in `uploader_config.json` are automatically migrated on first launch of v1.0.3. No manual steps required.
1717
18+
## [1.1.1] - 2026-04-30
19+
20+
### Changed (1.1.1)
21+
22+
- **More reliable Garmin login setup:** This release locks the app to the Garmin login version that worked in testing, so fresh sign-ins are more consistent.
23+
- **Update button now opens the right page:** In-app update prompts now take you to the current GitHub releases page.
24+
25+
### Fixed (1.1.1)
26+
27+
- **Fixed some Garmin login problems on first setup:** This should reduce the chance of hitting Garmin login errors when testing a new account.
28+
- **Fixed a bad environment mismatch:** The app now warns you clearly if it is running with an unsupported Garmin login library.
29+
1830
## [1.1.0] - TBD
1931

2032
### 🔐 Major Authentication Upgrade (1.1.0)
2133

22-
- **NEW: Mobile SSO Authentication:** Upgraded to `garminconnect` v0.3.1 which uses Garmin's official mobile SSO authentication flow (same as the Android app). This replaces the deprecated `garth` library that was causing authentication failures and rate limiting issues.
34+
- **NEW: Mobile SSO Authentication:** Upgraded to `garminconnect` v0.3.2 which uses Garmin's official mobile SSO authentication flow (same as the Android app). This replaces the deprecated `garth` library that was causing authentication failures and rate limiting issues.
2335
- **More reliable authentication:** The new mobile SSO endpoint (`sso.garmin.com/mobile/api/login`) is actively maintained by Garmin and significantly more stable than the previous method.
2436
- **Automatic token refresh:** Authentication tokens now auto-refresh indefinitely without user interaction, eliminating the need for periodic re-authentication.
2537

README.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Automatically upload workout activities from Wahoo, MyWhoosh, and TrainerDay to Garmin Connect.
44

5-
Version 1.0.7 | Windows Desktop Application (Nuitka onefile + folder build)
5+
Version 1.1.1 | Windows Desktop Application (Nuitka onefile + folder build)
66

77
---
88

@@ -70,20 +70,18 @@ I built this originally as a personal Python script to solve my own manual uploa
7070

7171
**Two download options are available:**
7272

73-
| Option | File | Best for |
74-
|--------|------|----------|
75-
| **Folder build (recommended)** | `GarminUploader-v1.0.7-folder.zip` | Most users — less likely to trigger Windows Defender |
76-
| **Single-file build** | `GarminUploader-v1.0.7.exe` | Advanced users who prefer a single executable |
73+
- **Folder build (recommended):** `GarminUploader-v1.1.1-folder.zip` — Most users; less likely to trigger Windows Defender.
74+
- **Single-file build:** `GarminUploader-v1.1.1.exe` — Advanced users who prefer a single executable.
7775

7876
#### Option A: Folder build (recommended)
7977

80-
1. Download `GarminUploader-v1.0.7-folder.zip` from Releases
78+
1. Download `GarminUploader-v1.1.1-folder.zip` from Releases
8179
2. Extract the ZIP to a folder (e.g., `C:\GarminUploader\` or your Desktop)
8280
3. Run `GarminUploader.exe` from the extracted folder
8381

8482
#### Option B: Single-file build
8583

86-
1. Download `GarminUploader-v1.0.7.exe` from Releases
84+
1. Download `GarminUploader-v1.1.1.exe` from Releases
8785
2. Place it anywhere you like (Desktop, Documents, etc.)
8886
3. Double-click to run
8987

@@ -242,11 +240,11 @@ The app stores its configuration and log files alongside the app, usually in a s
242240
**Recommended layout for folder builds (for easy upgrades):**
243241

244242
```text
245-
C:\\GarminUploader\\
243+
C:\GarminUploader\
246244
uploader_config.json
247245
garmin_uploader.log
248246
garmin_uploads.log
249-
GarminUploader-v1.0.7-folder\\
247+
GarminUploader-v1.1.1-folder\
250248
GarminUploader.exe
251249
```
252250

@@ -329,7 +327,7 @@ Because this app is a small, niche tool that logs in to a website, stores creden
329327

330328
**What you can do:**
331329

332-
1. **Try the folder build:** Download `GarminUploader-v1.0.5-folder.zip` instead of the single `.exe`. Unpacked builds are less likely to trigger false positives.
330+
1. **Try the folder build:** Download `GarminUploader-v1.1.1-folder.zip` instead of the single `.exe`. Unpacked builds are less likely to trigger false positives.
333331

334332
2. **Add an exception:** If you trust the source, add the app folder to Windows Defender's exclusion list:
335333
- Open **Windows Security****Virus & threat protection****Manage settings**
@@ -344,8 +342,8 @@ Because this app is a small, niche tool that logs in to a website, stores creden
344342
4. **Build from source:** If you prefer not to trust pre-built binaries:
345343

346344
```bash
347-
git clone https://github.com/Inc21/Wahoo-and-MyWhoos-to-Garmin-Conect-Auto-Uploader.git
348-
cd Wahoo-and-MyWhoos-to-Garmin-Conect-Auto-Uploader
345+
git clone https://github.com/Inc21/Garmin-Connect-Auto-Uploader.git
346+
cd Garmin-Connect-Auto-Uploader
349347
pip install -r requirements.txt
350348
python uploader_gui.py
351349
```
5.34 KB
Binary file not shown.

requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
garminconnect==0.3.2
2+
curl_cffi
3+
Pillow==11.2.1
4+
pystray==0.19.5
5+
keyring
6+
fitdecode

uploader_gui.py

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@
2323
import xml.etree.ElementTree as ET
2424
import urllib.request
2525
import urllib.error
26-
from garminconnect import Garmin
26+
from importlib.metadata import PackageNotFoundError, version as get_installed_package_version
27+
try:
28+
from garminconnect import Garmin
29+
GARMINCONNECT_IMPORT_ERROR = None
30+
except Exception as exc:
31+
Garmin = None
32+
GARMINCONNECT_IMPORT_ERROR = exc
2733
from PIL import Image, ImageTk
2834
from pystray import Icon, Menu, MenuItem
2935
import webbrowser
@@ -53,6 +59,16 @@ def _get_base_and_log_dirs():
5359
script_dir = os.path.dirname(os.path.abspath(__file__))
5460
return script_dir, script_dir
5561

62+
def _should_prefer_parent_files(default_dir):
63+
if not (getattr(sys, "frozen", False) or "__compiled__" in globals()):
64+
return True
65+
bundle_markers = (
66+
os.path.join(default_dir, "python311.dll"),
67+
os.path.join(default_dir, "assets"),
68+
os.path.join(default_dir, "tcl"),
69+
)
70+
return any(os.path.exists(marker) for marker in bundle_markers)
71+
5672
def _resolve_existing_or_default(filename, default_dir):
5773
"""Return a path for config/log files, preferring existing files.
5874
@@ -63,10 +79,11 @@ def _resolve_existing_or_default(filename, default_dir):
6379
"""
6480
parent_dir = os.path.dirname(default_dir) if default_dir else ""
6581
parent_path = os.path.join(parent_dir, filename) if parent_dir else ""
82+
prefer_parent = _should_prefer_parent_files(default_dir)
6683

6784
# Prefer an existing file in the parent directory so multiple versioned
6885
# folders (or different builds) automatically share the same files.
69-
if parent_path and os.path.isfile(parent_path):
86+
if prefer_parent and parent_path and os.path.isfile(parent_path):
7087
return parent_path
7188

7289
# Fall back to any existing file in the current (exe) directory to remain
@@ -86,7 +103,7 @@ def _is_drive_root(path: str) -> bool:
86103
# e.g. "C:\\" -> tail == ""
87104
return bool(drive) and tail == ""
88105

89-
if parent_dir and not _is_drive_root(parent_dir):
106+
if prefer_parent and parent_dir and not _is_drive_root(parent_dir):
90107
return parent_path
91108

92109
# As a last resort (e.g. onefile exe placed directly in an app folder
@@ -114,10 +131,12 @@ def find_resource(filename):
114131
WAHOO_LOGO_PATH = find_resource(os.path.join("assets", "wahoo.png"))
115132
MYWHOOSH_LOGO_PATH = find_resource(os.path.join("assets", "mywhoosh.png"))
116133
TRAINERDAY_LOGO_PATH = find_resource(os.path.join("assets", "trainerday.png"))
117-
VERSION = "1.1.0"
134+
VERSION = "1.1.1"
118135
GITHUB_REPO_URL = "https://github.com/Inc21/Garmin-Connect-Auto-Uploader"
119136
VERSION_JSON_URL = "https://raw.githubusercontent.com/Inc21/Garmin-Connect-Auto-Uploader/main/version.json"
120137
LEGACY_VERSION_JSON_URL = "https://raw.githubusercontent.com/Inc21/Wahoo-and-MyWhoos-to-Garmin-Conect-Auto-Uploader/main/version.json"
138+
MIN_GARMINCONNECT_VERSION = "0.3.2"
139+
RECOMMENDED_GARMINCONNECT_VERSION = "0.3.2"
121140
LOG_FILE = _resolve_existing_or_default("garmin_uploader.log", LOG_DIR)
122141
# Upload log (single file; month separators written when month changes)
123142
UPLOAD_LOG_FILE = _resolve_existing_or_default("garmin_uploads.log", LOG_DIR)
@@ -143,6 +162,70 @@ def find_resource(filename):
143162
)
144163
logger = logging.getLogger(__name__)
145164

165+
def _version_key(version_text):
166+
parts = []
167+
for raw_part in str(version_text).split("."):
168+
digits = "".join(ch for ch in raw_part if ch.isdigit())
169+
parts.append(int(digits) if digits else 0)
170+
while len(parts) < 3:
171+
parts.append(0)
172+
return tuple(parts[:3])
173+
174+
def _get_installed_garminconnect_version():
175+
try:
176+
return get_installed_package_version("garminconnect")
177+
except PackageNotFoundError:
178+
return None
179+
except Exception as exc:
180+
logger.warning(f"Could not read installed garminconnect version: {exc}")
181+
return None
182+
183+
def ensure_supported_garminconnect():
184+
installed_version = _get_installed_garminconnect_version()
185+
if GARMINCONNECT_IMPORT_ERROR is not None:
186+
logger.error(f"garminconnect import failed: {GARMINCONNECT_IMPORT_ERROR}")
187+
messagebox.showerror(
188+
"Unsupported Garmin Login Library",
189+
"Garmin Connect support could not be loaded.\n\n"
190+
f"Installed garminconnect version: {installed_version or 'not installed'}\n"
191+
f"Import error: {GARMINCONNECT_IMPORT_ERROR}\n\n"
192+
f"This app requires garminconnect {MIN_GARMINCONNECT_VERSION} or newer.\n"
193+
f"The recommended version is {RECOMMENDED_GARMINCONNECT_VERSION}.\n\n"
194+
"Please reinstall dependencies from requirements.txt and rebuild the app."
195+
)
196+
return False
197+
if not installed_version:
198+
logger.error("garminconnect is not installed")
199+
messagebox.showerror(
200+
"Unsupported Garmin Login Library",
201+
"garminconnect is not installed.\n\n"
202+
f"This app requires garminconnect {MIN_GARMINCONNECT_VERSION} or newer.\n"
203+
f"The recommended version is {RECOMMENDED_GARMINCONNECT_VERSION}.\n\n"
204+
"Please reinstall dependencies from requirements.txt and rebuild the app."
205+
)
206+
return False
207+
logger.info(f"Detected garminconnect {installed_version}")
208+
if _version_key(installed_version) < _version_key(MIN_GARMINCONNECT_VERSION):
209+
logger.error(
210+
f"Unsupported garminconnect version detected: {installed_version}. "
211+
f"Minimum supported version is {MIN_GARMINCONNECT_VERSION}."
212+
)
213+
messagebox.showerror(
214+
"Unsupported Garmin Login Library",
215+
f"This build is using garminconnect {installed_version}.\n\n"
216+
f"Garmin Connect Uploader v{VERSION} requires garminconnect "
217+
f"{MIN_GARMINCONNECT_VERSION} or newer.\n"
218+
f"The recommended version is {RECOMMENDED_GARMINCONNECT_VERSION}.\n\n"
219+
"Please reinstall dependencies from requirements.txt and rebuild the app."
220+
)
221+
return False
222+
if _version_key(installed_version) < _version_key(RECOMMENDED_GARMINCONNECT_VERSION):
223+
logger.warning(
224+
f"garminconnect {installed_version} is supported but older than the recommended "
225+
f"version {RECOMMENDED_GARMINCONNECT_VERSION}."
226+
)
227+
return True
228+
146229
# Dedicated upload-only logger (separate file, no rotation; date markers in file)
147230
upload_logger = logging.getLogger("upload_log")
148231
upload_handler = logging.FileHandler(UPLOAD_LOG_FILE, encoding='utf-8')
@@ -2782,7 +2865,7 @@ def do_search():
27822865
btn_frame.pack(pady=20)
27832866
ttk.Button(btn_frame, text="Find Next", command=do_search, width=15).pack(side='left', padx=10)
27842867
ttk.Button(btn_frame, text="Close", command=search_window.destroy, width=15).pack(side='left', padx=10)
2785-
2868+
27862869
# Bind Enter key to search
27872870
search_entry.bind('<Return>', lambda e: do_search())
27882871

@@ -2809,38 +2892,38 @@ def open_upload_log(self):
28092892
f"Uploads log not found yet.\n\nIt will be created at:\n{UPLOAD_LOG_FILE}\n\nonce uploads occur."
28102893
)
28112894
return
2812-
2895+
28132896
log_window = tk.Toplevel(self.root)
28142897
log_window.title("Garmin Uploader - Uploads Log")
2815-
2898+
28162899
base_width, base_height = 800, 500
28172900
width = int(base_width * (self.scaling / 1.33))
28182901
height = int(base_height * (self.scaling / 1.33))
28192902
log_window.geometry(f"{width}x{height}")
2820-
2903+
28212904
text_widget = scrolledtext.ScrolledText(
28222905
log_window,
28232906
wrap=tk.WORD,
28242907
font=('Courier New', 9)
28252908
)
28262909
text_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
2827-
2910+
28282911
with open(UPLOAD_LOG_FILE, 'r', encoding='utf-8') as f:
28292912
log_content = f.read()
28302913
text_widget.insert(1.0, log_content)
28312914
text_widget.see(tk.END)
2832-
2915+
28332916
def block_edit(event):
28342917
if event.state & 0x4:
28352918
return
28362919
if event.keysym in ('Left', 'Right', 'Up', 'Down', 'Home', 'End', 'Prior', 'Next'):
28372920
return
28382921
return "break"
28392922
text_widget.bind("<Key>", block_edit)
2840-
2923+
28412924
close_btn = ttk.Button(log_window, text="Close", command=log_window.destroy)
28422925
close_btn.pack(pady=5)
2843-
2926+
28442927
logger.info("Uploads log opened by user")
28452928
except Exception as e:
28462929
log_error(f"Failed to open uploads log file: {str(e)}")
@@ -2857,6 +2940,11 @@ def main():
28572940
start_minimized = '--minimized' in sys.argv or '--startup' in sys.argv
28582941

28592942
root = tk.Tk()
2943+
root.withdraw()
2944+
if not ensure_supported_garminconnect():
2945+
root.destroy()
2946+
return
2947+
root.deiconify()
28602948
app = ConnectUploaderGUI(root)
28612949

28622950
# Auto-start monitoring if both settings are enabled and starting from startup

version.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"version": "1.1.0",
3-
"url": "https://github.com/Inc21/Wahoo-and-MyWhoos-to-Garmin-Conect-Auto-Uploader/releases/latest"
2+
"version": "1.1.1",
3+
"url": "https://github.com/Inc21/Garmin-Connect-Auto-Uploader/releases/latest"
44
}

0 commit comments

Comments
 (0)