From 928353d7f8bb3bd881cd43e77f3e422a05350a42 Mon Sep 17 00:00:00 2001 From: This-Is-Ko <52279273+This-Is-Ko@users.noreply.github.com> Date: Sun, 8 Oct 2023 00:10:00 +1100 Subject: [PATCH] Enable image generation on server --- README.md | 8 ++++ build.gradle | 2 +- .../controllers/MainController.java | 32 -------------- .../controllers/PlayerController.java | 12 +----- .../datasource/FbrefDataSource.java | 1 + .../datasource/FotmobDataSource.java | 1 + .../models/PlayerMatchPerformanceStats.java | 7 ++- .../runners/FileAccessValidationRunner.java | 43 +++++++++++++++++++ .../services/EmailService.java | 7 +-- .../services/ImageGeneratorService.java | 10 ++--- .../services/PlayerService.java | 29 ++++++++----- .../ko/footballupdater/utils/PostHelper.java | 9 ++-- 12 files changed, 92 insertions(+), 69 deletions(-) delete mode 100644 src/main/java/com/ko/footballupdater/controllers/MainController.java create mode 100644 src/main/java/com/ko/footballupdater/runners/FileAccessValidationRunner.java diff --git a/README.md b/README.md index e73c2f9..13b18dc 100644 --- a/README.md +++ b/README.md @@ -131,3 +131,11 @@ Enable service # Restart service after file changes sudo systemctl daemon-reload sudo systemctl restart football-updater + +To set up fonts for image generation, add font files to + + /usr/share/fonts/ChakraPetch-Bold.ttf + /usr/share/fonts/Nike_Ithaca.otf + +Additional reference for fonts on Linux https://medium.com/source-words/how-to-manually-install-update-and-uninstall-fonts-on-linux-a8d09a3853b0 + diff --git a/build.gradle b/build.gradle index beec618..763417a 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'com.ko' -version = '0.0.4-SNAPSHOT' +version = '0.0.5-SNAPSHOT' java { sourceCompatibility = '20' diff --git a/src/main/java/com/ko/footballupdater/controllers/MainController.java b/src/main/java/com/ko/footballupdater/controllers/MainController.java deleted file mode 100644 index adac06a..0000000 --- a/src/main/java/com/ko/footballupdater/controllers/MainController.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ko.footballupdater.controllers; - - -import com.ko.footballupdater.models.Player; -import com.ko.footballupdater.repositories.PlayerRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -@RequestMapping(path="/main") -public class MainController { - @Autowired - private PlayerRepository playerRepository; - -// @PostMapping(path="/add") -// public @ResponseBody String addNewUser (@RequestParam String name -// , @RequestParam String email) { -// -// Player n = new Player(); -// n.setName(name); -// playerRepository.save(n); -// return "Saved"; -// } - - @GetMapping(path="/all") - public @ResponseBody Iterable getAllUsers() { - return playerRepository.findAll(); - } -} \ No newline at end of file diff --git a/src/main/java/com/ko/footballupdater/controllers/PlayerController.java b/src/main/java/com/ko/footballupdater/controllers/PlayerController.java index 8473d72..cd49736 100644 --- a/src/main/java/com/ko/footballupdater/controllers/PlayerController.java +++ b/src/main/java/com/ko/footballupdater/controllers/PlayerController.java @@ -59,14 +59,4 @@ public class PlayerController { HttpStatus.BAD_REQUEST, "Updating player failed", ex); } } - - @PostMapping(path="/data-source/update") - public @ResponseBody DataSource updatePlayerDataSource(@RequestBody DataSource dataSource) { - try { - return playerService.updatePlayerDataSource(dataSource); - } catch (Exception ex) { - throw new ResponseStatusException( - HttpStatus.CONFLICT, "Unable to add player", ex); - } - } -} \ No newline at end of file +} diff --git a/src/main/java/com/ko/footballupdater/datasource/FbrefDataSource.java b/src/main/java/com/ko/footballupdater/datasource/FbrefDataSource.java index 9621d5e..be0ee84 100644 --- a/src/main/java/com/ko/footballupdater/datasource/FbrefDataSource.java +++ b/src/main/java/com/ko/footballupdater/datasource/FbrefDataSource.java @@ -111,6 +111,7 @@ public PlayerMatchPerformanceStats parsePlayerMatchData(Player player, Document } return new PlayerMatchPerformanceStats( + dataSourceSiteName, new Match(latestMatchUrl, selectedMatchDate, homeTeam, awayTeam, relevantTeam), parseIntegerOrNull(resultRow.select("td[data-stat=minutes]").text()), parseIntegerOrNull(resultRow.select("td[data-stat=goals]").text()), diff --git a/src/main/java/com/ko/footballupdater/datasource/FotmobDataSource.java b/src/main/java/com/ko/footballupdater/datasource/FotmobDataSource.java index 1fb02c4..f97e611 100644 --- a/src/main/java/com/ko/footballupdater/datasource/FotmobDataSource.java +++ b/src/main/java/com/ko/footballupdater/datasource/FotmobDataSource.java @@ -161,6 +161,7 @@ private PlayerMatchPerformanceStats checkPlayerAndParse(Player player, JsonNode } return new PlayerMatchPerformanceStats( + dataSourceSiteName, match, getStatIntegerOrDefault(topStats, "Minutes played", 0), getStatIntegerOrDefault(topStats, "Goals", 0), diff --git a/src/main/java/com/ko/footballupdater/models/PlayerMatchPerformanceStats.java b/src/main/java/com/ko/footballupdater/models/PlayerMatchPerformanceStats.java index e881eb6..c96bff2 100644 --- a/src/main/java/com/ko/footballupdater/models/PlayerMatchPerformanceStats.java +++ b/src/main/java/com/ko/footballupdater/models/PlayerMatchPerformanceStats.java @@ -5,6 +5,7 @@ @Getter public class PlayerMatchPerformanceStats { + private DataSourceSiteName dataSourceSiteName; private Match match; private Integer minutesPlayed; private Integer goals; @@ -58,7 +59,8 @@ public class PlayerMatchPerformanceStats { private Integer gkPenaltiesScoredAgainst; private Integer gkPenaltiesSaved; - public PlayerMatchPerformanceStats(Match match, Integer minutesPlayed, Integer goals, Integer assists, Integer shots, Integer shotsBlocked, Integer fouls, Integer fouled, Integer offsides, String crossingAccuracyAll, Integer dispossessed, Integer touches, String tacklingSuccessAll, Integer defensiveActions, Integer recoveries, Integer duelsWon, Integer duelsLost, Integer groundDuelsWon, Integer aerialDuelsWon, Integer chancesCreatedAll, String passingAccuracyAll, Integer passesIntoFinalThird, String carriesSuccessAll) { + public PlayerMatchPerformanceStats(DataSourceSiteName dataSourceSiteName, Match match, Integer minutesPlayed, Integer goals, Integer assists, Integer shots, Integer shotsBlocked, Integer fouls, Integer fouled, Integer offsides, String crossingAccuracyAll, Integer dispossessed, Integer touches, String tacklingSuccessAll, Integer defensiveActions, Integer recoveries, Integer duelsWon, Integer duelsLost, Integer groundDuelsWon, Integer aerialDuelsWon, Integer chancesCreatedAll, String passingAccuracyAll, Integer passesIntoFinalThird, String carriesSuccessAll) { + this.dataSourceSiteName = dataSourceSiteName; this.match = match; this.minutesPlayed = minutesPlayed; this.goals = goals; @@ -85,7 +87,8 @@ public PlayerMatchPerformanceStats(Match match, Integer minutesPlayed, Integer g } // Used by FBREF - public PlayerMatchPerformanceStats(Match match, Integer minutesPlayed, Integer goals, Integer assists, Integer penaltiesScored, Integer penaltiesWon, Integer shots, Integer shotsOnTarget, Integer yellowCards, Integer redCards, Integer fouls, Integer fouled, Integer offsides, Integer crosses, Integer touches, Integer tackles, Integer tacklesWon, Integer interceptions, Integer blocks, Float xg, Float xg_assist, Integer shotCreatingActions, Integer goalCreatingActions, Integer passesCompleted, Integer passesAttempted, Float passesSuccessPercentage, Integer progressivePasses, Integer carries, Integer progressiveCarries, Integer takesOnsAttempted, Integer takesOnsCompleted, Integer gkShotsOnTargetAgainst, Integer gkGoalsAgainst, Integer gkSaves, Float gkSavePercentage, Integer gkPenaltiesAttemptedAgainst, Integer gkPenaltiesScoredAgainst, Integer gkPenaltiesSaved) { + public PlayerMatchPerformanceStats(DataSourceSiteName dataSourceSiteName, Match match, Integer minutesPlayed, Integer goals, Integer assists, Integer penaltiesScored, Integer penaltiesWon, Integer shots, Integer shotsOnTarget, Integer yellowCards, Integer redCards, Integer fouls, Integer fouled, Integer offsides, Integer crosses, Integer touches, Integer tackles, Integer tacklesWon, Integer interceptions, Integer blocks, Float xg, Float xg_assist, Integer shotCreatingActions, Integer goalCreatingActions, Integer passesCompleted, Integer passesAttempted, Float passesSuccessPercentage, Integer progressivePasses, Integer carries, Integer progressiveCarries, Integer takesOnsAttempted, Integer takesOnsCompleted, Integer gkShotsOnTargetAgainst, Integer gkGoalsAgainst, Integer gkSaves, Float gkSavePercentage, Integer gkPenaltiesAttemptedAgainst, Integer gkPenaltiesScoredAgainst, Integer gkPenaltiesSaved) { + this.dataSourceSiteName = dataSourceSiteName; this.match = match; this.minutesPlayed = minutesPlayed; this.goals = goals; diff --git a/src/main/java/com/ko/footballupdater/runners/FileAccessValidationRunner.java b/src/main/java/com/ko/footballupdater/runners/FileAccessValidationRunner.java new file mode 100644 index 0000000..5b941f8 --- /dev/null +++ b/src/main/java/com/ko/footballupdater/runners/FileAccessValidationRunner.java @@ -0,0 +1,43 @@ +package com.ko.footballupdater.runners; + +import com.ko.footballupdater.configuration.ImageGeneratorProperies; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import java.io.File; + +@Slf4j +@Component +public class FileAccessValidationRunner implements ApplicationRunner { + + @Autowired + private ImageGeneratorProperies imageGeneratorProperies; + + @Override + public void run(ApplicationArguments args) { + validateFileAccess(); + } + + public void validateFileAccess() { + File dir = new File(imageGeneratorProperies.getInputPath()); + File[] directoryListing = dir.listFiles(); + if (directoryListing != null) { + log.info(String.format("Found %d files in input directory", directoryListing.length)); + for (File child : directoryListing) { + try { + if (child.exists() && child.canRead()) { + log.debug("File access is valid for " + child.getAbsolutePath()); + } + } catch (Exception e) { + log.warn("File access is not valid for " + child.getAbsolutePath()); + } + } + log.info("Completed input directory file access check"); + } else { + log.warn("Input directory contains no files"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ko/footballupdater/services/EmailService.java b/src/main/java/com/ko/footballupdater/services/EmailService.java index de2498c..6fec60a 100644 --- a/src/main/java/com/ko/footballupdater/services/EmailService.java +++ b/src/main/java/com/ko/footballupdater/services/EmailService.java @@ -49,9 +49,11 @@ public boolean sendEmailUpdate(List posts){ // Create email body StringBuilder emailContent = new StringBuilder(); for (InstagramPost post : posts) { - emailContent.append("############").append(post.getPlayer().getName()).append(" - ").append(post.getPlayerMatchPerformanceStats().getMatch().getDateAsFormattedString()).append("############\n\n"); + emailContent.append("############").append(post.getPlayer().getName()).append(" - ").append(post.getPlayerMatchPerformanceStats().getDataSourceSiteName().toString()).append(" - ").append(post.getPlayerMatchPerformanceStats().getMatch().getDateAsFormattedString()).append("############\n\n"); emailContent.append(PostHelper.generatePostCaption(instagramPostProperies.getVersion(), post)).append("\n\n"); - emailContent.append("Stat image(s)\n").append(PostHelper.generateS3UrlList(post)).append("\n"); + if (!post.getImagesS3Urls().isEmpty()) { + emailContent.append("Stat image(s)\n").append(PostHelper.generateS3UrlList(post)).append("\n"); + } emailContent.append("Google image search links\n").append(PostHelper.generatePostImageSearchUrl(post)).append("\n\n\n"); // Add images to attachment - config driven @@ -77,7 +79,6 @@ public boolean sendEmailUpdate(List posts){ .withTransportStrategy(TransportStrategy.SMTP_TLS) .withSessionTimeout(10 * 1000) .clearEmailValidator() - .withDebugLogging(true) .buildMailer(); mailer.validate(email); diff --git a/src/main/java/com/ko/footballupdater/services/ImageGeneratorService.java b/src/main/java/com/ko/footballupdater/services/ImageGeneratorService.java index 25bdab0..fc1bdfe 100644 --- a/src/main/java/com/ko/footballupdater/services/ImageGeneratorService.java +++ b/src/main/java/com/ko/footballupdater/services/ImageGeneratorService.java @@ -26,7 +26,7 @@ public class ImageGeneratorService { private final int STAT_Y_COORDINATE = 350; private final String BASE_IMAGE_FILE_NAME = "_base_player_stat_image.jpg"; - public void generatePlayerStatImage(InstagramPost post) { + public void generatePlayerStatImage(InstagramPost post) throws Exception { if (!imageGeneratorProperies.isEnabled()) { return; } @@ -45,12 +45,12 @@ public void generatePlayerStatImage(InstagramPost post) { int attributeCounter = 0; int createdImageCounter = 0; -// Map attributeValueMap = new HashMap<>(); for (Field field : post.getPlayerMatchPerformanceStats().getClass().getDeclaredFields()) { field.setAccessible(true); // Make the private field accessible try { Object value = field.get(post.getPlayerMatchPerformanceStats()); // Get the field's value - if (value != null && !field.getName().equals("match")) { + // Only use stat values which are populated and filter select stats if they are NOT zero + if (value != null && !field.getName().equals("match") && !field.getName().equals("dataSourceSiteName")) { List zeroValueFilter = getZeroValueFilter(); if (zeroValueFilter.contains(field.getName()) && value.equals(0)) { continue; @@ -58,7 +58,6 @@ public void generatePlayerStatImage(InstagramPost post) { // Generate proper stat name - capitalise words and spacing ImageStatEntry imageStatEntry = generateDisplayedName(field.getName(), value); -// attributeValueMap.put(field.getName(), value); graphics.drawString(imageStatEntry.getName(), statNameX, statY); graphics.drawString(imageStatEntry.getValue(), statValueX, statY); // Shift y coordinate down to next position @@ -87,6 +86,7 @@ public void generatePlayerStatImage(InstagramPost post) { saveImage(post, image, createdImageCounter); } catch (Exception ex) { log.warn(post.getPlayer().getName() + " - Error while generating stat image ", ex); + throw new Exception(post.getPlayer().getName() + " - Error while generating stat image ", ex); } } @@ -110,6 +110,7 @@ private Graphics2D setUpStatsGraphicsDefaults(BufferedImage image) { Color textColor = Color.BLACK; graphics.setFont(font); graphics.setColor(textColor); + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); return graphics; } @@ -162,5 +163,4 @@ private static List getZeroValueFilter() { zeroValueFilter.add("gkPenaltiesSaved"); return zeroValueFilter; } - } diff --git a/src/main/java/com/ko/footballupdater/services/PlayerService.java b/src/main/java/com/ko/footballupdater/services/PlayerService.java index 6142bb3..6901c28 100644 --- a/src/main/java/com/ko/footballupdater/services/PlayerService.java +++ b/src/main/java/com/ko/footballupdater/services/PlayerService.java @@ -1,7 +1,6 @@ package com.ko.footballupdater.services; import com.ko.footballupdater.models.CheckedStatus; -import com.ko.footballupdater.models.DataSource; import com.ko.footballupdater.models.DataSourceSiteName; import com.ko.footballupdater.models.InstagramPost; import com.ko.footballupdater.models.Player; @@ -9,6 +8,7 @@ import com.ko.footballupdater.repositories.PlayerRepository; import com.ko.footballupdater.repositories.UpdateStatusRepository; import com.ko.footballupdater.responses.UpdatePlayersResponse; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; +@Slf4j @Service public class PlayerService { @@ -93,10 +94,16 @@ public UpdatePlayersResponse updateDataForPlayers(List requestPlayersToU } // Generate post and caption InstagramPost post = new InstagramPost(player, playerMatchPerformanceStats); - // Generate stat images - imageGeneratorService.generatePlayerStatImage(post); - // Upload stat images to s3 - amazonS3Service.uploadtoS3(post); + try { + // Generate stat images + imageGeneratorService.generatePlayerStatImage(post); + // Upload stat images to s3 + amazonS3Service.uploadtoS3(post); + } catch (Exception e) { + // Skip if image generation or upload fails, allows future retry + log.warn(post.getPlayer().getName() + " - Unable to generate or upload image"); + continue; + } posts.add(post); } @@ -105,13 +112,14 @@ public UpdatePlayersResponse updateDataForPlayers(List requestPlayersToU return response; } + // Attempt to send email with updates boolean isEmailSent = emailService.sendEmailUpdate(posts); response.setEmailSent(isEmailSent); + // Update player checked status if email was sent if (isEmailSent) { List playersToUpdate = new ArrayList<>(); Date currentDateTime = new Date(); - // Update player checked status if email was sent for (InstagramPost post : posts) { post.getPlayer().getCheckedStatus().setLastChecked(currentDateTime); post.getPlayer().getCheckedStatus().setLatestCheckedMatchUrl(post.getPlayerMatchPerformanceStats().getMatch().getUrl()); @@ -126,9 +134,8 @@ public UpdatePlayersResponse updateDataForPlayers(List requestPlayersToU return response; } - - public DataSource updatePlayerDataSource(DataSource dataSource) { -// playerRepository.findByNameEquals(); - return dataSource; - } +// public DataSource updatePlayerDataSource(DataSource dataSource) { +//// playerRepository.findByNameEquals(); +// return dataSource; +// } } diff --git a/src/main/java/com/ko/footballupdater/utils/PostHelper.java b/src/main/java/com/ko/footballupdater/utils/PostHelper.java index f81426f..dcfe430 100644 --- a/src/main/java/com/ko/footballupdater/utils/PostHelper.java +++ b/src/main/java/com/ko/footballupdater/utils/PostHelper.java @@ -49,10 +49,11 @@ public static String generatePlayerHashtags(Player player, PlayerMatchPerformanc teamNameHashtag = "#" + playerMatchPerformanceStats.getMatch().getRelevantTeam().replaceAll(" ", ""); } return "#" + player.getName().replaceAll(" ", "") + " " + - "#upthetillies" + " " + - "#womensfootball" + " " + - "#womenssoccer" + " " + - "#woso" + " " + + "#upthetillies " + + "#matildas " + + "#womensfootball " + + "#womenssoccer" + + "#woso " + teamNameHashtag; }