@@ -119,10 +119,9 @@ fn match_game_to_screenscraper(
119119 let mut redis_conn = client. redis_conn ( ) . clone ( ) ;
120120 let system_id = get_game_platform_screenscraper_id ( & game, & db_conn) . await ?;
121121
122- if let Some ( ( ) ) =
123- try_match_by_hashes ( & game, system_id, & client, & db_conn, & mut redis_conn) . await ?
124- {
125- return Ok ( ( ) ) ;
122+ match try_match_by_hashes ( & game, system_id, & client, & db_conn, & mut redis_conn) . await ? {
123+ HashPassOutcome :: Matched | HashPassOutcome :: SkippedTooMany => return Ok ( ( ) ) ,
124+ HashPassOutcome :: Missed => { }
126125 }
127126
128127 if client. is_quota_exhausted ( ) {
@@ -296,16 +295,44 @@ async fn run_rung(
296295 }
297296}
298297
298+ /// ScreenScraper indexes whole games, not file chunks. Anything above the
299+ /// cap gets a `Failed/TooManyFiles` row without an API call.
300+ const MAX_GAME_FILES_FOR_SCREENSCRAPER : usize = 5 ;
301+
302+ enum HashPassOutcome {
303+ Matched ,
304+ SkippedTooMany ,
305+ Missed ,
306+ }
307+
299308async fn try_match_by_hashes (
300309 game : & Model ,
301310 system_id : i32 ,
302311 client : & ScreenScraperClient ,
303312 db_conn : & DbConn ,
304313 redis_conn : & mut redis:: aio:: MultiplexedConnection ,
305- ) -> anyhow:: Result < Option < ( ) > > {
314+ ) -> anyhow:: Result < HashPassOutcome > {
306315 let files = get_game_files_from_game_id ( game. id , db_conn) . await ?;
307316 if files. is_empty ( ) {
308- return Ok ( None ) ;
317+ return Ok ( HashPassOutcome :: Missed ) ;
318+ }
319+ if files. len ( ) > MAX_GAME_FILES_FOR_SCREENSCRAPER {
320+ debug ! (
321+ "ScreenScraper skip for Game \" {}\" : {} files exceeds cap of {}" ,
322+ game. name,
323+ files. len( ) ,
324+ MAX_GAME_FILES_FOR_SCREENSCRAPER
325+ ) ;
326+ write_auto_match_failed (
327+ "screenscraper" ,
328+ MetadataProviderEnum :: Screenscraper ,
329+ Target :: Game ( game. id ) ,
330+ FailedMatchReasonEnum :: TooManyFiles ,
331+ db_conn,
332+ redis_conn,
333+ )
334+ . await ?;
335+ return Ok ( HashPassOutcome :: SkippedTooMany ) ;
309336 }
310337
311338 let parsed_dat = parse_name ( & game. name ) ;
@@ -317,7 +344,7 @@ async fn try_match_by_hashes(
317344
318345 for file in & files {
319346 if client. is_quota_exhausted ( ) {
320- return Ok ( None ) ;
347+ return Ok ( HashPassOutcome :: Missed ) ;
321348 }
322349 let rom_name = file. file_name . as_str ( ) ;
323350 let rom_size = file. file_size_in_bytes ;
@@ -341,7 +368,7 @@ async fn try_match_by_hashes(
341368 match found {
342369 Some ( found) => {
343370 let winning = pick_winning_hash ( & found, sha1, md5, crc, & submitted) ;
344- record_hash_match (
371+ let wrote = record_hash_match (
345372 game,
346373 & found,
347374 reason_for ( winning) ,
@@ -350,11 +377,16 @@ async fn try_match_by_hashes(
350377 redis_conn,
351378 )
352379 . await ?;
380+ if wrote {
381+ for h in & submitted {
382+ let outcome = if * h == winning { "hit" } else { "miss" } ;
383+ crate :: metrics:: record_match_rung ( "screenscraper" , rung_for ( * h) , outcome) ;
384+ }
385+ return Ok ( HashPassOutcome :: Matched ) ;
386+ }
353387 for h in & submitted {
354- let outcome = if * h == winning { "hit" } else { "miss" } ;
355- crate :: metrics:: record_match_rung ( "screenscraper" , rung_for ( * h) , outcome) ;
388+ crate :: metrics:: record_match_rung ( "screenscraper" , rung_for ( * h) , "miss" ) ;
356389 }
357- return Ok ( Some ( ( ) ) ) ;
358390 }
359391 None => {
360392 for h in & submitted {
@@ -364,7 +396,7 @@ async fn try_match_by_hashes(
364396 }
365397 }
366398
367- Ok ( None )
399+ Ok ( HashPassOutcome :: Missed )
368400}
369401
370402/// `Ord` derive is load-bearing: variants are listed weakest to strongest so
@@ -453,20 +485,22 @@ fn pick_winning_hash(
453485 best. unwrap_or ( strongest_submitted)
454486}
455487
488+ /// `Ok(false)` when the response carried no game id and no row was written;
489+ /// the caller treats it as a miss and moves on to the next file.
456490async fn record_hash_match (
457491 game : & Model ,
458492 found : & SsGame ,
459493 reason : AutomaticMatchReasonEnum ,
460494 dat_ss_regions : & [ & str ] ,
461495 db_conn : & DbConn ,
462496 redis_conn : & mut redis:: aio:: MultiplexedConnection ,
463- ) -> anyhow:: Result < ( ) > {
497+ ) -> anyhow:: Result < bool > {
464498 let Some ( found_id) = found. id else {
465499 debug ! (
466500 "ScreenScraper hash response missing id for Game \" {}\" ; skipping" ,
467501 game. name
468502 ) ;
469- return Ok ( ( ) ) ;
503+ return Ok ( false ) ;
470504 } ;
471505 debug ! (
472506 "Matched Game \" {}\" to ScreenScraper Game ID {} ({:?})" ,
@@ -487,7 +521,8 @@ async fn record_hash_match(
487521 db_conn,
488522 redis_conn,
489523 )
490- . await
524+ . await ?;
525+ Ok ( true )
491526}
492527
493528async fn get_game_platform_screenscraper_id ( game : & Model , db_conn : & DbConn ) -> anyhow:: Result < i32 > {
0 commit comments