1010from ..exceptions import FileNotFoundError
1111
1212
13- def find_file (client , name , deduplicate , search_alternatives ):
13+ def find_file (client , name , deduplicate , search_alternatives , search_upgrades = False ):
1414 # Main search function to find a software file.
1515 # It performs a direct search and, if requested, a fuzzy search for alternatives.
1616 # Returns a dictionary with file details.
1717 alternative_found = False
18+ search_method = 'exact'
1819
1920 # First, attempt a direct search for the exact filename.
2021 software_search = _search_software (client , name )
@@ -89,7 +90,7 @@ def find_file(client, name, deduplicate, search_alternatives):
8990 )
9091
9192 try :
92- software_fuzzy_found = _search_software_fuzzy (client , name )
93+ software_fuzzy_found = _search_software_fuzzy (client , name , search_upgrades )
9394 except Exception as e :
9495 # Handle cases where API returns errors due to overly broad queries
9596 if 'RetryError' in str (type (e ).__name__ ) or '500 error' in str (e ):
@@ -105,7 +106,7 @@ def find_file(client, name, deduplicate, search_alternatives):
105106 # Re-raise other exceptions
106107 raise
107108
108- software_fuzzy_filtered , suggested_filename = _filter_fuzzy_search (software_fuzzy_found , name )
109+ software_fuzzy_filtered , suggested_filename , search_method = _filter_fuzzy_search (software_fuzzy_found , name , search_upgrades )
109110 if len (software_fuzzy_filtered ) == 0 :
110111 raise FileNotFoundError (
111112 f'File "{ name } " is not available '
@@ -134,8 +135,8 @@ def find_file(client, name, deduplicate, search_alternatives):
134135 last_option = software_search_alternatives_filtered [- 1 ]['Title' ]
135136
136137 raise FileNotFoundError (
137- f'More than one alternative was found: '
138- f'{ ", " .join (names )} . \n '
138+ f'More than one alternative was found: { alternatives_count } files \n '
139+ f'Files: { ", " .join (names )} \n '
139140 f'Please use a more specific filename '
140141 f'or set deduplicate parameter.\n '
141142 f'Options for deduplicate:\n '
@@ -163,7 +164,8 @@ def find_file(client, name, deduplicate, search_alternatives):
163164 last_option = software_filtered [- 1 ]['Title' ]
164165
165166 raise FileNotFoundError (
166- f'More than one result was found: { ", " .join (names )} .\n '
167+ f'More than one result was found: { files_count } files\n '
168+ f'Files: { ", " .join (names )} \n '
167169 f'Please use the correct full filename '
168170 f'or set deduplicate parameter.\n '
169171 f'Options for deduplicate:\n '
@@ -181,7 +183,8 @@ def find_file(client, name, deduplicate, search_alternatives):
181183 return {
182184 'download_link' : software_found ['DownloadDirectLink' ],
183185 'filename' : _get_valid_filename (software_found ),
184- 'alternative_found' : alternative_found
186+ 'alternative_found' : alternative_found ,
187+ 'search_method' : search_method
185188 }
186189
187190
@@ -207,7 +210,7 @@ def _search_software(client, keyword):
207210 return results
208211
209212
210- def _search_software_fuzzy (client , query ):
213+ def _search_software_fuzzy (client , query , search_upgrades = False ):
211214 # Executes a fuzzy search to find alternative versions.
212215 # Strategy: Try prefix search first (more specific), fallback to ID search if needed.
213216 filename_base = os .path .splitext (query )[0 ]
@@ -218,7 +221,7 @@ def _search_software_fuzzy(client, query):
218221
219222 # Extract ID and prepare suggested filename prefix
220223 filename_id = filename_base .split ('-' )[- 1 ]
221- suggested_filename , suggested_filename_next , suggested_filename_base = _prepare_search_filename (query )
224+ suggested_filename , suggested_filename_next , suggested_filename_base = _prepare_search_filename (query , search_upgrades )
222225 has_wildcard = '*' in query
223226
224227 fuzzy_results = []
@@ -233,8 +236,12 @@ def _search_software_fuzzy(client, query):
233236 if f'-{ filename_id } ' in r .get ('Title' , '' ):
234237 fuzzy_results .append (_remove_useless_keys (r ))
235238
239+ # Check if prefix search hit the 50-result limit (may have missed results)
240+ # If yes, fallback to paginated ID search to ensure completeness
241+ if len (results ) >= 50 :
242+ fuzzy_results = [] # Clear partial results, will use paginated ID search below
236243 # If empty and suggested_filename_next exists, try incremented version
237- if len (fuzzy_results ) == 0 and suggested_filename_next :
244+ elif len (fuzzy_results ) == 0 and suggested_filename_next :
238245 results = _search_software (client , suggested_filename_next )
239246 for r in results :
240247 if f'-{ filename_id } ' in r .get ('Title' , '' ):
@@ -266,8 +273,11 @@ def _search_software_fuzzy(client, query):
266273 return fuzzy_results
267274
268275
269- def _filter_fuzzy_search (fuzzy_results , filename ):
276+ def _filter_fuzzy_search (fuzzy_results , filename , search_upgrades = False ):
270277 # Filters fuzzy search output using the original filename.
278+ # Returns: (sorted_results, suggested_filename, search_method)
279+ search_method = 'alternative_default' # Default assumption
280+
271281 if '*' in filename :
272282 prefix , suffix = filename .split ('*' )
273283 suffix_base = os .path .splitext (suffix )[0 ]
@@ -277,7 +287,7 @@ def _filter_fuzzy_search(fuzzy_results, filename):
277287 ]
278288 suggested_filename = prefix
279289 else :
280- suggested_filename , suggested_filename_next , suggested_filename_base = _prepare_search_filename (filename )
290+ suggested_filename , suggested_filename_next , suggested_filename_base = _prepare_search_filename (filename , search_upgrades )
281291
282292 # Create result list with same version if available.
283293 fuzzy_results_filtered = [
@@ -294,6 +304,7 @@ def _filter_fuzzy_search(fuzzy_results, filename):
294304 # Update return suggested filename to incremented version if alternatives are found with it.
295305 if len (fuzzy_results_filtered ) > 0 :
296306 suggested_filename = suggested_filename_next
307+ search_method = 'alternative_increment'
297308
298309 # Attempt to create result list with broader base prefix.
299310 if len (fuzzy_results_filtered ) == 0 and suggested_filename_base :
@@ -304,17 +315,18 @@ def _filter_fuzzy_search(fuzzy_results, filename):
304315 # Update return suggested filename to base if alternatives are found with it.
305316 if len (fuzzy_results_filtered ) > 0 :
306317 suggested_filename = suggested_filename_base
318+ search_method = 'alternative_base'
307319
308320 fuzzy_results_sorted = _sort_fuzzy_results (fuzzy_results_filtered )
309- return fuzzy_results_sorted , suggested_filename
321+ return fuzzy_results_sorted , suggested_filename , search_method
310322
311323
312- def _prepare_search_filename (filename ):
324+ def _prepare_search_filename (filename , search_upgrades = False ):
313325 # Prepares suggested search keywords for known products.
314326 # Returns triplet: (suggested, suggested_next, suggested_base)
315327 # - suggested: exact version match
316328 # - suggested_next: incremented version (safe)
317- # - suggested_base: broader prefix (only for known safe files)
329+ # - suggested_base: broader prefix (only for known safe files or when search_upgrades=True )
318330
319331 # Filename without extension.
320332 filename_base = os .path .splitext (filename )[0 ]
@@ -345,29 +357,44 @@ def _prepare_search_filename(filename):
345357 return suggested , _increment_last_digits (suggested ), None
346358
347359 # Revision version will be kept to ensure correct component versions.
348- # Example: IMDB_SERVER20_067_4-80002046.SAR (Rev 67) returns (IMDB_SERVER20_067, None, None)
349- # Example: IMDB_AFL20_077_0-80002045.SAR (Rev 77) returns (IMDB_AFL20_077, None, None)
350- # Example: IMDB_AFL100_102P_41-10012328.SAR (Rev 102) returns (IMDB_AFL100_102, None, None)
351- # Example: IMDB_LCAPPS_122P_3300-20010426.SAR (Rev 122) returns (IMDB_LCAPPS_122, None, None)
352- # Example: IMDB_LCAPPS_2067P_400-80002183.SAR (Rev 67) returns (IMDB_LCAPPS_2067, None, None)
360+ # Example: IMDB_SERVER20_067_4-80002046.SAR returns (IMDB_SERVER20_067, None, None)
361+ # With search_upgrades: (IMDB_SERVER20_067, IMDB_SERVER20_06, IMDB_SERVER20_)
362+ # Example: IMDB_AFL20_077_0-80002045.SAR returns (IMDB_AFL20_077, None, None)
363+ # With search_upgrades: (IMDB_AFL20_077, IMDB_AFL20_07, IMDB_AFL20_)
353364 elif filename_base .startswith (('IMDB_SERVER' , 'IMDB_AFL' , 'IMDB_LCAPPS_1' , 'IMDB_LCAPPS_2' )):
354365 # Remove P from the 3rd element (index 2) to improve fuzzy search.
355366 if len (filename_parts ) > 2 :
356367 filename_parts [2 ] = filename_parts [2 ].rstrip ('Pp' )
357368 # Re-join the first three elements -> "IMDB_AFL100_102"
358369 suggested = "_" .join (filename_parts [:3 ])
370+ if search_upgrades :
371+ # Extract base prefix (e.g., IMDB_SERVER20_)
372+ suggested_base = "_" .join (filename_parts [:2 ]) + '_'
373+ # For 3-digit revisions, return prefix without last digit to find closest revision
374+ # Example: IMDB_SERVER20_077 → IMDB_SERVER20_07 catches 070-079 within same SPS 07.
375+ suggested_next = _increment_last_digits (suggested )
376+ if len (filename_parts ) > 2 and len (filename_parts [2 ]) >= 3 :
377+ suggested_next = "_" .join (filename_parts [:2 ]) + '_' + filename_parts [2 ][:2 ]
378+ return suggested , suggested_next , suggested_base
359379 return suggested , None , None
360380
361381 # Example: IMDB_CLIENT20_021_31-80002082.SAR returns (IMDB_CLIENT20_021, IMDB_CLIENT20_022, None)
382+ # With search_upgrades: (IMDB_CLIENT20_021, IMDB_CLIENT20_022, IMDB_CLIENT20_)
362383 elif filename_base .startswith ('IMDB_CLIENT' ):
363384 if len (filename_parts ) > 2 :
364385 filename_parts [2 ] = filename_parts [2 ].rstrip ('Pp' )
365386 suggested = "_" .join (filename_parts [:3 ])
387+ if search_upgrades :
388+ suggested_base = "_" .join (filename_parts [:2 ]) + '_'
389+ return suggested , _increment_last_digits (suggested ), suggested_base
366390 return suggested , _increment_last_digits (suggested ), None
367391
368392 # Example: SAPEXE_100-80005374.SAR returns (SAPEXE_100, SAPEXE_101, None)
393+ # With search_upgrades: (SAPEXE_100, SAPEXE_101, SAPEXE_)
369394 elif filename_base .startswith ('SAPEXE' ):
370395 suggested = filename_main
396+ if search_upgrades :
397+ return suggested , _increment_last_digits (suggested ), 'SAPEXE_'
371398 return suggested , _increment_last_digits (suggested ), None
372399
373400 # Example: SAPHANACOCKPIT02_0-70002300.SAR returns (SAPHANACOCKPIT02, SAPHANACOCKPIT03, None)
0 commit comments