diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 5af8f1b3969ef017ce099bfa94cd3c0b1eff2c17..125e84be27211419faad1b09a177291243065cf9 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -23,10 +23,10 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; +import javax.swing.SwingWorker; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.Element; @@ -36,29 +36,30 @@ import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit.HTMLFactory; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.util.Cancellable; /** - * Panel displays HTML content sent to ExtractedContentViewer, and provides - * a combo-box to select between multiple sources. + * Panel displays HTML content sent to ExtractedContentViewer, and provides a + * combo-box to select between multiple sources. */ class ExtractedContentPanel extends javax.swing.JPanel { private static Logger logger = Logger.getLogger(ExtractedContentPanel.class.getName()); - ExtractedContentPanel() { + ExtractedContentPanel() { initComponents(); initControls(); - + customizeComponents(); - + } - + private void customizeComponents() { extractedTextPane.setEditorKit(new HTMLEditorKit() { - ViewFactory viewFactory = new HTMLFactory() { - @Override public View create(Element elem) { AttributeSet attrs = elem.getAttributes(); @@ -81,38 +82,39 @@ public ViewFactory getViewFactory() { }); sourceComboBox.addItemListener(new ItemListener() { - @Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { MarkupSource source = (MarkupSource) e.getItem(); - setPanelText(source.getMarkup()); + //setPanelText(source.getMarkup()); + new SetMarkup(source).execute(); } } }); - + setSources(new ArrayList<MarkupSource>()); - + extractedTextPane.setComponentPopupMenu(rightClickMenu); - ActionListener actList = new ActionListener(){ + ActionListener actList = new ActionListener() { @Override - public void actionPerformed(ActionEvent e){ + public void actionPerformed(ActionEvent e) { JMenuItem jmi = (JMenuItem) e.getSource(); - if(jmi.equals(copyMenuItem)) + if (jmi.equals(copyMenuItem)) { extractedTextPane.copy(); - else if(jmi.equals(selectAllMenuItem)) + } else if (jmi.equals(selectAllMenuItem)) { extractedTextPane.selectAll(); + } } }; copyMenuItem.addActionListener(actList); selectAllMenuItem.addActionListener(actList); } - /** This method is called from within the constructor to - * initialize the form. - * WARNING: Do NOT modify this code. The content of this method is - * always regenerated by the Form Editor. + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents @@ -282,7 +284,6 @@ private void initComponents() { .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 293, Short.MAX_VALUE)) ); }// </editor-fold>//GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JMenuItem copyMenuItem; private javax.swing.JTextPane extractedTextPane; @@ -306,16 +307,17 @@ private void initComponents() { private javax.swing.JComboBox sourceComboBox; // End of variables declaration//GEN-END:variables - void refreshCurrentMarkup() { - MarkupSource ms = (MarkupSource)sourceComboBox.getSelectedItem(); - setPanelText(ms.getMarkup()); + MarkupSource ms = (MarkupSource) sourceComboBox.getSelectedItem(); + //setPanelText(ms.getMarkup()); + new SetMarkup(ms).execute(); } - + /** * Set the available sources (selects the first source in the list by * default) - * @param sources + * + * @param sources */ void setSources(List<MarkupSource> sources) { sourceComboBox.removeAllItems(); @@ -331,7 +333,7 @@ void setSources(List<MarkupSource> sources) { } /** - * + * * @return currently available sources on the panel */ public List<MarkupSource> getSources() { @@ -343,7 +345,7 @@ public List<MarkupSource> getSources() { } /** - * + * * @return currently selected Source */ public MarkupSource getSelectedSource() { @@ -360,34 +362,36 @@ private void initControls() { hitNextButton.setEnabled(false); } - void scrollToAnchor(String anchor) { extractedTextPane.scrollToReference(anchor); } /** - * + * * @param current, current hit to update the display with */ void updateCurrentMatchDisplay(int current) { - if (current == 0) + if (current == 0) { hitCountLabel.setText("-"); - else hitCountLabel.setText(Integer.toString(current)); + } else { + hitCountLabel.setText(Integer.toString(current)); + } } /** - * + * * @param total total number of hits to update the display with */ void updateTotaMatcheslDisplay(int total) { - if (total == 0) + if (total == 0) { hitTotalLabel.setText("-"); - else hitTotalLabel.setText(Integer.toString(total)); + } else { + hitTotalLabel.setText(Integer.toString(total)); + } } - - + /** - * + * * @param current, current page to update the display with */ void updateCurrentPageDisplay(int current) { @@ -395,19 +399,18 @@ void updateCurrentPageDisplay(int current) { } /** - * + * * @param total total number of pages to update the display with */ void updateTotalPageslDisplay(int total) { pageTotalLabel.setText(Integer.toString(total)); } - - + void resetDisplay() { resetHitDisplay(); resetPagesDisplay(); } - + /** * reset the current/total hits display */ @@ -415,7 +418,7 @@ void resetHitDisplay() { hitTotalLabel.setText("-"); hitCountLabel.setText("-"); } - + /** * reset the current/total pages display */ @@ -426,6 +429,7 @@ void resetPagesDisplay() { /** * enable previous match control + * * @param enable whether to enable or disable */ void enablePrevMatchControl(boolean enable) { @@ -434,6 +438,7 @@ void enablePrevMatchControl(boolean enable) { /** * enable next match control + * * @param enable whether to enable or disable */ void enableNextMatchControl(boolean enable) { @@ -447,10 +452,10 @@ void addPrevMatchControlListener(ActionListener l) { void addNextMatchControlListener(ActionListener l) { hitNextButton.addActionListener(l); } - - + /** * enable previous oage control + * * @param enable whether to enable or disable */ void enablePrevPageControl(boolean enable) { @@ -459,6 +464,7 @@ void enablePrevPageControl(boolean enable) { /** * enable next page control + * * @param enable whether to enable or disable */ void enableNextPageControl(boolean enable) { @@ -476,4 +482,43 @@ void addNextPageControlListener(ActionListener l) { void addSourceComboControlListener(ActionListener l) { sourceComboBox.addActionListener(l); } + + /** + * Swingworker to get makrup source content String from Solr in background thread + * and then set the panel text in the EDT + * Helps not to block the UI while content from Solr is retrieved. + */ + private final class SetMarkup extends SwingWorker<Object, Void> { + + private MarkupSource source; + private String markup; + private ProgressHandle progress; + + SetMarkup(MarkupSource source) { + this.source = source; + } + + @Override + protected Object doInBackground() throws Exception { + progress = ProgressHandleFactory.createHandle("Loading text"); + progress.setDisplayName("Loading text"); + progress.start(); + progress.switchToIndeterminate(); + + setPanelText("<span style='font-style:italic'>Loading text... Please wait</span>"); + markup = source.getMarkup(); + return null; + } + + @Override + protected void done() { + super.done(); + progress.finish(); + if (markup != null) { + setPanelText(markup); + } else { + setPanelText(""); + } + } + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 5fe463a1992fbbacacfb50b2c5036917b296d43c..3cd21954deddc7177c74cb5e4c2e4be5cec79274 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -41,11 +41,11 @@ import org.sleuthkit.datamodel.Directory; /** - * Displays marked-up (HTML) content for a Node. The sources are all the - * MarkupSource items in the selected Node's lookup, plus the content that - * Solr extracted (if there is any). + * Displays marked-up (HTML) content for a Node. The sources are all the + * MarkupSource items in the selected Node's lookup, plus the content that Solr + * extracted (if there is any). */ -@ServiceProvider(service = DataContentViewer.class, position=4) +@ServiceProvider(service = DataContentViewer.class, position = 4) public class ExtractedContentViewer implements DataContentViewer { private static final Logger logger = Logger.getLogger(ExtractedContentViewer.class.getName()); @@ -53,7 +53,6 @@ public class ExtractedContentViewer implements DataContentViewer { private Node currentNode = null; private MarkupSource currentSource = null; private final IsDirVisitor isDirVisitor = new IsDirVisitor(); - //keep last content cached private String curContent; private long curContentId; @@ -77,161 +76,166 @@ public void setNode(final Node selectedNode) { // sources are custom markup from the node (if available) and default // markup is fetched from solr - List<MarkupSource> sources = new ArrayList<MarkupSource>(); + final List<MarkupSource> sources = new ArrayList<MarkupSource>(); //add additional registered sources for this node sources.addAll(selectedNode.getLookup().lookupAll(MarkupSource.class)); - if (solrHasContent(selectedNode)) { - Content content = selectedNode.getLookup().lookup(Content.class); - if (content == null) { - return; - } + if (!solrHasContent(selectedNode)) { + //currentNode = null; + //resetComponent(); + // first source will be the default displayed + setPanel(sources); + return; + } + Content content = selectedNode.getLookup().lookup(Content.class); + if (content == null) { + return; + } - //add to page tracking if not there yet - final long contentID = content.getId(); + //add to page tracking if not there yet + final long contentID = content.getId(); - MarkupSource newSource = new MarkupSource() { + final MarkupSource newSource = new MarkupSource() { + private boolean inited = false; + private int numPages = 0; + private int currentPage = 0; + private boolean hasChunks = false; - private boolean inited = false; - private int numPages = 0; - private int currentPage = 0; - private boolean hasChunks = false; + @Override + public int getCurrentPage() { + return this.currentPage; + } - @Override - public int getCurrentPage() { - return this.currentPage; - } + @Override + public boolean hasNextPage() { + return currentPage < numPages; + } - @Override - public boolean hasNextPage() { - return currentPage < numPages; - } + @Override + public boolean hasPreviousPage() { + return currentPage > 1; + } - @Override - public boolean hasPreviousPage() { - return currentPage > 1; + @Override + public int nextPage() { + if (!hasNextPage()) { + throw new IllegalStateException("No next page."); } + ++currentPage; + return currentPage; + } - @Override - public int nextPage() { - if (!hasNextPage()) { - throw new IllegalStateException("No next page."); - } - ++currentPage; - return currentPage; + @Override + public int previousPage() { + if (!hasPreviousPage()) { + throw new IllegalStateException("No previous page."); } + --currentPage; + return currentPage; + } - @Override - public int previousPage() { - if (!hasPreviousPage()) { - throw new IllegalStateException("No previous page."); - } - --currentPage; - return currentPage; - } + @Override + public boolean hasNextItem() { + throw new UnsupportedOperationException("Not supported, not a searchable source."); + } - @Override - public boolean hasNextItem() { - throw new UnsupportedOperationException("Not supported, not a searchable source."); - } + @Override + public boolean hasPreviousItem() { + throw new UnsupportedOperationException("Not supported, not a searchable source."); + } - @Override - public boolean hasPreviousItem() { - throw new UnsupportedOperationException("Not supported, not a searchable source."); - } + @Override + public int nextItem() { + throw new UnsupportedOperationException("Not supported, not a searchable source."); + } - @Override - public int nextItem() { - throw new UnsupportedOperationException("Not supported, not a searchable source."); - } + @Override + public int previousItem() { + throw new UnsupportedOperationException("Not supported, not a searchable source."); + } - @Override - public int previousItem() { - throw new UnsupportedOperationException("Not supported, not a searchable source."); - } + @Override + public int currentItem() { + throw new UnsupportedOperationException("Not supported, not a searchable source."); + } - @Override - public int currentItem() { - throw new UnsupportedOperationException("Not supported, not a searchable source."); + @Override + public String getMarkup() { + try { + return getSolrContent(selectedNode, currentPage, hasChunks); + } catch (SolrServerException ex) { + logger.log(Level.WARNING, "Couldn't get extracted content.", ex); + return ""; } + } - @Override - public String getMarkup() { - try { - return getSolrContent(selectedNode, currentPage, hasChunks); - } catch (SolrServerException ex) { - logger.log(Level.WARNING, "Couldn't get extracted content.", ex); - return ""; - } - } + @Override + public String toString() { + return "Extracted Content"; + } - @Override - public String toString() { - return "Extracted Content"; - } + @Override + public boolean isSearchable() { + return false; + } - @Override - public boolean isSearchable() { - return false; - } + @Override + public String getAnchorPrefix() { + return ""; + } - @Override - public String getAnchorPrefix() { - return ""; - } + @Override + public int getNumberHits() { + return 0; + } - @Override - public int getNumberHits() { - return 0; - } + @Override + public LinkedHashMap<Integer, Integer> getHitsPages() { + return null; + } - @Override - public LinkedHashMap<Integer, Integer> getHitsPages() { - return null; + @Override + public int getNumberPages() { + if (inited) { + return this.numPages; } - @Override - public int getNumberPages() { - if (inited) { - return this.numPages; - } + final Server solrServer = KeywordSearch.getServer(); - final Server solrServer = KeywordSearch.getServer(); - - try { - numPages = solrServer.queryNumFileChunks(contentID); - if (numPages == 0) { - numPages = 1; - hasChunks = false; - } else { - hasChunks = true; - } - inited = true; - } catch (KeywordSearchModuleException ex) { - logger.log(Level.WARNING, "Could not get number of chunks: ", ex); - - } catch (NoOpenCoreException ex) { - logger.log(Level.WARNING, "Could not get number of chunks: ", ex); + try { + numPages = solrServer.queryNumFileChunks(contentID); + if (numPages == 0) { + numPages = 1; + hasChunks = false; + } else { + hasChunks = true; } - return numPages; + inited = true; + } catch (KeywordSearchModuleException ex) { + logger.log(Level.WARNING, "Could not get number of chunks: ", ex); + + } catch (NoOpenCoreException ex) { + logger.log(Level.WARNING, "Could not get number of chunks: ", ex); } - }; + return numPages; + } + }; - currentSource = newSource; - sources.add(newSource); + currentSource = newSource; + sources.add(newSource); - //init pages - final int totalPages = currentSource.getNumberPages(); - int currentPage = currentSource.getCurrentPage(); - if (currentPage == 0 && currentSource.hasNextPage()) { - currentSource.nextPage(); - } + //init pages + final int totalPages = currentSource.getNumberPages(); + int currentPage = currentSource.getCurrentPage(); + if (currentPage == 0 && currentSource.hasNextPage()) { + currentSource.nextPage(); + } - updatePageControls(); - } + updatePageControls(); + // first source will be the default displayed @@ -248,7 +252,6 @@ private void scrollToCurrentHit() { // using invokeLater to wait for ComboBox selection to complete EventQueue.invokeLater(new Runnable() { - @Override public void run() { panel.scrollToAnchor(source.getAnchorPrefix() + Integer.toString(source.currentItem())); @@ -306,10 +309,10 @@ public boolean isSupported(Node node) { public int isPreferred(Node node, boolean isSupported) { BlackboardArtifact art = node.getLookup().lookup(BlackboardArtifact.class); - if(isSupported) { - if(art == null) { + if (isSupported) { + if (art == null) { return 4; - } else if(art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { + } else if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { return 6; } else { return 4; @@ -322,7 +325,8 @@ public int isPreferred(Node node, /** * Set the MarkupSources for the panel to display (safe to call even if the * panel hasn't been created yet) - * @param sources + * + * @param sources */ private void setPanel(List<MarkupSource> sources) { if (panel != null) { @@ -355,6 +359,7 @@ public Boolean visit(Directory d) { /** * Check if Solr has extracted content for a given node + * * @param node * @return true if Solr has content, else false */ @@ -364,8 +369,9 @@ private boolean solrHasContent(Node node) { return false; } - if (content.getSize() == 0) + if (content.getSize() == 0) { return false; + } final Server solrServer = KeywordSearch.getServer(); @@ -389,10 +395,12 @@ private boolean solrHasContent(Node node) { /** * Get extracted content for a node from Solr + * * @param node a node that has extracted content in Solr (check with * solrHasContent(ContentNode)) - * @param currentPage currently used page - * @param hasChunks true if the content behind the node has multiple chunks. This means we need to address the content pages specially. + * @param currentPage currently used page + * @param hasChunks true if the content behind the node has multiple chunks. + * This means we need to address the content pages specially. * @return the extracted content * @throws SolrServerException if something goes wrong */