1313
1414
1515def get_meowth_version () -> str :
16- """Get the current meowth package version.
17-
18- Returns:
19- Version string (e.g., "0.3.1")
20- """
16+ """Get the current meowth package version."""
2117 try :
2218 import importlib .metadata
2319 return importlib .metadata .version ("meowth" )
2420 except Exception :
25- # Fallback for development mode
26- return "0.3.1"
21+ return "0.3.2"
2722
2823
2924def get_platform_name () -> str :
30- """Get the normalized platform name for binary selection.
31-
32- Returns:
33- One of: "windows", "macos", "linux"
34- """
25+ """Get the normalized platform name for binary selection."""
3526 system = platform .system ().lower ()
3627 if system == "darwin" :
3728 return "macos"
@@ -40,16 +31,11 @@ def get_platform_name() -> str:
4031 elif system == "linux" :
4132 return "linux"
4233 else :
43- # Fallback for unknown platforms
4434 return "linux"
4535
4636
4737def get_executable_name () -> str :
48- """Get the executable name for the current platform.
49-
50- Returns:
51- "MeowthBridge.exe" on Windows, "MeowthBridge" on Unix
52- """
38+ """Get the executable name for the current platform."""
5339 return "MeowthBridge.exe" if platform .system () == "Windows" else "MeowthBridge"
5440
5541
@@ -60,12 +46,8 @@ def find_meowth_bridge() -> Path:
6046 1. Environment variable MEOWTH_BRIDGE_PATH (if set)
6147 2. Bundled binary in package (src/meowth/binaries/{platform}/)
6248 3. Development build (src/MeowthBridge/bin/Release or Debug)
63-
64- Returns:
65- Path to the MeowthBridge executable
66-
67- Raises:
68- FileNotFoundError: If MeowthBridge cannot be found
49+ 4. Cached download (~/.meowth/binaries/{platform}/)
50+ 5. Download from GitHub releases
6951 """
7052 exe_name = get_executable_name ()
7153
@@ -75,93 +57,64 @@ def find_meowth_bridge() -> Path:
7557 env_exe = Path (env_path )
7658 if env_exe .exists () and env_exe .is_file ():
7759 return env_exe
78- # If MEOWTH_BRIDGE_PATH is a directory, look for executable inside
7960 if env_exe .is_dir ():
8061 env_exe = env_exe / exe_name
8162 if env_exe .exists ():
8263 return env_exe
8364
8465 # Strategy 2: Check bundled binary in package
85- # This is where the binary will be when installed via pip
8666 package_dir = Path (__file__ ).parent
8767 platform_name = get_platform_name ()
8868 bundled_exe = package_dir / platform_name / exe_name
8969
9070 if bundled_exe .exists ():
91- # Make sure it's executable on Unix systems
9271 if platform .system () != "Windows" :
9372 bundled_exe .chmod (0o755 )
9473 return bundled_exe
9574
9675 # Strategy 3: Check development build
97- # This is for developers working on the project
98- # Look for src/MeowthBridge/bin/{Release,Debug}/net8.0/MeowthBridge
9976 project_root = Path (__file__ ).parent .parent .parent .parent
10077 meowth_bridge_dir = project_root / "src" / "MeowthBridge"
10178
102- # Try Release first, then Debug
10379 for build_config in ("Release" , "Debug" ):
10480 dev_exe = meowth_bridge_dir / "bin" / build_config / "net8.0" / exe_name
10581 if dev_exe .exists ():
10682 return dev_exe
10783
108- # Strategy 4: Check if it's in the old location (backward compatibility)
84+ # Strategy 4: Check old location (backward compatibility)
10985 old_location = project_root / "MeowthBridge" / "bin"
11086 for build_config in ("Release" , "Debug" ):
11187 old_exe = old_location / build_config / "net8.0" / exe_name
11288 if old_exe .exists ():
11389 return old_exe
11490
115- # Not found in standard locations, try downloading from GitHub
91+ # Strategy 5: Download from GitHub
11692 try :
11793 return _download_meowth_bridge ()
11894 except Exception as download_error :
119- # If download fails, raise the original error
12095 raise FileNotFoundError (
12196 f"MeowthBridge executable not found. Tried:\n "
12297 f" 1. Environment variable MEOWTH_BRIDGE_PATH: { env_path or '(not set)' } \n "
12398 f" 2. Bundled binary: { bundled_exe } \n "
124- f" 3. Development build: { meowth_bridge_dir / 'bin' / '{Release,Debug}' / 'net8.0' / exe_name } \n "
99+ f" 3. Development build: { meowth_bridge_dir / 'bin' } \n "
125100 f" 4. Download from GitHub: { download_error } \n "
126101 f"\n "
127102 f"To fix this:\n "
128- f" - If developing: Build MeowthBridge with 'dotnet build src/MeowthBridge -c Release'\n "
129- f" - If using pip: Check your internet connection or set MEOWTH_BRIDGE_PATH\n "
130- f" - Set MEOWTH_BRIDGE_PATH environment variable to the executable path"
103+ f" - Set MEOWTH_BRIDGE_PATH environment variable to the executable path\n "
104+ f" - Or check your internet connection for auto-download"
131105 ) from download_error
132106
133107
134-
135-
136108def _download_meowth_bridge () -> Path :
137- """Download MeowthBridge binary from GitHub release if not found locally.
138-
139- Returns:
140- Path to the downloaded executable
141-
142- Raises:
143- FileNotFoundError: If download fails
144- """
109+ """Download MeowthBridge ZIP from GitHub release and extract it."""
145110 exe_name = get_executable_name ()
146111 platform_name = get_platform_name ()
147-
148- # Get current version
149112 version = get_meowth_version ()
150113
151- # Determine the ZIP asset name based on platform
152- asset_name_map = {
153- "macos" : "MeowthBridge-macos.zip" ,
154- "windows" : "MeowthBridge-windows.zip" ,
155- "linux" : "MeowthBridge-linux.zip" ,
156- }
157- asset_name = asset_name_map .get (platform_name )
158- if not asset_name :
159- raise FileNotFoundError (f"Unsupported platform: { platform_name } " )
160-
161- # URL to the GitHub release
114+ asset_name = f"MeowthBridge-{ platform_name } .zip"
162115 download_url = f"https://github.com/Olcmyk/Meowth-GBA-Translator/releases/download/v{ version } /{ asset_name } "
163116
164- # Create cache directory
117+ # Cache directory
165118 cache_dir = Path .home () / ".meowth" / "binaries" / platform_name
166119 cache_dir .mkdir (parents = True , exist_ok = True )
167120
@@ -177,8 +130,8 @@ def _download_meowth_bridge() -> Path:
177130 print (f"🔽 First-time setup: Downloading MeowthBridge for { platform_name } ..." )
178131 print (f" Source: { download_url } " )
179132
133+ tmp_zip_path = None
180134 try :
181- # Download to temporary file
182135 with tempfile .NamedTemporaryFile (delete = False , suffix = '.zip' ) as tmp_file :
183136 tmp_zip_path = Path (tmp_file .name )
184137
@@ -201,6 +154,8 @@ def _download_meowth_bridge() -> Path:
201154 return exe_path
202155
203156 except urllib .error .HTTPError as e :
157+ if tmp_zip_path and tmp_zip_path .exists ():
158+ tmp_zip_path .unlink ()
204159 if e .code == 404 :
205160 raise FileNotFoundError (
206161 f"MeowthBridge binary not found in release v{ version } .\n "
@@ -209,97 +164,77 @@ def _download_meowth_bridge() -> Path:
209164 f" 1. The release hasn't been published yet\n "
210165 f" 2. The binary wasn't uploaded to the release\n \n "
211166 f"Workaround:\n "
212- f" Set MEOWTH_BRIDGE_PATH environment variable to a local binary:\n "
213- t MEOWTH_BRIDGE_PATH = / path / to / MeowthBridge "
167+ f" export MEOWTH_BRIDGE_PATH=/path/to/MeowthBridge"
214168 )
215169 else :
216170 raise FileNotFoundError (f"HTTP error { e .code } downloading from { download_url } : { e } " )
217171 except Exception as e :
172+ if tmp_zip_path and tmp_zip_path .exists ():
173+ tmp_zip_path .unlink ()
218174 raise FileNotFoundError (f"Failed to download MeowthBridge: { e } " )
219175
220176
221177def _download_with_progress_and_retry (url : str , dest : Path , max_retries : int = 3 ):
222- """Download file with progress display and retry logic.
223-
224- Args:
225- url: URL to download from
226- dest: Destination file path
227- max_retries: Maximum number of retry attempts
228-
229- Raises:
230- Exception: If download fails after all retries
231- """
178+ """Download file with progress display and retry logic."""
232179 for attempt in range (1 , max_retries + 1 ):
233180 try :
234181 _download_with_progress (url , dest )
235- return # Success
182+ return
236183 except Exception as e :
237184 if attempt < max_retries :
238185 print (f" ⚠️ Download failed (attempt { attempt } /{ max_retries } ): { e } " )
239186 print (f" 🔄 Retrying in 2 seconds..." )
240187 time .sleep (2 )
241188 else :
242- raise # Final attempt failed
189+ raise
243190
244191
245192def _download_with_progress (url : str , dest : Path ):
246- """Download file with progress display.
193+ """Download file with progress display."""
194+ tmp_path = None
195+ try :
196+ with tempfile .NamedTemporaryFile (delete = False ) as tmp_file :
197+ tmp_path = Path (tmp_file .name )
198+
199+ response = urllib .request .urlopen (url , timeout = 60 )
200+ total_size = int (response .headers .get ('content-length' , 0 ))
201+
202+ downloaded = 0
203+ chunk_size = 65536
204+ last_percent = - 1
205+
206+ with open (tmp_path , 'wb' ) as f :
207+ while True :
208+ chunk = response .read (chunk_size )
209+ if not chunk :
210+ break
211+ f .write (chunk )
212+ downloaded += len (chunk )
213+
214+ if total_size > 0 :
215+ percent = int ((downloaded / total_size ) * 100 )
216+ if percent != last_percent and percent % 10 == 0 :
217+ mb_downloaded = downloaded / (1024 * 1024 )
218+ mb_total = total_size / (1024 * 1024 )
219+ print (f" [{ percent :3d} %] { mb_downloaded :.1f} MB / { mb_total :.1f} MB" )
220+ last_percent = percent
221+
222+ shutil .move (str (tmp_path ), str (dest ))
247223
248- Args:
249- url: URL to download from
250- dest: Destination file path
251- """
252- with tempfile .NamedTemporaryFile (delete = False ) as tmp_file :
253- tmp_path = Path (tmp_file .name )
224+ except Exception :
225+ if tmp_path and tmp_path .exists ():
226+ tmp_path .unlink ()
227+ raise
254228
255- try :
256- response = urllib .request .urlopen (url , timeout = 30 )
257- total_size = int (response .headers .get ('content-length' , 0 ))
258-
259- downloaded = 0
260- chunk_size = 8192
261- last_percent = - 1
262-
263- with open (tmp_path , 'wb' ) as f :
264- while True :
265- chunk = response .read (chunk_size )
266- if not chunk :
267- break
268- f .write (chunk )
269- downloaded += len (chunk )
270-
271- # Show progress
272- if total_size > 0 :
273- percent = int ((downloaded / total_size ) * 100 )
274- if percent != last_percent and percent % 10 == 0 :
275- mb_downloaded = downloaded / (1024 * 1024 )
276- mb_total = total_size / (1024 * 1024 )
277- print (f" [{ percent :3d} %] { mb_downloaded :.1f} MB / { mb_total :.1f} MB" )
278- last_percent = percent
279-
280- # Move to final destination
281- shutil .move (str (tmp_path ), str (dest ))
282229
283- except Exception as e :
284- # Clean up temp file on error
285- if tmp_path .exists ():
286- tmp_path .unlink ()
287- raise
288- """Get information about the MeowthBridge binary.
289-
290- Returns:
291- Dictionary with binary information:
292- - path: Path to the executable
293- - platform: Platform name (windows/macos/linux)
294- - source: Where the binary was found (env/bundled/dev)
295- - exists: Whether the binary exists
296- """
230+ def get_binary_info () -> dict :
231+ """Get information about the MeowthBridge binary."""
297232 try :
298233 exe_path = find_meowth_bridge ()
299-
300- # Determine source
301234 if os .environ .get ("MEOWTH_BRIDGE_PATH" ):
302235 source = "environment"
236+ elif ".meowth" in str (exe_path ):
237+ source = "downloaded"
303238 elif "binaries" in str (exe_path ):
304239 source = "bundled"
305240 else :
0 commit comments