3434import com .faforever .commons .api .elide .ElideNavigatorOnId ;
3535import com .faforever .commons .replay .ReplayDataParser ;
3636import com .faforever .commons .replay .ReplayMetadata ;
37+ import com .fasterxml .jackson .databind .util .ByteBufferBackedInputStream ;
3738import com .google .common .annotations .VisibleForTesting ;
39+ import com .google .common .cache .Cache ;
40+ import com .google .common .cache .CacheBuilder ;
3841import lombok .RequiredArgsConstructor ;
3942import lombok .extern .slf4j .Slf4j ;
43+ import org .jetbrains .annotations .NotNull ;
4044import org .springframework .beans .factory .ObjectFactory ;
4145import org .springframework .cache .annotation .Cacheable ;
4246import org .springframework .context .annotation .Lazy ;
4549import reactor .core .publisher .Mono ;
4650import reactor .util .function .Tuple2 ;
4751
48- import java .io .ByteArrayInputStream ;
4952import java .io .FileNotFoundException ;
5053import java .io .IOException ;
5154import java .net .MalformedURLException ;
5255import java .net .URL ;
56+ import java .nio .ByteBuffer ;
5357import java .nio .file .DirectoryStream ;
5458import java .nio .file .Files ;
5559import java .nio .file .Path ;
6367import java .util .Objects ;
6468import java .util .Set ;
6569import java .util .concurrent .CompletableFuture ;
70+ import java .util .concurrent .TimeUnit ;
6671import java .util .regex .Matcher ;
6772import java .util .regex .Pattern ;
6873import java .util .stream .Collectors ;
@@ -101,6 +106,10 @@ public class ReplayService {
101106 private final DataPrefs dataPrefs ;
102107 private final ObjectFactory <ReplayDownloadTask > replayDownloadTaskFactory ;
103108 private final ReplayHistoryPrefs replayHistory ;
109+ private final Cache <@ NotNull Path , @ NotNull Replay > replayCache = CacheBuilder .newBuilder ()
110+ .maximumSize (500 )
111+ .expireAfterWrite (20 , TimeUnit .MINUTES )
112+ .build ();
104113
105114 @ VisibleForTesting
106115 static Integer parseSupComVersion (ReplayDataParser parser ) {
@@ -179,16 +188,20 @@ public Mono<Tuple2<List<Replay>, Integer>> loadLocalReplayPage(int pageSize, int
179188
180189 return Mono .fromFuture (CompletableFuture .allOf (replayFutures .toArray (new CompletableFuture [0 ]))
181190 .thenApply (_ -> replayFutures .stream ()
182- .map (CompletableFuture ::join )
183- .filter (Objects ::nonNull )
184- .collect (Collectors .toList ())))
191+ .map (CompletableFuture ::join )
192+ .filter (Objects ::nonNull )
193+ .collect (Collectors .toList ())))
185194 .zipWith (Mono .just (numPages ));
186195 }
187196 }
188197
189-
190198 private CompletableFuture <Replay > tryLoadingLocalReplay (Path replayFile ) {
191199 try {
200+ final Replay cachedReplay = this .replayCache .getIfPresent (replayFile );
201+ if (cachedReplay != null ) {
202+ return CompletableFuture .completedFuture (cachedReplay );
203+ }
204+
192205 ReplayDataParser replayData = replayFileReader .parseReplay (replayFile );
193206 ReplayMetadata replayMetadata = replayData .getMetadata ();
194207
@@ -203,7 +216,10 @@ private CompletableFuture<Replay> tryLoadingLocalReplay(Path replayFile) {
203216 if (mapVersion == null ) {
204217 log .warn ("Could not find map for replay file `{}`" , replayFile );
205218 }
206- return replayMapper .map (replayData , replayFile , featuredMod , mapVersion );
219+
220+ final Replay replay = replayMapper .map (replayData , replayFile , featuredMod , mapVersion );
221+ this .replayCache .put (replayFile , replay );
222+ return replay ;
207223 }).exceptionally (throwable -> {
208224 log .warn ("Could not read replay file `{}`" , replayFile , throwable );
209225 moveCorruptedReplayFile (replayFile );
@@ -287,7 +303,10 @@ public ReplayDetails loadReplayDetails(Path path) throws IOException {
287303 List <ChatMessage > chatMessages = replayDataParser .getChatMessages ().stream ().map (replayMapper ::map ).toList ();
288304 List <GameOption > gameOptions = Stream .concat (
289305 Stream .of (new GameOption ("FAF Version" , String .valueOf (parseSupComVersion (replayDataParser )))),
290- replayDataParser .getGameOptions ().stream ().map (replayMapper ::map ).sorted (Comparator .comparing (GameOption ::key , String .CASE_INSENSITIVE_ORDER ))).toList ();
306+ replayDataParser .getGameOptions ()
307+ .stream ()
308+ .map (replayMapper ::map )
309+ .sorted (Comparator .comparing (GameOption ::key , String .CASE_INSENSITIVE_ORDER ))).toList ();
291310
292311 String mapFolderName = parseMapFolderName (replayDataParser );
293312 Map map = new Map (null , mapFolderName , 0 , null , false , null , null );
@@ -351,12 +370,13 @@ private void runOnlineReplay(int replayId) {
351370
352371 private void runFafReplayFile (Path path ) throws IOException {
353372 ReplayDataParser replayData = replayFileReader .parseReplay (path );
354- byte [] rawReplayBytes = replayData .getData ();
373+ ByteBuffer rawReplayByteBuffer = replayData .getData ();
355374
356375 Path tempSupComReplayFile = dataPrefs .getCacheDirectory ().resolve (TEMP_SCFA_REPLAY_FILE_NAME );
357376
358377 Files .createDirectories (tempSupComReplayFile .getParent ());
359- Files .copy (new ByteArrayInputStream (rawReplayBytes ), tempSupComReplayFile , StandardCopyOption .REPLACE_EXISTING );
378+ Files .copy (new ByteBufferBackedInputStream (rawReplayByteBuffer ), tempSupComReplayFile ,
379+ StandardCopyOption .REPLACE_EXISTING );
360380
361381 ReplayMetadata replayMetadata = replayData .getMetadata ();
362382 String gameType = replayMetadata .getFeaturedMod ();
@@ -445,8 +465,7 @@ public Mono<Tuple2<List<Replay>, Integer>> findByQueryWithPageCount(SearchConfig
445465 @ Cacheable (value = CacheNames .REPLAYS_SEARCH , sync = true )
446466 public Mono <Replay > findById (int id ) {
447467 ElideNavigatorOnId <Game > navigator = ElideNavigator .of (Game .class ).id (String .valueOf (id ));
448- return fafApiAccessor .getOne (navigator ).map (replayMapper ::map )
449- .cache ();
468+ return fafApiAccessor .getOne (navigator ).map (replayMapper ::map ).cache ();
450469 }
451470
452471 @ Cacheable (value = CacheNames .REPLAYS_MINE , sync = true )
@@ -467,10 +486,13 @@ private Mono<Tuple2<List<Replay>, Integer>> getReplayPage(ElideNavigatorOnCollec
467486 }
468487
469488 public Flux <LeagueScoreJournal > getLeagueScoreJournalForReplay (Replay replay ) {
470- ElideNavigatorOnCollection <com .faforever .commons .api .dto .LeagueScoreJournal > navigator = ElideNavigator .of (com .faforever .commons .api .dto .LeagueScoreJournal .class ).collection ()
471- .setFilter (qBuilder ().intNum ("gameId" ).eq (replay .id ()));
472- return fafApiAccessor .getMany (navigator )
473- .map (replayMapper ::map )
474- .cache ();
489+ ElideNavigatorOnCollection <com .faforever .commons .api .dto .LeagueScoreJournal > navigator = ElideNavigator .of (
490+ com .faforever .commons .api .dto .LeagueScoreJournal .class )
491+ .collection ()
492+ .setFilter (
493+ qBuilder ().intNum (
494+ "gameId" )
495+ .eq (replay .id ()));
496+ return fafApiAccessor .getMany (navigator ).map (replayMapper ::map ).cache ();
475497 }
476498}
0 commit comments