diff options
Diffstat (limited to 'src/main/java/eu/olli/cowlection/search')
4 files changed, 0 insertions, 871 deletions
diff --git a/src/main/java/eu/olli/cowlection/search/GuiDateField.java b/src/main/java/eu/olli/cowlection/search/GuiDateField.java deleted file mode 100644 index bb08a02..0000000 --- a/src/main/java/eu/olli/cowlection/search/GuiDateField.java +++ /dev/null @@ -1,37 +0,0 @@ -package eu.olli.cowlection.search; - -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.GuiTextField; - -import java.time.LocalDate; -import java.time.format.DateTimeParseException; - -class GuiDateField extends GuiTextField { - GuiDateField(int componentId, FontRenderer fontrendererObj, int x, int y, int width, int height) { - super(componentId, fontrendererObj, x, y, width, height); - } - - LocalDate getDate() { - try { - return LocalDate.parse(this.getText()); - } catch (DateTimeParseException e) { - return LocalDate.now(); - } - } - - boolean validateDate() { - try { - LocalDate localDate = LocalDate.parse(this.getText()); - if (localDate.isAfter(LocalDate.now()) || localDate.isBefore(LocalDate.ofYearDay(2009, 1))) { - // searching for things written in the future isn't possible (yet). It is also not possible to perform a search before the existence of mc. - setTextColor(0xFFFF3333); - return false; - } - } catch (DateTimeParseException e) { - setTextColor(0xFFFF3333); - return false; - } - setTextColor(0xFFFFFF); - return true; - } -} diff --git a/src/main/java/eu/olli/cowlection/search/GuiSearch.java b/src/main/java/eu/olli/cowlection/search/GuiSearch.java deleted file mode 100644 index d693e59..0000000 --- a/src/main/java/eu/olli/cowlection/search/GuiSearch.java +++ /dev/null @@ -1,603 +0,0 @@ -package eu.olli.cowlection.search; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.mojang.realmsclient.util.Pair; -import eu.olli.cowlection.Cowlection; -import eu.olli.cowlection.config.MooConfig; -import eu.olli.cowlection.data.LogEntry; -import eu.olli.cowlection.util.Utils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.*; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.util.ChatComponentText; -import net.minecraft.util.EnumChatFormatting; -import net.minecraft.util.IChatComponent; -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.client.GuiScrollingList; -import net.minecraftforge.fml.client.config.GuiButtonExt; -import net.minecraftforge.fml.client.config.GuiCheckBox; -import net.minecraftforge.fml.client.config.GuiUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.apache.commons.lang3.tuple.ImmutableTriple; -import org.lwjgl.input.Keyboard; -import org.lwjgl.opengl.GL11; - -import java.awt.*; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.reflect.Field; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.*; -import java.util.concurrent.*; -import java.util.stream.Collectors; -import java.util.zip.GZIPInputStream; - -public class GuiSearch extends GuiScreen { - private static final String SEARCH_QUERY_PLACE_HOLDER = "Search for..."; - private final File mcLogOutputFile; - /** - * @see Executors#newCachedThreadPool() - */ - private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, - 60L, TimeUnit.SECONDS, - new LinkedBlockingQueue<>(), new ThreadFactoryBuilder().setNameFormat(Cowlection.MODID + "-logfilesearcher-%d").build()); - // data - private String searchQuery; - private boolean chatOnly; - private boolean matchCase; - private boolean removeFormatting; - /** - * Cached results are required after resizing the client - */ - private List<LogEntry> searchResults; - private LocalDate dateStart; - private LocalDate dateEnd; - - // gui elements - private GuiButton buttonSearch; - private GuiButton buttonClose; - private GuiButton buttonHelp; - private GuiCheckBox checkboxChatOnly; - private GuiCheckBox checkboxMatchCase; - private GuiCheckBox checkboxRemoveFormatting; - private GuiTextField fieldSearchQuery; - private GuiDateField fieldDateStart; - private GuiDateField fieldDateEnd; - private SearchResults guiSearchResults; - private List<GuiTooltip> guiTooltips; - private boolean isSearchInProgress; - private String analyzedFiles; - private String analyzedFilesWithHits; - private boolean areEntriesSearchResults; - - public GuiSearch(File configDirectory) { - this.mcLogOutputFile = new File(configDirectory, "mc-log.txt"); - try { - mcLogOutputFile.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - } - this.searchQuery = SEARCH_QUERY_PLACE_HOLDER; - this.searchResults = new ArrayList<>(); - this.dateStart = MooConfig.calculateStartDate(); - this.dateEnd = LocalDate.now(); - this.chatOnly = true; - } - - /** - * Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the - * window resizes, the buttonList is cleared beforehand. - */ - @Override - public void initGui() { - this.guiTooltips = new ArrayList<>(); - - this.fieldSearchQuery = new GuiTextField(42, this.fontRendererObj, this.width / 2 - 100, 13, 200, 20); - this.fieldSearchQuery.setMaxStringLength(255); - this.fieldSearchQuery.setText(searchQuery); - if (SEARCH_QUERY_PLACE_HOLDER.equals(searchQuery)) { - this.fieldSearchQuery.setFocused(true); - this.fieldSearchQuery.setSelectionPos(0); - } - - // date field: start - this.fieldDateStart = new GuiDateField(50, this.fontRendererObj, this.width / 2 + 110, 15, 70, 15); - this.fieldDateStart.setText(dateStart.toString()); - addTooltip(fieldDateStart, Arrays.asList(EnumChatFormatting.YELLOW + "Start date", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Format: " + EnumChatFormatting.RESET + "year-month-day")); - // date field: end - this.fieldDateEnd = new GuiDateField(51, this.fontRendererObj, this.width / 2 + 110, 35, 70, 15); - this.fieldDateEnd.setText(dateEnd.toString()); - addTooltip(fieldDateEnd, Arrays.asList(EnumChatFormatting.YELLOW + "End date", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Format: " + EnumChatFormatting.RESET + "year-month-day")); - - // close - this.buttonList.add(this.buttonClose = new GuiButtonExt(0, this.width - 25, 3, 22, 20, EnumChatFormatting.RED + "X")); - addTooltip(buttonClose, Arrays.asList(EnumChatFormatting.RED + "Close search interface", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC")); - // help - this.buttonList.add(this.buttonHelp = new GuiButtonExt(1, this.width - 25 - 25, 3, 22, 20, "?")); - addTooltip(buttonHelp, Collections.singletonList(EnumChatFormatting.YELLOW + "Show help")); - - // chatOnly - this.buttonList.add(this.checkboxChatOnly = new GuiCheckBox(21, this.width / 2 - 100, 35, " Chatbox only", chatOnly)); - addTooltip(checkboxChatOnly, Collections.singletonList(EnumChatFormatting.YELLOW + "Should " + EnumChatFormatting.GOLD + "only " + EnumChatFormatting.YELLOW + "results that have " + EnumChatFormatting.GOLD + "appeared in the chat box " + EnumChatFormatting.YELLOW + "be displayed?\n" - + EnumChatFormatting.GRAY + "For example, this " + EnumChatFormatting.WHITE + "excludes error messages" + EnumChatFormatting.GRAY + " but still " + EnumChatFormatting.WHITE + "includes messages sent by a server" + EnumChatFormatting.GRAY + ".")); - // matchCase - this.buttonList.add(this.checkboxMatchCase = new GuiCheckBox(20, this.width / 2 - 100, 45, " Match case", matchCase)); - addTooltip(checkboxMatchCase, Collections.singletonList(EnumChatFormatting.YELLOW + "Should the search be " + EnumChatFormatting.GOLD + "case-sensitive" + EnumChatFormatting.YELLOW + "?")); - // removeFormatting - this.buttonList.add(this.checkboxRemoveFormatting = new GuiCheckBox(22, this.width / 2 - 100, 55, " Remove formatting", removeFormatting)); - addTooltip(checkboxRemoveFormatting, Collections.singletonList(EnumChatFormatting.YELLOW + "Should " + EnumChatFormatting.GOLD + "formatting " + EnumChatFormatting.YELLOW + "and " + EnumChatFormatting.GOLD + "color codes " + EnumChatFormatting.YELLOW + "be " + EnumChatFormatting.GOLD + "removed " + EnumChatFormatting.YELLOW + "from the search results?")); - // search - this.buttonList.add(this.buttonSearch = new GuiButtonExt(100, this.width / 2 + 40, 40, 60, 20, "Search")); - - this.guiSearchResults = new SearchResults(70); - this.guiSearchResults.setResults(searchResults); - - this.setIsSearchInProgress(isSearchInProgress); - - boolean isStartDateValid = fieldDateStart.validateDate(); - boolean isEndDateValid = fieldDateEnd.validateDate(); - this.buttonSearch.enabled = !isSearchInProgress && this.fieldSearchQuery.getText().trim().length() > 1 && !this.fieldSearchQuery.getText().startsWith(SEARCH_QUERY_PLACE_HOLDER) && isStartDateValid && isEndDateValid && !dateStart.isAfter(dateEnd); - - if (isStartDateValid && isEndDateValid && dateStart.isAfter(dateEnd)) { - fieldDateStart.setTextColor(0xFFDD3333); - fieldDateEnd.setTextColor(0xFFCC3333); - } - } - - private <T extends Gui> void addTooltip(T field, List<String> tooltip) { - GuiTooltip guiTooltip = new GuiTooltip(field, tooltip); - this.guiTooltips.add(guiTooltip); - } - - @Override - public void updateScreen() { - fieldSearchQuery.updateCursorCounter(); - fieldDateStart.updateCursorCounter(); - fieldDateEnd.updateCursorCounter(); - } - - @Override - protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { - // allow clicks on 'close' button even while a search is in progress - super.mouseClicked(mouseX, mouseY, mouseButton); - if (isSearchInProgress) { - // search in progress, abort - return; - } - fieldSearchQuery.mouseClicked(mouseX, mouseY, mouseButton); - fieldDateStart.mouseClicked(mouseX, mouseY, mouseButton); - fieldDateEnd.mouseClicked(mouseX, mouseY, mouseButton); - } - - @Override - protected void keyTyped(char typedChar, int keyCode) throws IOException { - if (isSearchInProgress && keyCode != Keyboard.KEY_ESCAPE) { - // search in progress, don't process key typed - but allow escape to exit gui - return; - } - if (dateStart.isBefore(dateEnd)) { - fieldDateStart.setTextColor(0xFFFFFFFF); - fieldDateEnd.setTextColor(0xFFFFFFFF); - } - if (keyCode == Keyboard.KEY_RETURN && this.fieldSearchQuery.isFocused()) { - // perform search - actionPerformed(buttonSearch); - } else if (this.fieldSearchQuery.textboxKeyTyped(typedChar, keyCode)) { - searchQuery = this.fieldSearchQuery.getText(); - } else if (this.fieldDateStart.textboxKeyTyped(typedChar, keyCode)) { - if (fieldDateStart.validateDate()) { - dateStart = fieldDateStart.getDate(); - } - } else if (this.fieldDateEnd.textboxKeyTyped(typedChar, keyCode)) { - if (fieldDateEnd.validateDate()) { - dateEnd = fieldDateEnd.getDate(); - } - } else if (GuiScreen.isKeyComboCtrlA(keyCode)) { - // copy all search results - String searchResults = guiSearchResults.getAllSearchResults(); - if (!searchResults.isEmpty()) { - GuiScreen.setClipboardString(searchResults); - } - } else if (GuiScreen.isKeyComboCtrlC(keyCode)) { - // copy current selected entry - LogEntry selectedSearchResult = guiSearchResults.getSelectedSearchResult(); - if (selectedSearchResult != null) { - GuiScreen.setClipboardString(EnumChatFormatting.getTextWithoutFormattingCodes(selectedSearchResult.getMessage())); - } - } else if (keyCode == Keyboard.KEY_C && isCtrlKeyDown() && isShiftKeyDown() && !isAltKeyDown()) { - // copy current selected entry with formatting codes - LogEntry selectedSearchResult = guiSearchResults.getSelectedSearchResult(); - if (selectedSearchResult != null) { - GuiScreen.setClipboardString(selectedSearchResult.getMessage()); - } - } else { - if (keyCode == Keyboard.KEY_ESCAPE) { - guiSearchResults = null; - } - super.keyTyped(typedChar, keyCode); - } - - boolean isStartDateValid = fieldDateStart.validateDate(); - boolean isEndDateValid = fieldDateEnd.validateDate(); - this.buttonSearch.enabled = !isSearchInProgress && searchQuery.trim().length() > 1 && !searchQuery.startsWith(SEARCH_QUERY_PLACE_HOLDER) && isStartDateValid && isEndDateValid && !dateStart.isAfter(dateEnd); - - if (isStartDateValid && isEndDateValid && dateStart.isAfter(dateEnd)) { - fieldDateStart.setTextColor(0xFFDD3333); - fieldDateEnd.setTextColor(0xFFCC3333); - } - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - this.drawDefaultBackground(); - this.drawCenteredString(this.fontRendererObj, EnumChatFormatting.BOLD + "Minecraft Log Search", this.width / 2, 2, 0xFFFFFF); - this.fieldSearchQuery.drawTextBox(); - this.fieldDateStart.drawTextBox(); - this.fieldDateEnd.drawTextBox(); - this.guiSearchResults.drawScreen(mouseX, mouseY, partialTicks); - - super.drawScreen(mouseX, mouseY, partialTicks); - - for (GuiTooltip guiTooltip : guiTooltips) { - if (guiTooltip.checkHover(mouseX, mouseY)) { - drawHoveringText(guiTooltip.getText(), mouseX, mouseY, 300); - // only one tooltip can be displayed at a time: break! - break; - } - } - } - - @Override - protected void actionPerformed(GuiButton button) throws IOException { - if (button == this.buttonClose && button.enabled) { - guiSearchResults = null; - this.mc.setIngameFocus(); - } - if (isSearchInProgress || !button.enabled) { - return; - } - if (button == this.buttonSearch) { - setIsSearchInProgress(true); - - executorService.execute(() -> { - try { - ImmutableTriple<Integer, Integer, List<LogEntry>> searchResultsData = new LogFilesSearcher().searchFor(this.fieldSearchQuery.getText(), checkboxChatOnly.isChecked(), checkboxMatchCase.isChecked(), checkboxRemoveFormatting.isChecked(), dateStart, dateEnd); - this.searchResults = searchResultsData.right; - this.analyzedFiles = "Analyzed files: " + EnumChatFormatting.WHITE + searchResultsData.left; - this.analyzedFilesWithHits = "Files with hits: " + EnumChatFormatting.WHITE + searchResultsData.middle; - if (this.searchResults.isEmpty()) { - this.searchResults.add(new LogEntry(EnumChatFormatting.ITALIC + "No results")); - areEntriesSearchResults = false; - } else { - areEntriesSearchResults = true; - } - } catch (IOException e) { - System.err.println("Error reading/parsing file log files:"); - e.printStackTrace(); - if (e.getStackTrace().length > 0) { - searchResults.add(new LogEntry(StringUtils.replaceEach(ExceptionUtils.getStackTrace(e), new String[]{"\t", "\r\n"}, new String[]{" ", "\n"}))); - } - } - Minecraft.getMinecraft().addScheduledTask(() -> { - this.guiSearchResults.setResults(this.searchResults); - setIsSearchInProgress(false); - }); - }); - } else if (button == checkboxChatOnly) { - chatOnly = checkboxChatOnly.isChecked(); - } else if (button == checkboxMatchCase) { - matchCase = checkboxMatchCase.isChecked(); - } else if (button == checkboxRemoveFormatting) { - removeFormatting = checkboxRemoveFormatting.isChecked(); - } else if (button == buttonHelp) { - this.areEntriesSearchResults = false; - this.searchResults.clear(); - this.searchResults.add(new LogEntry("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Initial setup/Configuration " + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "/moo config")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 1) " + EnumChatFormatting.RESET + "Configure directories that should be scanned for log files (\"Directories with Minecraft log files\")")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 2) " + EnumChatFormatting.RESET + "Set default starting date (\"Start date for log file search\")")); - this.searchResults.add(new LogEntry("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Performing a search " + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "/moo search")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 1) " + EnumChatFormatting.RESET + "Enter search term")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 2) " + EnumChatFormatting.RESET + "Adjust start and end date")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 3) " + EnumChatFormatting.RESET + "Select desired options (match case, ...)")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 4) " + EnumChatFormatting.RESET + "Click 'Search'")); - this.searchResults.add(new LogEntry("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Search results")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " - " + EnumChatFormatting.YELLOW + "CTRL + C " + EnumChatFormatting.RESET + "to copy selected search result")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " - " + EnumChatFormatting.YELLOW + "CTRL + Shift + C " + EnumChatFormatting.RESET + "to copy selected search result " + EnumChatFormatting.ITALIC + "with" + EnumChatFormatting.RESET + " formatting codes")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " - " + EnumChatFormatting.YELLOW + "CTRL + A " + EnumChatFormatting.RESET + "to copy all search results")); - this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " - " + EnumChatFormatting.YELLOW + "Double click search result " + EnumChatFormatting.RESET + "to open corresponding log file in default text editor")); - this.guiSearchResults.setResults(searchResults); - } - } - - private void setIsSearchInProgress(boolean isSearchInProgress) { - this.isSearchInProgress = isSearchInProgress; - buttonSearch.enabled = !isSearchInProgress; - fieldSearchQuery.setEnabled(!isSearchInProgress); - fieldDateStart.setEnabled(!isSearchInProgress); - fieldDateEnd.setEnabled(!isSearchInProgress); - checkboxChatOnly.enabled = !isSearchInProgress; - checkboxMatchCase.enabled = !isSearchInProgress; - checkboxRemoveFormatting.enabled = !isSearchInProgress; - if (isSearchInProgress) { - fieldSearchQuery.setFocused(false); - fieldDateStart.setFocused(false); - fieldDateEnd.setFocused(false); - buttonSearch.displayString = EnumChatFormatting.ITALIC + "Searching"; - searchResults.clear(); - guiSearchResults.clearResults(); - analyzedFiles = null; - analyzedFilesWithHits = null; - } else { - buttonSearch.displayString = "Search"; - } - } - - private void drawHoveringText(List<String> textLines, int mouseX, int mouseY, int maxTextWidth) { - if (ForgeVersion.getBuildVersion() < 1808) { - // we're running a forge version from before 24 March 2016 (http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.8.9.html for reference) - // using mc built-in method - drawHoveringText(textLines, mouseX, mouseY, fontRendererObj); - } else { - // we're on a newer forge version, so we can use the improved tooltip rendering added in 1.8.9-11.15.1.1808 (released 03/24/16 09:25 PM) in this pull request: https://github.com/MinecraftForge/MinecraftForge/pull/2649 - GuiUtils.drawHoveringText(textLines, mouseX, mouseY, width, height, maxTextWidth, fontRendererObj); - } - } - - /** - * List gui element similar to GuiModList.Info - */ - class SearchResults extends GuiScrollingList { - private final String[] spinner = new String[]{"oooooo", "Oooooo", "oOoooo", "ooOooo", "oooOoo", "ooooOo", "oooooO"}; - private final DateTimeFormatter coloredDateFormatter = DateTimeFormatter.ofPattern(EnumChatFormatting.GRAY + "HH" + EnumChatFormatting.DARK_GRAY + ":" + EnumChatFormatting.GRAY + "mm" + EnumChatFormatting.DARK_GRAY + ":" + EnumChatFormatting.GRAY + "ss"); - private List<LogEntry> rawResults; - private List<IChatComponent> slotsData; - /** - * key: slot id of 1st line of a search result (if multi-line-result), value: search result id - */ - private NavigableMap<Integer, Integer> searchResultEntries; - private Pair<Long, String> errorMessage; - private String resultsCount; - - SearchResults(int marginTop) { - super(GuiSearch.this.mc, - GuiSearch.this.width - 10, // 5 pixel margin each - GuiSearch.this.height - marginTop - 5, - marginTop, GuiSearch.this.height - 5, - 5, 12, - GuiSearch.this.width, - GuiSearch.this.height); - this.rawResults = Collections.emptyList(); - this.slotsData = Collections.emptyList(); - this.searchResultEntries = Collections.emptyNavigableMap(); - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - super.drawScreen(mouseX, mouseY, partialTicks); - if (isSearchInProgress) { - // spinner taken from IProgressMeter and GuiAchievements#drawScreen - GuiSearch.this.drawCenteredString(GuiSearch.this.fontRendererObj, "Searching for '" + GuiSearch.this.searchQuery + "'", GuiSearch.this.width / 2, GuiSearch.this.height / 2, 16777215); - GuiSearch.this.drawCenteredString(GuiSearch.this.fontRendererObj, spinner[(int) (Minecraft.getSystemTime() / 150L % (long) spinner.length)], GuiSearch.this.width / 2, GuiSearch.this.height / 2 + GuiSearch.this.fontRendererObj.FONT_HEIGHT * 2, 16777215); - } - int hoveredSlotId = this.func_27256_c(mouseX, mouseY); - if (hoveredSlotId >= 0 && mouseY > top && mouseY < bottom) { - float scrollDistance = getScrollDistance(); - if (scrollDistance != Float.MIN_VALUE) { - // draw hovered entry details - - int hoveredSearchResultId = getSearchResultIdBySlotId(hoveredSlotId); - LogEntry hoveredEntry = getSearchResultByResultId(hoveredSearchResultId); - if (hoveredEntry != null && !hoveredEntry.isError()) { - // draw 'tooltips' in the top left corner - drawString(fontRendererObj, "Log file: ", 2, 2, 0xff888888); - GlStateManager.pushMatrix(); - float scaleFactor = 0.75f; - GL11.glScalef(scaleFactor, scaleFactor, scaleFactor); - fontRendererObj.drawSplitString(EnumChatFormatting.GRAY + Utils.toRealPath(hoveredEntry.getFilePath()), 5, (int) ((4 + fontRendererObj.FONT_HEIGHT) * (1 / scaleFactor)), (int) ((GuiSearch.this.fieldSearchQuery.xPosition - 8) * (1 / scaleFactor)), 0xff888888); - GlStateManager.popMatrix(); - drawString(fontRendererObj, "Result: " + EnumChatFormatting.WHITE + (hoveredSearchResultId + 1) + EnumChatFormatting.RESET + "/" + EnumChatFormatting.WHITE + this.rawResults.size(), 8, 48, 0xff888888); - drawString(fontRendererObj, "Time: " + hoveredEntry.getTime().format(coloredDateFormatter), 8, 58, 0xff888888); - } - - // formula from GuiScrollingList#drawScreen slotTop - int baseY = this.top + /* border: */4 - (int) scrollDistance; - - // highlight multiline search results - Integer resultIndexStart = searchResultEntries.floorKey(hoveredSlotId); - Integer resultIndexEnd = searchResultEntries.higherKey(hoveredSlotId); - - if (resultIndexStart == null) { - return; - } else if (resultIndexEnd == null) { - // last result entry - resultIndexEnd = getSize(); - } - - int slotTop = baseY + resultIndexStart * this.slotHeight - 2; - int slotBottom = baseY + resultIndexEnd * this.slotHeight - 2; - drawRect(this.left, Math.max(slotTop, top), right - /* scrollBar: */7, Math.min(slotBottom, bottom), 0x22ffffff); - } - } else if (areEntriesSearchResults) { - if (analyzedFiles != null) { - drawString(fontRendererObj, analyzedFiles, 8, 22, 0xff888888); - } - if (analyzedFilesWithHits != null) { - drawString(fontRendererObj, analyzedFilesWithHits, 8, 32, 0xff888888); - } - if (resultsCount != null) { - drawString(fontRendererObj, resultsCount, 8, 48, 0xff888888); - } - } - if (errorMessage != null) { - if (errorMessage.first().compareTo(System.currentTimeMillis()) > 0) { - String errorText = "Error: " + EnumChatFormatting.RED + errorMessage.second(); - int stringWidth = fontRendererObj.getStringWidth(errorText); - int margin = 5; - int left = width / 2 - stringWidth / 2 - margin; - int top = height / 2 - margin; - drawRect(left, top, left + stringWidth + 2 * margin, top + fontRendererObj.FONT_HEIGHT + 2 * margin, 0xff000000); - drawCenteredString(fontRendererObj, errorText,/* 2, 30*/width / 2, height / 2, 0xffDD1111); - } else { - errorMessage = null; - } - } - } - - private float getScrollDistance() { - Field scrollDistanceField = FieldUtils.getField(GuiScrollingList.class, "scrollDistance", true); - if (scrollDistanceField == null) { - // scrollDistance field not found in class GuiScrollingList - return Float.MIN_VALUE; - } - try { - return (float) scrollDistanceField.get(this); - } catch (IllegalAccessException e) { - e.printStackTrace(); - return Float.MIN_VALUE; - } - } - - @Override - protected int getSize() { - return slotsData.size(); - } - - @Override - protected void elementClicked(int index, boolean doubleClick) { - if (doubleClick) { - int searchResultIdBySlotId = getSearchResultIdBySlotId(index); - LogEntry searchResult = rawResults.get(searchResultIdBySlotId); - if (searchResult.getFilePath() == null) { - setErrorMessage("This log entry is not from a file"); - return; - } - byte[] buffer = new byte[1024]; - String logFileName = Utils.toRealPath(searchResult.getFilePath()); - if (logFileName.endsWith("latest.log")) { - try { - Files.copy(searchResult.getFilePath(), mcLogOutputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - e.printStackTrace(); - } - } else { // .log.gz - String newLine = System.getProperty("line.separator"); - String fileHeader = "# Original filename: " + logFileName + newLine + "# Use CTRL + F to search for specific words" + newLine + newLine; - try (GZIPInputStream logFileGzipped = new GZIPInputStream(new FileInputStream(logFileName)); - FileOutputStream logFileUnGzipped = new FileOutputStream(mcLogOutputFile)) { - logFileUnGzipped.write(fileHeader.getBytes()); - int len; - while ((len = logFileGzipped.read(buffer)) > 0) { - logFileUnGzipped.write(buffer, 0, len); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - try { - Desktop.getDesktop().open(mcLogOutputFile); - } catch (IOException e) { - setErrorMessage("File extension .txt has no associated default editor"); - e.printStackTrace(); - } catch (IllegalArgumentException e) { - setErrorMessage(e.getMessage()); // The file: <path> doesn't exist. - e.printStackTrace(); - } catch (UnsupportedOperationException e) { - setErrorMessage("Can't open files on this OS"); - e.printStackTrace(); - } - } - } - - private void setErrorMessage(String errorMessage) { - int showDuration = 10000; // ms - this.errorMessage = Pair.of(System.currentTimeMillis() + showDuration, errorMessage); - } - - @Override - protected boolean isSelected(int index) { - return false; - } - - @Override - protected void drawBackground() { - } - - @Override - protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess) { - int drawnResultIndex = searchResultEntries.floorKey(slotIdx); - if (Objects.equals(searchResultEntries.floorKey(selectedIndex), drawnResultIndex)) { - // highlight all lines of selected entry - drawRect(this.left, slotTop - 2, entryRight, slotTop + slotHeight - 2, 0x99000000); - } - IChatComponent slotData = slotsData.get(slotIdx); - if (slotData != null) { - GlStateManager.enableBlend(); - GuiSearch.this.fontRendererObj.drawStringWithShadow(slotData.getFormattedText(), this.left + 4, slotTop, 0xFFFFFF); - GlStateManager.disableAlpha(); - GlStateManager.disableBlend(); - } - } - - private void setResults(List<LogEntry> searchResult) { - this.rawResults = searchResult; - this.slotsData = resizeContent(searchResult); - if (GuiSearch.this.areEntriesSearchResults) { - this.resultsCount = "Results: " + EnumChatFormatting.WHITE + this.rawResults.size(); - } - } - - private void clearResults() { - this.rawResults = Collections.emptyList(); - this.resultsCount = null; - this.slotsData = resizeContent(Collections.emptyList()); - } - - private List<IChatComponent> resizeContent(List<LogEntry> searchResults) { - this.searchResultEntries = new TreeMap<>(); - List<IChatComponent> slotsData = new ArrayList<>(); - for (int searchResultIndex = 0; searchResultIndex < searchResults.size(); searchResultIndex++) { - LogEntry searchResult = searchResults.get(searchResultIndex); - - String searchResultEntry; - if (searchResult.isError()) { - searchResultEntry = searchResult.getMessage(); - } else { - searchResultEntry = EnumChatFormatting.DARK_GRAY + searchResult.getTime().format(DateTimeFormatter.ISO_LOCAL_DATE) + " " + EnumChatFormatting.RESET + searchResult.getMessage(); - } - searchResultEntries.put(slotsData.size(), searchResultIndex); - List<IChatComponent> multilineResult = GuiUtilRenderComponents.splitText(new ChatComponentText(searchResultEntry), this.listWidth - 8, GuiSearch.this.fontRendererObj, false, true); - slotsData.addAll(multilineResult); - } - return slotsData; - } - - LogEntry getSelectedSearchResult() { - int searchResultId = getSearchResultIdBySlotId(selectedIndex); - return getSearchResultByResultId(searchResultId); - } - - private LogEntry getSearchResultByResultId(int searchResultId) { - return (searchResultId >= 0 && searchResultId < rawResults.size()) ? rawResults.get(searchResultId) : null; - } - - private int getSearchResultIdBySlotId(int slotId) { - Map.Entry<Integer, Integer> searchResultIds = searchResultEntries.floorEntry(slotId); - return searchResultIds != null ? searchResultIds.getValue() : -1; - } - - String getAllSearchResults() { - return rawResults.stream().map(logEntry -> EnumChatFormatting.getTextWithoutFormattingCodes(logEntry.getMessage())) - .collect(Collectors.joining("\n")); - } - } -} diff --git a/src/main/java/eu/olli/cowlection/search/GuiTooltip.java b/src/main/java/eu/olli/cowlection/search/GuiTooltip.java deleted file mode 100644 index f76bf2d..0000000 --- a/src/main/java/eu/olli/cowlection/search/GuiTooltip.java +++ /dev/null @@ -1,50 +0,0 @@ -package eu.olli.cowlection.search; - -import net.minecraft.client.gui.Gui; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiTextField; -import net.minecraftforge.fml.client.config.GuiCheckBox; -import net.minecraftforge.fml.client.config.HoverChecker; - -import java.util.List; - -public class GuiTooltip { - private final HoverChecker hoverChecker; - private final List<String> tooltip; - - public <T extends Gui> GuiTooltip(T field, List<String> tooltip) { - if (field instanceof GuiCheckBox) { - // checkbox - GuiCheckBox guiCheckBox = (GuiCheckBox) field; - int top = guiCheckBox.yPosition; - int bottom = guiCheckBox.yPosition + guiCheckBox.height; - int left = guiCheckBox.xPosition; - int right = guiCheckBox.xPosition + guiCheckBox.width; - - this.hoverChecker = new HoverChecker(top, bottom, left, right, 300); - } else if (field instanceof GuiTextField) { - // text field - GuiTextField guiTextField = (GuiTextField) field; - int top = guiTextField.yPosition; - int bottom = guiTextField.yPosition + guiTextField.height; - int left = guiTextField.xPosition; - int right = guiTextField.xPosition + guiTextField.width; - - this.hoverChecker = new HoverChecker(top, bottom, left, right, 300); - } else if (field instanceof GuiButton) { - // button - this.hoverChecker = new HoverChecker((GuiButton) field, 300); - } else { - throw new IllegalArgumentException("Tried to add a tooltip to an illegal field type: " + field.getClass()); - } - this.tooltip = tooltip; - } - - public List<String> getText() { - return tooltip; - } - - public boolean checkHover(int mouseX, int mouseY) { - return hoverChecker.checkHover(mouseX, mouseY); - } -} diff --git a/src/main/java/eu/olli/cowlection/search/LogFilesSearcher.java b/src/main/java/eu/olli/cowlection/search/LogFilesSearcher.java deleted file mode 100644 index 7aeb2aa..0000000 --- a/src/main/java/eu/olli/cowlection/search/LogFilesSearcher.java +++ /dev/null @@ -1,181 +0,0 @@ -package eu.olli.cowlection.search; - -import eu.olli.cowlection.config.MooConfig; -import eu.olli.cowlection.data.LogEntry; -import net.minecraft.util.EnumChatFormatting; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutableTriple; - -import java.io.*; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.GZIPInputStream; - -class LogFilesSearcher { - /** - * Log4j.xml PatternLayout: [%d{HH:mm:ss}] [%t/%level]: %msg%n - * Log line: [TIME] [THREAD/LEVEL]: [CHAT] msg - * examples: - * - [13:33:37] [Client thread/INFO]: [CHAT] Hello World - * - [08:15:42] [Client thread/ERROR]: Item entity 9001 has no item?! - */ - private static final Pattern LOG4J_PATTERN = Pattern.compile("^\\[(?<timeHours>[\\d]{2}):(?<timeMinutes>[\\d]{2}):(?<timeSeconds>[\\d]{2})] \\[(?<thread>[^/]+)/(?<logLevel>[A-Z]+)]:(?<isChat> \\[CHAT])? (?<message>.*)$"); - private int analyzedFilesWithHits = 0; - - ImmutableTriple<Integer, Integer, List<LogEntry>> searchFor(String searchQuery, boolean chatOnly, boolean matchCase, boolean removeFormatting, LocalDate dateStart, LocalDate dateEnd) throws IOException { - List<Path> files = new ArrayList<>(); - for (String logsDirPath : MooConfig.logsDirs) { - File logsDir = new File(logsDirPath); - if (logsDir.exists() && logsDir.isDirectory()) { - try { - files.addAll(fileList(logsDir, dateStart, dateEnd)); - } catch (IOException e) { - throw throwIoException(logsDirPath, e); - } - } - } - - if (files.isEmpty()) { - throw new FileNotFoundException(EnumChatFormatting.DARK_RED + "ERROR: Couldn't find any Minecraft log files. Please check if the log file directories are set correctly (/moo config)."); - } else { - List<LogEntry> searchResults = analyzeFiles(files, searchQuery, chatOnly, matchCase, removeFormatting) - .stream().sorted(Comparator.comparing(LogEntry::getTime)).collect(Collectors.toList()); - return new ImmutableTriple<>(files.size(), analyzedFilesWithHits, searchResults); - } - } - - private List<LogEntry> analyzeFiles(List<Path> paths, String searchTerm, boolean chatOnly, boolean matchCase, boolean removeFormatting) throws IOException { - List<LogEntry> searchResults = new ArrayList<>(); - for (Path path : paths) { - boolean foundSearchTermInFile = false; - try (BufferedReader in = (path.endsWith("latest.log") - ? new BufferedReader(new InputStreamReader(new FileInputStream(path.toFile()))) // latest.log - : new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(path.toFile())))))) { // ....log.gz - String fileName = path.getFileName().toString(); // 2020-04-20-3.log.gz - String date = fileName.equals("latest.log") - ? LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE) - : fileName.substring(0, fileName.lastIndexOf('-')); - String content; - LogEntry logEntry = null; - while ((content = in.readLine()) != null) { - Matcher logLineMatcher = LOG4J_PATTERN.matcher(content); - if (logLineMatcher.matches()) { // current line is a new log entry - if (logEntry != null) { - // we had a previous log entry; analyze it! - LogEntry result = analyzeLogEntry(logEntry, searchTerm, matchCase, removeFormatting); - if (result != null) { - searchResults.add(result); - foundSearchTermInFile = true; - } - logEntry = null; - } - // handle first line of new log entry - if (chatOnly && logLineMatcher.group("isChat") == null) { - // not a chat log entry, although we're only searching for chat messages, abort! - continue; - } - LocalDateTime dateTime = getDate(date, logLineMatcher); - logEntry = new LogEntry(dateTime, path, logLineMatcher.group("message")); - } else if (logEntry != null) { - // multiline log entry - logEntry.addLogLine(content); - } - } - if (logEntry != null) { - // end of file! analyze last log entry in file - LogEntry result = analyzeLogEntry(logEntry, searchTerm, matchCase, removeFormatting); - if (result != null) { - searchResults.add(result); - foundSearchTermInFile = true; - } - } - if (foundSearchTermInFile) { - analyzedFilesWithHits++; - } - } catch (IOException e) { - throw throwIoException(path.toString(), e); - } - } - return searchResults; - } - - private LocalDateTime getDate(String date, Matcher logLineMatcher) { - int year = Integer.parseInt(date.substring(0, 4)); - int month = Integer.parseInt(date.substring(5, 7)); - int day = Integer.parseInt(date.substring(8, 10)); - int hour = Integer.parseInt(logLineMatcher.group(1)); - int minute = Integer.parseInt(logLineMatcher.group(2)); - int sec = Integer.parseInt(logLineMatcher.group(3)); - - return LocalDateTime.of(year, month, day, hour, minute, sec); - } - - private LogEntry analyzeLogEntry(LogEntry logEntry, String searchTerms, boolean matchCase, boolean removeFormatting) { - if (logEntry.getMessage().length() > 5000) { - // avoid ultra long log entries - return null; - } - logEntry.fixWeirdCharacters(); - - if (removeFormatting) { - logEntry.removeFormatting(); - } - String logMessage = logEntry.getMessage(); - if (!matchCase) { - if (!StringUtils.containsIgnoreCase(logMessage, searchTerms)) { - // no result, abort - return null; - } - } else if (!logMessage.contains(searchTerms)) { - // no result, abort - return null; - } - - return logEntry; - } - - private List<Path> fileList(File directory, LocalDate startDate, LocalDate endDate) throws IOException { - List<Path> fileNames = new ArrayList<>(); - try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory.toPath())) { - for (Path path : directoryStream) { - if (path.toString().endsWith(".log.gz")) { - String[] fileDate = path.getFileName().toString().split("-"); - if (fileDate.length == 4) { - LocalDate fileLocalDate = LocalDate.of(Integer.parseInt(fileDate[0]), - Integer.parseInt(fileDate[1]), Integer.parseInt(fileDate[2])); - - if (fileLocalDate.compareTo(startDate) >= 0 && fileLocalDate.compareTo(endDate) <= 0) { - fileNames.add(path); - } - } else { - System.err.println("Error with " + path.toString()); - } - } else if (path.getFileName().toString().equals("latest.log")) { - LocalDate lastModified = Instant.ofEpochMilli(path.toFile().lastModified()).atZone(ZoneId.systemDefault()).toLocalDate(); - if (!lastModified.isBefore(startDate) && !lastModified.isAfter(endDate)) { - fileNames.add(path); - } - } - } - } - return fileNames; - } - - private IOException throwIoException(String file, IOException e) throws IOException { - IOException ioException = new IOException(EnumChatFormatting.DARK_RED + "ERROR: An error occurred trying to read/parse '" + EnumChatFormatting.RED + file + EnumChatFormatting.DARK_RED + "'"); - ioException.setStackTrace(e.getStackTrace()); - throw ioException; - } -} |