use of ai.elimu.model.contributor.StoryBookContributionEvent in project webapp by elimu-ai.
the class StoryBookParagraphEditController method handleSubmit.
@RequestMapping(value = "/{id}", method = RequestMethod.POST)
public String handleSubmit(HttpServletRequest request, HttpSession session, @Valid StoryBookParagraph storyBookParagraph, BindingResult result, Model model) {
logger.info("handleSubmit");
Contributor contributor = (Contributor) session.getAttribute("contributor");
if (result.hasErrors()) {
model.addAttribute("storyBookParagraph", storyBookParagraph);
model.addAttribute("timeStart", System.currentTimeMillis());
return "content/storybook/paragraph/edit";
} else {
// Fetch previously stored paragraph to make it possible to check if the text was modified or not when
// storing the StoryBookContributionEvent below.
StoryBookParagraph storyBookParagraphBeforeEdit = storyBookParagraphDao.read(storyBookParagraph.getId());
storyBookParagraphDao.update(storyBookParagraph);
// Update the storybook's metadata
StoryBook storyBook = storyBookParagraph.getStoryBookChapter().getStoryBook();
storyBook.setTimeLastUpdate(Calendar.getInstance());
storyBook.setRevisionNumber(storyBook.getRevisionNumber() + 1);
storyBook.setPeerReviewStatus(PeerReviewStatus.PENDING);
storyBookDao.update(storyBook);
// Store contribution event
StoryBookContributionEvent storyBookContributionEvent = new StoryBookContributionEvent();
storyBookContributionEvent.setContributor(contributor);
storyBookContributionEvent.setTime(Calendar.getInstance());
storyBookContributionEvent.setStoryBook(storyBook);
storyBookContributionEvent.setRevisionNumber(storyBook.getRevisionNumber());
storyBookContributionEvent.setComment("Edited storybook paragraph in chapter " + (storyBookParagraph.getStoryBookChapter().getSortOrder() + 1) + " (🤖 auto-generated comment)");
if (!storyBookParagraphBeforeEdit.getOriginalText().equals(storyBookParagraph.getOriginalText())) {
storyBookContributionEvent.setParagraphTextBefore(StringUtils.abbreviate(storyBookParagraphBeforeEdit.getOriginalText(), 1000));
storyBookContributionEvent.setParagraphTextAfter(StringUtils.abbreviate(storyBookParagraph.getOriginalText(), 1000));
}
storyBookContributionEvent.setTimeSpentMs(System.currentTimeMillis() - Long.valueOf(request.getParameter("timeStart")));
storyBookContributionEvent.setPlatform(Platform.WEBAPP);
storyBookContributionEventDao.create(storyBookContributionEvent);
String contentUrl = "http://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/content/storybook/edit/" + storyBook.getId();
String embedThumbnailUrl = null;
if (storyBook.getCoverImage() != null) {
embedThumbnailUrl = "http://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/image/" + storyBook.getCoverImage().getId() + "_r" + storyBook.getCoverImage().getRevisionNumber() + "." + storyBook.getCoverImage().getImageFormat().toString().toLowerCase();
}
DiscordHelper.sendChannelMessage("Storybook paragraph edited: " + contentUrl, "\"" + storyBookContributionEvent.getStoryBook().getTitle() + "\"", "Comment: \"" + storyBookContributionEvent.getComment() + "\"", null, embedThumbnailUrl);
// Refresh the REST API cache
storyBooksJsonService.refreshStoryBooksJSONArray();
return "redirect:/content/storybook/edit/" + storyBookParagraph.getStoryBookChapter().getStoryBook().getId() + "#ch-id-" + storyBookParagraph.getStoryBookChapter().getId();
}
}
use of ai.elimu.model.contributor.StoryBookContributionEvent in project webapp by elimu-ai.
the class StoryBookPeerReviewEventCreateController method handleSubmit.
@RequestMapping(method = RequestMethod.POST)
public String handleSubmit(@RequestParam Long storyBookContributionEventId, @RequestParam Boolean approved, @RequestParam(required = false) String comment, HttpSession session) {
logger.info("handleSubmit");
Contributor contributor = (Contributor) session.getAttribute("contributor");
logger.info("storyBookContributionEventId: " + storyBookContributionEventId);
StoryBookContributionEvent storyBookContributionEvent = storyBookContributionEventDao.read(storyBookContributionEventId);
logger.info("storyBookContributionEvent: " + storyBookContributionEvent);
// Store the peer review event
StoryBookPeerReviewEvent storyBookPeerReviewEvent = new StoryBookPeerReviewEvent();
storyBookPeerReviewEvent.setContributor(contributor);
storyBookPeerReviewEvent.setStoryBookContributionEvent(storyBookContributionEvent);
storyBookPeerReviewEvent.setApproved(approved);
storyBookPeerReviewEvent.setComment(StringUtils.abbreviate(comment, 1000));
storyBookPeerReviewEvent.setTime(Calendar.getInstance());
storyBookPeerReviewEvent.setPlatform(Platform.WEBAPP);
storyBookPeerReviewEventDao.create(storyBookPeerReviewEvent);
String contentUrl = "http://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/content/storybook/edit/" + storyBookContributionEvent.getStoryBook().getId();
String embedThumbnailUrl = null;
if (storyBookContributionEvent.getStoryBook().getCoverImage() != null) {
embedThumbnailUrl = "http://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/image/" + storyBookContributionEvent.getStoryBook().getCoverImage().getId() + "_r" + storyBookContributionEvent.getStoryBook().getCoverImage().getRevisionNumber() + "." + storyBookContributionEvent.getStoryBook().getCoverImage().getImageFormat().toString().toLowerCase();
}
DiscordHelper.sendChannelMessage("Storybook peer-reviewed: " + contentUrl, "\"" + storyBookContributionEvent.getStoryBook().getTitle() + "\"", "Comment: \"" + storyBookPeerReviewEvent.getComment() + "\"", storyBookPeerReviewEvent.isApproved(), embedThumbnailUrl);
// Update the storybook's peer review status
int approvedCount = 0;
int notApprovedCount = 0;
for (StoryBookPeerReviewEvent peerReviewEvent : storyBookPeerReviewEventDao.readAll(storyBookContributionEvent)) {
if (peerReviewEvent.isApproved()) {
approvedCount++;
} else {
notApprovedCount++;
}
}
logger.info("approvedCount: " + approvedCount);
logger.info("notApprovedCount: " + notApprovedCount);
StoryBook storyBook = storyBookContributionEvent.getStoryBook();
if (approvedCount >= notApprovedCount) {
storyBook.setPeerReviewStatus(PeerReviewStatus.APPROVED);
} else {
storyBook.setPeerReviewStatus(PeerReviewStatus.NOT_APPROVED);
}
storyBookDao.update(storyBook);
return "redirect:/content/storybook/edit/" + storyBookContributionEvent.getStoryBook().getId() + "#contribution-events";
}
use of ai.elimu.model.contributor.StoryBookContributionEvent in project webapp by elimu-ai.
the class ContributorController method handleRequest.
@RequestMapping(method = RequestMethod.GET)
public String handleRequest(@PathVariable Long contributorId, Model model) {
logger.info("handleRequest");
Contributor contributor = contributorDao.read(contributorId);
model.addAttribute("contributor2", contributor);
// For contributor-summarized.jsp
model.addAttribute("storyBookContributionsCount", storyBookContributionEventDao.readCount(contributor));
model.addAttribute("storyBookPeerReviewsCount", storyBookPeerReviewEventDao.readCount(contributor));
model.addAttribute("audioContributionsCount", audioContributionEventDao.readCount(contributor));
model.addAttribute("audioPeerReviewsCount", audioPeerReviewEventDao.readCount(contributor));
model.addAttribute("wordContributionsCount", wordContributionEventDao.readCount(contributor));
model.addAttribute("wordPeerReviewsCount", wordPeerReviewEventDao.readCount(contributor));
model.addAttribute("numberContributionsCount", numberContributionEventDao.readCount(contributor));
model.addAttribute("numberPeerReviewsCount", numberPeerReviewEventDao.readCount(contributor));
// For contributor-storybooks.jsp
List<StoryBookContributionEvent> storyBookContributionEvents = storyBookContributionEventDao.readAll(contributor);
model.addAttribute("storyBookContributionEvents", storyBookContributionEvents);
model.addAttribute("storyBookPeerReviewEvents", storyBookPeerReviewEventDao.readAll(contributor));
Map<Long, List<StoryBookPeerReviewEvent>> storyBookPeerReviewEventsByContributionMap = new HashMap<>();
for (StoryBookContributionEvent storyBookContributionEvent : storyBookContributionEvents) {
storyBookPeerReviewEventsByContributionMap.put(storyBookContributionEvent.getId(), storyBookPeerReviewEventDao.readAll(storyBookContributionEvent));
}
model.addAttribute("storyBookPeerReviewEventsByContributionMap", storyBookPeerReviewEventsByContributionMap);
// For contributor-words.jsp
List<WordContributionEvent> wordContributionEvents = wordContributionEventDao.readAll(contributor);
model.addAttribute("wordContributionEvents", wordContributionEvents);
model.addAttribute("wordPeerReviewEvents", wordPeerReviewEventDao.readAll(contributor));
Map<Long, List<WordPeerReviewEvent>> wordPeerReviewEventsByContributionMap = new HashMap<>();
for (WordContributionEvent wordContributionEvent : wordContributionEvents) {
wordPeerReviewEventsByContributionMap.put(wordContributionEvent.getId(), wordPeerReviewEventDao.readAll(wordContributionEvent));
}
model.addAttribute("wordPeerReviewEventsByContributionMap", wordPeerReviewEventsByContributionMap);
// For contributor-numbers.jsp
List<NumberContributionEvent> numberContributionEvents = numberContributionEventDao.readAll(contributor);
model.addAttribute("numberContributionEvents", numberContributionEvents);
model.addAttribute("numberPeerReviewEvents", numberPeerReviewEventDao.readAll(contributor));
Map<Long, List<NumberPeerReviewEvent>> numberPeerReviewEventsByContributionMap = new HashMap<>();
for (NumberContributionEvent numberContributionEvent : numberContributionEvents) {
numberPeerReviewEventsByContributionMap.put(numberContributionEvent.getId(), numberPeerReviewEventDao.readAll(numberContributionEvent));
}
model.addAttribute("numberPeerReviewEventsByContributionMap", numberPeerReviewEventsByContributionMap);
return "content/contributor/contributor";
}
use of ai.elimu.model.contributor.StoryBookContributionEvent in project webapp by elimu-ai.
the class DbContentImportHelper method performDatabaseContentImport.
/**
* Extracts educational content from the CSV files in {@code src/main/resources/db/content_TEST/<Language>/} and
* stores it in the database.
*
* @param environment The environment from which to import the database content.
* @param language The language to use during the import.
* @param webApplicationContext Context needed to access DAOs.
*/
public synchronized void performDatabaseContentImport(Environment environment, Language language, WebApplicationContext webApplicationContext) {
logger.info("performDatabaseContentImport");
logger.info("environment: " + environment + ", language: " + language);
if (!((environment == Environment.TEST) || (environment == Environment.PROD))) {
throw new IllegalArgumentException("Database content can only be imported from the TEST environment or from the PROD environment");
}
String contentDirectoryPath = "db" + File.separator + "content_" + environment + File.separator + language.toString().toLowerCase();
logger.info("contentDirectoryPath: \"" + contentDirectoryPath + "\"");
URL contentDirectoryURL = getClass().getClassLoader().getResource(contentDirectoryPath);
logger.info("contentDirectoryURL: " + contentDirectoryURL);
if (contentDirectoryURL == null) {
logger.warn("The content directory was not found. Aborting content import.");
return;
}
File contentDirectory = new File(contentDirectoryURL.getPath());
logger.info("contentDirectory: " + contentDirectory);
contributorDao = (ContributorDao) webApplicationContext.getBean("contributorDao");
Contributor contributor = new Contributor();
contributor.setEmail("dev@elimu.ai");
contributor.setFirstName("Dev");
contributor.setLastName("Contributor");
contributor.setRoles(new HashSet<>(Arrays.asList(Role.CONTRIBUTOR, Role.EDITOR, Role.ANALYST, Role.ADMIN)));
contributor.setRegistrationTime(Calendar.getInstance());
contributorDao.create(contributor);
// Extract and import Letters from CSV file in src/main/resources/
File lettersCsvFile = new File(contentDirectory, "letters.csv");
List<Letter> letters = CsvContentExtractionHelper.getLettersFromCsvBackup(lettersCsvFile, soundDao);
logger.info("letters.size(): " + letters.size());
letterDao = (LetterDao) webApplicationContext.getBean("letterDao");
letterContributionEventDao = (LetterContributionEventDao) webApplicationContext.getBean("letterContributionEventDao");
for (Letter letter : letters) {
letterDao.create(letter);
LetterContributionEvent letterContributionEvent = new LetterContributionEvent();
letterContributionEvent.setContributor(contributor);
letterContributionEvent.setLetter(letter);
letterContributionEvent.setRevisionNumber(1);
letterContributionEvent.setTime(Calendar.getInstance());
letterContributionEvent.setTimeSpentMs((long) (Math.random() * 10) * 60000L);
letterContributionEvent.setPlatform(Platform.WEBAPP);
letterContributionEventDao.create(letterContributionEvent);
}
// Extract and import Sounds from CSV file in src/main/resources/
File soundsCsvFile = new File(contentDirectory, "sounds.csv");
List<Sound> sounds = CsvContentExtractionHelper.getSoundsFromCsvBackup(soundsCsvFile);
logger.info("sounds.size(): " + sounds.size());
soundDao = (SoundDao) webApplicationContext.getBean("soundDao");
for (Sound sound : sounds) {
soundDao.create(sound);
}
// Extract and import letter-sound correspondences in src/main/resources/
File letterToAllophioneMappingsCsvFile = new File(contentDirectory, "letter-sound-correspondences.csv");
List<LetterSoundCorrespondence> letterSoundCorrespondences = CsvContentExtractionHelper.getLetterSoundCorrespondencesFromCsvBackup(letterToAllophioneMappingsCsvFile, letterDao, soundDao, letterSoundCorrespondenceDao);
logger.info("letterSoundCorrespondences.size(): " + letterSoundCorrespondences.size());
letterSoundCorrespondenceDao = (LetterSoundCorrespondenceDao) webApplicationContext.getBean("letterSoundCorrespondenceDao");
letterSoundCorrespondenceContributionEventDao = (LetterSoundCorrespondenceContributionEventDao) webApplicationContext.getBean("letterSoundCorrespondenceContributionEventDao");
for (LetterSoundCorrespondence letterSoundCorrespondence : letterSoundCorrespondences) {
letterSoundCorrespondenceDao.create(letterSoundCorrespondence);
LetterSoundCorrespondenceContributionEvent letterSoundCorrespondenceContributionEvent = new LetterSoundCorrespondenceContributionEvent();
letterSoundCorrespondenceContributionEvent.setContributor(contributor);
letterSoundCorrespondenceContributionEvent.setLetterSoundCorrespondence(letterSoundCorrespondence);
letterSoundCorrespondenceContributionEvent.setRevisionNumber(1);
letterSoundCorrespondenceContributionEvent.setTime(Calendar.getInstance());
letterSoundCorrespondenceContributionEvent.setTimeSpentMs((long) (Math.random() * 10) * 60000L);
letterSoundCorrespondenceContributionEvent.setPlatform(Platform.WEBAPP);
letterSoundCorrespondenceContributionEventDao.create(letterSoundCorrespondenceContributionEvent);
}
// Extract and import Words from CSV file in src/main/resources/
File wordsCsvFile = new File(contentDirectory, "words.csv");
List<Word> words = CsvContentExtractionHelper.getWordsFromCsvBackup(wordsCsvFile, letterDao, soundDao, letterSoundCorrespondenceDao, wordDao);
logger.info("words.size(): " + words.size());
wordDao = (WordDao) webApplicationContext.getBean("wordDao");
wordContributionEventDao = (WordContributionEventDao) webApplicationContext.getBean("wordContributionEventDao");
for (Word word : words) {
wordDao.create(word);
WordContributionEvent wordContributionEvent = new WordContributionEvent();
wordContributionEvent.setContributor(contributor);
wordContributionEvent.setWord(word);
wordContributionEvent.setRevisionNumber(1);
wordContributionEvent.setTime(Calendar.getInstance());
wordContributionEvent.setTimeSpentMs((long) (Math.random() * 10) * 60000L);
wordContributionEvent.setPlatform(Platform.WEBAPP);
wordContributionEventDao.create(wordContributionEvent);
}
// Extract and import Numbers from CSV file in src/main/resources/
File numbersCsvFile = new File(contentDirectory, "numbers.csv");
List<Number> numbers = CsvContentExtractionHelper.getNumbersFromCsvBackup(numbersCsvFile, wordDao);
logger.info("numbers.size(): " + numbers.size());
numberDao = (NumberDao) webApplicationContext.getBean("numberDao");
numberContributionEventDao = (NumberContributionEventDao) webApplicationContext.getBean("numberContributionEventDao");
for (Number number : numbers) {
numberDao.create(number);
NumberContributionEvent numberContributionEvent = new NumberContributionEvent();
numberContributionEvent.setContributor(contributor);
numberContributionEvent.setNumber(number);
numberContributionEvent.setRevisionNumber(1);
numberContributionEvent.setTime(Calendar.getInstance());
numberContributionEvent.setTimeSpentMs((long) (Math.random() * 10) * 60000L);
numberContributionEvent.setPlatform(Platform.WEBAPP);
numberContributionEventDao.create(numberContributionEvent);
}
// Extract and import Syllables from CSV file in src/main/resources/
// TODO
// Extract and import Emojis from CSV file in src/main/resources/
File emojisCsvFile = new File(contentDirectory, "emojis.csv");
List<Emoji> emojis = CsvContentExtractionHelper.getEmojisFromCsvBackup(emojisCsvFile, wordDao);
logger.info("emojis.size(): " + emojis.size());
emojiDao = (EmojiDao) webApplicationContext.getBean("emojiDao");
for (Emoji emoji : emojis) {
emojiDao.create(emoji);
}
// Extract and import Images from CSV file in src/main/resources/
// TODO
// Extract and import Audios from CSV file in src/main/resources/
// TODO
// Extract and import StoryBooks from CSV file in src/main/resources/
File storyBooksCsvFile = new File(contentDirectory, "storybooks.csv");
List<StoryBookGson> storyBookGsons = CsvContentExtractionHelper.getStoryBooksFromCsvBackup(storyBooksCsvFile);
logger.info("storyBookGsons.size(): " + storyBookGsons.size());
storyBookDao = (StoryBookDao) webApplicationContext.getBean("storyBookDao");
storyBookChapterDao = (StoryBookChapterDao) webApplicationContext.getBean("storyBookChapterDao");
storyBookParagraphDao = (StoryBookParagraphDao) webApplicationContext.getBean("storyBookParagraphDao");
storyBookContributionEventDao = (StoryBookContributionEventDao) webApplicationContext.getBean("storyBookContributionEventDao");
for (StoryBookGson storyBookGson : storyBookGsons) {
// Convert from GSON to JPA
StoryBook storyBook = new StoryBook();
storyBook.setTitle(storyBookGson.getTitle());
storyBook.setDescription(storyBookGson.getDescription());
// TODO: storyBook.setContentLicense();
// TODO: storyBook.setAttributionUrl();
storyBook.setReadingLevel(storyBookGson.getReadingLevel());
storyBookDao.create(storyBook);
for (StoryBookChapterGson storyBookChapterGson : storyBookGson.getStoryBookChapters()) {
// Convert from GSON to JPA
StoryBookChapter storyBookChapter = new StoryBookChapter();
storyBookChapter.setStoryBook(storyBook);
storyBookChapter.setSortOrder(storyBookChapterGson.getSortOrder());
// TODO: storyBookChapter.setImage();
storyBookChapterDao.create(storyBookChapter);
for (StoryBookParagraphGson storyBookParagraphGson : storyBookChapterGson.getStoryBookParagraphs()) {
// Convert from GSON to JPA
StoryBookParagraph storyBookParagraph = new StoryBookParagraph();
storyBookParagraph.setStoryBookChapter(storyBookChapter);
storyBookParagraph.setSortOrder(storyBookParagraphGson.getSortOrder());
storyBookParagraph.setOriginalText(storyBookParagraphGson.getOriginalText());
List<String> wordsInOriginalText = WordExtractionHelper.getWords(storyBookParagraph.getOriginalText(), language);
logger.info("wordsInOriginalText.size(): " + wordsInOriginalText.size());
List<Word> paragraphWords = new ArrayList<>();
logger.info("paragraphWords.size(): " + paragraphWords.size());
for (String wordInOriginalText : wordsInOriginalText) {
logger.info("wordInOriginalText: \"" + wordInOriginalText + "\"");
wordInOriginalText = wordInOriginalText.toLowerCase();
logger.info("wordInOriginalText (lower-case): \"" + wordInOriginalText + "\"");
Word word = wordDao.readByText(wordInOriginalText);
logger.info("word: " + word);
paragraphWords.add(word);
}
storyBookParagraph.setWords(paragraphWords);
storyBookParagraphDao.create(storyBookParagraph);
}
}
StoryBookContributionEvent storyBookContributionEvent = new StoryBookContributionEvent();
storyBookContributionEvent.setContributor(contributor);
storyBookContributionEvent.setStoryBook(storyBook);
storyBookContributionEvent.setRevisionNumber(1);
storyBookContributionEvent.setTime(Calendar.getInstance());
storyBookContributionEvent.setTimeSpentMs((long) (Math.random() * 10) * 60000L);
storyBookContributionEvent.setPlatform(Platform.WEBAPP);
storyBookContributionEventDao.create(storyBookContributionEvent);
}
// Extract and import Videos from CSV file in src/main/resources/
// TODO
String analyticsDirectoryPath = "db" + File.separator + "analytics_" + environment + File.separator + language.toString().toLowerCase();
logger.info("analyticsDirectoryPath: \"" + analyticsDirectoryPath + "\"");
URL analyticsDirectoryURL = getClass().getClassLoader().getResource(analyticsDirectoryPath);
logger.info("analyticsDirectoryURL: " + analyticsDirectoryURL);
if (analyticsDirectoryURL == null) {
logger.warn("The analytics directory was not found. Aborting analytics import.");
return;
}
File analyticsDirectory = new File(analyticsDirectoryURL.getPath());
logger.info("analyticsDirectory: " + analyticsDirectory);
// Extract and import LetterLearningEvents from CSV file in src/main/resources/
// TODO
// Extract and import WordLearningEvents from CSV file in src/main/resources/
// TODO
// Extract and import StoryBookLearningEvents from CSV file in src/main/resources/
File storyBookLearningEventsCsvFile = new File(analyticsDirectory, "storybook-learning-events.csv");
applicationDao = (ApplicationDao) webApplicationContext.getBean("applicationDao");
List<StoryBookLearningEvent> storyBookLearningEvents = CsvAnalyticsExtractionHelper.getStoryBookLearningEventsFromCsvBackup(storyBookLearningEventsCsvFile, applicationDao, storyBookDao);
logger.info("storyBookLearningEvents.size(): " + storyBookLearningEvents.size());
storyBookLearningEventDao = (StoryBookLearningEventDao) webApplicationContext.getBean("storyBookLearningEventDao");
for (StoryBookLearningEvent storyBookLearningEvent : storyBookLearningEvents) {
storyBookLearningEventDao.create(storyBookLearningEvent);
}
logger.info("Content import complete");
}
use of ai.elimu.model.contributor.StoryBookContributionEvent in project webapp by elimu-ai.
the class StoryBookCreateFromEPubController method handleSubmit.
@RequestMapping(method = RequestMethod.POST)
public String handleSubmit(StoryBook storyBook, @RequestParam("bytes") MultipartFile multipartFile, BindingResult result, Model model, HttpServletRequest request, HttpSession session) throws IOException {
logger.info("handleSubmit");
Image storyBookCoverImage = null;
List<StoryBookChapter> storyBookChapters = new ArrayList<>();
List<StoryBookParagraph> storyBookParagraphs = new ArrayList<>();
if (multipartFile.isEmpty()) {
result.rejectValue("bytes", "NotNull");
} else {
String contentType = multipartFile.getContentType();
logger.info("contentType: " + contentType);
String name = multipartFile.getName();
logger.info("name: " + name);
String originalFilename = multipartFile.getOriginalFilename();
logger.info("originalFilename: " + originalFilename);
long size = multipartFile.getSize();
logger.info("size: " + size + " (" + (size / 1024) + "kB)");
byte[] ePubBytes = multipartFile.getBytes();
logger.info("ePubBytes.length: " + (ePubBytes.length / 1024 / 1024) + "MB");
List<File> filesInEPub = unzipFiles(ePubBytes, originalFilename);
logger.info("filesInEPub.size(): " + filesInEPub.size());
// Extract the ePUB's metadata from its OPF file
File opfFile = null;
for (File file : filesInEPub) {
if (file.getName().endsWith(".opf")) {
opfFile = file;
}
}
logger.info("opfFile: \"" + opfFile + "\"");
if (opfFile == null) {
throw new IllegalArgumentException("The OPF file was not found");
} else {
String title = EPubMetadataExtractionHelper.extractTitleFromOpfFile(opfFile);
logger.info("title: \"" + title + "\"");
storyBook.setTitle(title);
String description = EPubMetadataExtractionHelper.extractDescriptionFromOpfFile(opfFile);
logger.info("description: \"" + description + "\"");
if (StringUtils.isNotBlank(description)) {
logger.info("description.length(): " + description.length());
if (description.length() > 1024) {
description = description.substring(0, 1023);
}
storyBook.setDescription(description);
}
storyBookCoverImage = new Image();
String coverImageReference = EPubMetadataExtractionHelper.extractCoverImageReferenceFromOpfFile(opfFile);
logger.info("coverImageReference: " + coverImageReference);
File coverImageFile = new File(opfFile.getParent(), coverImageReference);
logger.info("coverImageFile: " + coverImageFile);
logger.info("coverImageFile.exists(): " + coverImageFile.exists());
URI coverImageUri = coverImageFile.toURI();
logger.info("coverImageUri: " + coverImageUri);
byte[] coverImageBytes = IOUtils.toByteArray(coverImageUri);
storyBookCoverImage.setBytes(coverImageBytes);
byte[] headerBytes = Arrays.copyOfRange(coverImageBytes, 0, 6);
// "GIF87a"
byte[] gifHeader87a = { 71, 73, 70, 56, 55, 97 };
// "GIF89a"
byte[] gifHeader89a = { 71, 73, 70, 56, 57, 97 };
if (Arrays.equals(gifHeader87a, headerBytes) || Arrays.equals(gifHeader89a, headerBytes)) {
storyBookCoverImage.setContentType("image/gif");
storyBookCoverImage.setImageFormat(ImageFormat.GIF);
} else if (coverImageFile.getName().toLowerCase().endsWith(".png")) {
storyBookCoverImage.setContentType("image/png");
storyBookCoverImage.setImageFormat(ImageFormat.PNG);
} else if (coverImageFile.getName().toLowerCase().endsWith(".jpg") || coverImageFile.getName().toLowerCase().endsWith(".jpeg")) {
storyBookCoverImage.setContentType("image/jpg");
storyBookCoverImage.setImageFormat(ImageFormat.JPG);
} else if (coverImageFile.getName().toLowerCase().endsWith(".gif")) {
storyBookCoverImage.setContentType("image/gif");
storyBookCoverImage.setImageFormat(ImageFormat.GIF);
}
try {
int[] dominantColor = ImageColorHelper.getDominantColor(storyBookCoverImage.getBytes());
storyBookCoverImage.setDominantColor("rgb(" + dominantColor[0] + "," + dominantColor[1] + "," + dominantColor[2] + ")");
} catch (NullPointerException ex) {
// javax.imageio.IIOException: Unsupported Image Type
}
if (storyBookCoverImage.getImageFormat() != ImageFormat.GIF) {
// Reduce size if large image
int imageWidth = ImageHelper.getWidth(coverImageBytes);
logger.info("imageWidth: " + imageWidth + "px");
if (imageWidth > ImageHelper.MINIMUM_WIDTH) {
coverImageBytes = ImageHelper.scaleImage(coverImageBytes, ImageHelper.MINIMUM_WIDTH);
storyBookCoverImage.setBytes(coverImageBytes);
}
}
}
// Extract the ePUB's chapters
File tableOfContentsFile = null;
for (File file : filesInEPub) {
if (file.getName().startsWith("toc.")) {
tableOfContentsFile = file;
}
}
logger.info("tableOfContentsFile: \"" + tableOfContentsFile + "\"");
if (tableOfContentsFile == null) {
throw new IllegalArgumentException("The TOC file was not found");
} else {
List<String> chapterReferences = null;
if (tableOfContentsFile.getName().endsWith(".xhtml")) {
// StoryBookProvider#GLOBAL_DIGITAL_LIBRARY or StoryBookProvider#LETS_READ_ASIA
chapterReferences = EPubChapterExtractionHelper.extractChapterReferencesFromTableOfContentsFile(tableOfContentsFile);
} else if (tableOfContentsFile.getName().endsWith(".ncx")) {
// StoryBookProvider#STORYWEAVER
chapterReferences = EPubChapterExtractionHelper.extractChapterReferencesFromTableOfContentsFileNcx(tableOfContentsFile);
}
logger.info("chapterReferences.size(): " + chapterReferences.size());
// Extract each chapter's image (if any) and paragraphs
for (String chapterReference : chapterReferences) {
logger.info("chapterReference: \"" + chapterReference + "\"");
File chapterFile = new File(opfFile.getParent(), chapterReference);
logger.info("chapterFile: \"" + chapterFile + "\"");
StoryBookChapter storyBookChapter = new StoryBookChapter();
storyBookChapter.setSortOrder(storyBookChapters.size());
storyBookChapters.add(storyBookChapter);
String chapterImageReference = EPubImageExtractionHelper.extractImageReferenceFromChapterFile(chapterFile);
logger.info("chapterImageReference: " + chapterImageReference);
if (StringUtils.isNotBlank(chapterImageReference)) {
File chapterImageFile = null;
if (chapterImageReference.startsWith("http://") || chapterImageReference.startsWith("https://")) {
// Download the file
URL sourceUrl = new URL(chapterImageReference);
String tmpDir = System.getProperty("java.io.tmpdir");
logger.info("tmpDir: " + tmpDir);
File tmpDirElimuAi = new File(tmpDir, "elimu-ai");
logger.info("tmpDirElimuAi: " + tmpDirElimuAi);
logger.info("tmpDirElimuAi.mkdir(): " + tmpDirElimuAi.mkdir());
chapterImageFile = new File(tmpDirElimuAi, "chapter-image");
logger.warn("Downloading image from " + sourceUrl + " and storing at " + chapterImageFile);
// 1000 milliseconds x 10
int connectionTimeout = 1000 * 10;
// 1000 milliseconds x 10
int readTimeout = 1000 * 10;
FileUtils.copyURLToFile(sourceUrl, chapterImageFile, connectionTimeout, readTimeout);
} else {
chapterImageFile = new File(chapterFile.getParent(), chapterImageReference);
}
logger.info("chapterImageFile: " + chapterImageFile);
logger.info("chapterImageFile.exists(): " + chapterImageFile.exists());
URI chapterImageUri = chapterImageFile.toURI();
logger.info("chapterImageUri: " + chapterImageUri);
byte[] chapterImageBytes = IOUtils.toByteArray(chapterImageUri);
Image chapterImage = new Image();
chapterImage.setBytes(chapterImageBytes);
byte[] headerBytes = Arrays.copyOfRange(chapterImageBytes, 0, 6);
// "GIF87a"
byte[] gifHeader87a = { 71, 73, 70, 56, 55, 97 };
// "GIF89a"
byte[] gifHeader89a = { 71, 73, 70, 56, 57, 97 };
if (Arrays.equals(gifHeader87a, headerBytes) || Arrays.equals(gifHeader89a, headerBytes)) {
chapterImage.setContentType("image/gif");
chapterImage.setImageFormat(ImageFormat.GIF);
} else if (chapterImageFile.getName().toLowerCase().endsWith(".png")) {
chapterImage.setContentType("image/png");
chapterImage.setImageFormat(ImageFormat.PNG);
} else if (chapterImageFile.getName().toLowerCase().endsWith(".jpg") || chapterImageFile.getName().toLowerCase().endsWith(".jpeg")) {
chapterImage.setContentType("image/jpg");
chapterImage.setImageFormat(ImageFormat.JPG);
} else if (chapterImageFile.getName().toLowerCase().endsWith(".gif")) {
chapterImage.setContentType("image/gif");
chapterImage.setImageFormat(ImageFormat.GIF);
}
try {
int[] dominantColor = ImageColorHelper.getDominantColor(chapterImage.getBytes());
chapterImage.setDominantColor("rgb(" + dominantColor[0] + "," + dominantColor[1] + "," + dominantColor[2] + ")");
} catch (NullPointerException ex) {
// javax.imageio.IIOException: Unsupported Image Type
}
if (chapterImage.getImageFormat() != ImageFormat.GIF) {
// Reduce size if large image
int imageWidth = ImageHelper.getWidth(chapterImageBytes);
logger.info("imageWidth: " + imageWidth + "px");
if (imageWidth > ImageHelper.MINIMUM_WIDTH) {
chapterImageBytes = ImageHelper.scaleImage(chapterImageBytes, ImageHelper.MINIMUM_WIDTH);
chapterImage.setBytes(chapterImageBytes);
}
}
storyBookChapter.setImage(chapterImage);
}
List<String> paragraphs = EPubParagraphExtractionHelper.extractParagraphsFromChapterFile(chapterFile);
logger.info("paragraphs.size(): " + paragraphs.size());
for (int i = 0; i < paragraphs.size(); i++) {
String paragraph = paragraphs.get(i);
logger.info("paragraph: \"" + paragraph + "\"");
logger.info("paragraph.length(): " + paragraph.length());
StoryBookParagraph storyBookParagraph = new StoryBookParagraph();
storyBookParagraph.setStoryBookChapter(storyBookChapter);
storyBookParagraph.setSortOrder(i);
if (paragraph.length() > 1024) {
logger.warn("Reducing the length of the paragraph to its initial 1,024 characters.");
paragraph = paragraph.substring(0, 1023);
}
storyBookParagraph.setOriginalText(paragraph);
// Note: updating the paragraph's list of Words is handled by the ParagraphWordScheduler
storyBookParagraphs.add(storyBookParagraph);
}
}
}
}
if (result.hasErrors()) {
return "content/storybook/create-from-epub";
} else {
// Store the StoryBook in the database
storyBook.setTimeLastUpdate(Calendar.getInstance());
storyBookDao.create(storyBook);
StoryBookContributionEvent storyBookContributionEvent = new StoryBookContributionEvent();
storyBookContributionEvent.setContributor((Contributor) session.getAttribute("contributor"));
storyBookContributionEvent.setTime(Calendar.getInstance());
storyBookContributionEvent.setStoryBook(storyBook);
storyBookContributionEvent.setRevisionNumber(storyBook.getRevisionNumber());
storyBookContributionEvent.setComment("Uploaded ePUB file (🤖 auto-generated comment)");
storyBookContributionEvent.setTimeSpentMs(System.currentTimeMillis() - Long.valueOf(request.getParameter("timeStart")));
storyBookContributionEvent.setPlatform(Platform.WEBAPP);
storyBookContributionEventDao.create(storyBookContributionEvent);
// Store the StoryBook's cover image in the database, and assign it to the StoryBook
storyBookCoverImage.setTitle("storybook-" + storyBook.getId() + "-cover");
imageDao.create(storyBookCoverImage);
storeImageContributionEvent(storyBookCoverImage, session, request);
storyBook.setCoverImage(storyBookCoverImage);
storyBookDao.update(storyBook);
// Store the StoryBookChapters in the database
int chapterSortOrder = 0;
for (StoryBookChapter storyBookChapter : storyBookChapters) {
storyBookChapter.setStoryBook(storyBook);
// Get the paragraphs associated with this chapter
List<StoryBookParagraph> storyBookParagraphsAssociatedWithChapter = new ArrayList<>();
for (StoryBookParagraph storyBookParagraph : storyBookParagraphs) {
if (storyBookParagraph.getStoryBookChapter().getSortOrder().equals(storyBookChapter.getSortOrder())) {
storyBookParagraphsAssociatedWithChapter.add(storyBookParagraph);
}
}
logger.info("storyBookParagraphsAssociatedWithChapter.size(): " + storyBookParagraphsAssociatedWithChapter.size());
// Exclude chapters containing book metadata
boolean isMetadata = false;
for (StoryBookParagraph storyBookParagraph : storyBookParagraphsAssociatedWithChapter) {
String originalTextLowerCase = storyBookParagraph.getOriginalText().toLowerCase();
if (originalTextLowerCase.contains("author: ") || originalTextLowerCase.contains("illustrator: ") || originalTextLowerCase.contains("translator: ") || originalTextLowerCase.contains("creative commons") || originalTextLowerCase.contains("pratham books") || originalTextLowerCase.contains("storyweaver") || originalTextLowerCase.contains("copyright page")) {
isMetadata = true;
break;
}
}
if (isMetadata) {
continue;
}
// Update the chapter's sort order (in case any of the previous chapters were excluded)
storyBookChapter.setSortOrder(chapterSortOrder);
// Store the chapter's image (if any)
Image chapterImage = storyBookChapter.getImage();
if (chapterImage != null) {
chapterImage.setTitle("storybook-" + storyBook.getId() + "-ch-" + (storyBookChapter.getSortOrder() + 1));
imageDao.create(chapterImage);
storeImageContributionEvent(chapterImage, session, request);
}
// Only store the chapter if it has an image or at least one paragraph
if ((chapterImage != null) || (!storyBookParagraphsAssociatedWithChapter.isEmpty())) {
storyBookChapterDao.create(storyBookChapter);
chapterSortOrder++;
}
// Store the chapter's paragraphs in the database
for (StoryBookParagraph storyBookParagraph : storyBookParagraphsAssociatedWithChapter) {
storyBookParagraph.setStoryBookChapter(storyBookChapter);
storyBookParagraphDao.create(storyBookParagraph);
}
}
String contentUrl = "http://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/content/storybook/edit/" + storyBook.getId();
String embedThumbnailUrl = null;
if (storyBook.getCoverImage() != null) {
embedThumbnailUrl = "http://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/image/" + storyBook.getCoverImage().getId() + "_r" + storyBook.getCoverImage().getRevisionNumber() + "." + storyBook.getCoverImage().getImageFormat().toString().toLowerCase();
}
DiscordHelper.sendChannelMessage("Storybook created (imported from ePUB): " + contentUrl, "\"" + storyBookContributionEvent.getStoryBook().getTitle() + "\"", "Comment: \"" + storyBookContributionEvent.getComment() + "\"", null, embedThumbnailUrl);
return "redirect:/content/storybook/edit/" + storyBook.getId();
}
}
Aggregations