diff --git a/source/net/sourceforge/filebot/ui/panel/search/FetchEpisodesTask.java b/source/net/sourceforge/filebot/ui/panel/search/FetchEpisodeListTask.java similarity index 84% rename from source/net/sourceforge/filebot/ui/panel/search/FetchEpisodesTask.java rename to source/net/sourceforge/filebot/ui/panel/search/FetchEpisodeListTask.java index 7303e062..2ddc983f 100644 --- a/source/net/sourceforge/filebot/ui/panel/search/FetchEpisodesTask.java +++ b/source/net/sourceforge/filebot/ui/panel/search/FetchEpisodeListTask.java @@ -10,7 +10,7 @@ import net.sourceforge.filebot.web.Episode; import net.sourceforge.filebot.web.EpisodeListClient; -class FetchEpisodesTask extends SwingWorker, Object> { +class FetchEpisodeListTask extends SwingWorker, Object> { private String showName; private EpisodeListClient searchEngine; @@ -18,7 +18,7 @@ class FetchEpisodesTask extends SwingWorker, Object> { private long duration; - public FetchEpisodesTask(EpisodeListClient searchEngine, String showname, int numberOfSeason) { + public FetchEpisodeListTask(EpisodeListClient searchEngine, String showname, int numberOfSeason) { showName = showname; this.searchEngine = searchEngine; this.numberOfSeason = numberOfSeason; diff --git a/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java b/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java index b2f06a39..e3d1b9fc 100644 --- a/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java @@ -25,6 +25,7 @@ import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.KeyStroke; +import javax.swing.ScrollPaneConstants; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; @@ -76,7 +77,7 @@ public class SearchPanel extends FileBotPanel { searchField.getTextField().setColumns(25); searchFieldCompletion = new TextCompletion(searchField.getTextField()); - searchFieldCompletion.addCompletionTerms(Settings.getSettings().getStringList(Settings.SEARCH_HISTORY)); + searchFieldCompletion.addTerms(Settings.getSettings().getStringList(Settings.SEARCH_HISTORY)); searchFieldCompletion.hook(); JPanel mainPanel = new JPanel(new BorderLayout(5, 5)); @@ -111,6 +112,8 @@ public class SearchPanel extends FileBotPanel { JScrollPane sp = new JScrollPane(history); sp.setBorder(BorderFactory.createEmptyBorder()); + sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + tabbedPane.addTab("History", ResourceManager.getIcon("tab.history"), sp); mainPanel.add(searchBox, BorderLayout.NORTH); @@ -134,9 +137,9 @@ public class SearchPanel extends FileBotPanel { private final PropertyChangeListener searchFieldListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { - EpisodeListClient se = searchField.getSelectedValue(); + EpisodeListClient client = searchField.getSelectedValue(); - if (!se.isSingleSeasonSupported()) { + if (!client.isSingleSeasonSupported()) { seasonSpinnerModel.setMaximum(SeasonSpinnerEditor.ALL_SEASONS); seasonSpinnerModel.setValue(SeasonSpinnerEditor.ALL_SEASONS); } else { @@ -277,8 +280,9 @@ public class SearchPanel extends FileBotPanel { String title = task.getSearchTerm(); - if (task.getNumberOfSeason() != SeasonSpinnerEditor.ALL_SEASONS) - title += " - Season " + task.getNumberOfSeason(); + if (task.getNumberOfSeason() != SeasonSpinnerEditor.ALL_SEASONS) { + title += String.format(" - Season %d", task.getNumberOfSeason()); + } episodeList.setTitle(title); episodeList.setIcon(task.getSearchEngine().getIcon()); @@ -306,17 +310,23 @@ public class SearchPanel extends FileBotPanel { tabbedPane.remove(episodeList); Throwable cause = FileBotUtil.getRootCause(e); + MessageManager.showWarning(cause.getMessage()); - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, cause.toString()); + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, cause.toString()); return; } String showname = null; - if (shows.size() == 1) + if (task.getSearchEngine().getFoundName(task.getSearchTerm()) != null) { + // a show matching the search term exactly has already been found + showname = task.getSearchEngine().getFoundName(task.getSearchTerm()); + } else if (shows.size() == 1) { + // only one show found, select this one showname = shows.get(0); - else if (shows.size() > 1) { + } else if (shows.size() > 1) { + // multiple shows found, let user selected one Window window = SwingUtilities.getWindowAncestor(SearchPanel.this); SelectDialog select = new SelectDialog(window, shows); @@ -324,6 +334,7 @@ public class SearchPanel extends FileBotPanel { select.setText("Select a Show:"); select.setIconImage(episodeList.getIcon().getImage()); select.setVisible(true); + showname = select.getSelectedValue(); } else { MessageManager.showWarning("\"" + task.getSearchTerm() + "\" has not been found."); @@ -334,29 +345,31 @@ public class SearchPanel extends FileBotPanel { return; } - searchFieldCompletion.addCompletionTerm(showname); - Settings.getSettings().putStringList(Settings.SEARCH_HISTORY, searchFieldCompletion.getCompletionTerms()); + searchFieldCompletion.addTerm(showname); + Settings.getSettings().putStringList(Settings.SEARCH_HISTORY, searchFieldCompletion.getTerms()); String title = showname; - if (task.getNumberOfSeason() != SeasonSpinnerEditor.ALL_SEASONS) - title += " - Season " + task.getNumberOfSeason(); + if (task.getNumberOfSeason() != SeasonSpinnerEditor.ALL_SEASONS) { + title += String.format(" - Season %d", task.getNumberOfSeason()); + } episodeList.setTitle(title); - FetchEpisodesTask getEpisodesTask = new FetchEpisodesTask(task.getSearchEngine(), showname, task.getNumberOfSeason()); - getEpisodesTask.addPropertyChangeListener(new FetchEpisodesTaskListener(episodeList)); + FetchEpisodeListTask getEpisodesTask = new FetchEpisodeListTask(task.getSearchEngine(), showname, task.getNumberOfSeason()); + getEpisodesTask.addPropertyChangeListener(new FetchEpisodeListTaskListener(episodeList)); + getEpisodesTask.execute(); } } - private class FetchEpisodesTaskListener extends SwingWorkerPropertyChangeAdapter { + private class FetchEpisodeListTaskListener extends SwingWorkerPropertyChangeAdapter { private EpisodeListPanel episodeList; - public FetchEpisodesTaskListener(EpisodeListPanel episodeList) { + public FetchEpisodeListTaskListener(EpisodeListPanel episodeList) { this.episodeList = episodeList; } @@ -367,7 +380,7 @@ public class SearchPanel extends FileBotPanel { if (tabbedPane.indexOfComponent(episodeList) < 0) return; - FetchEpisodesTask task = (FetchEpisodesTask) evt.getSource(); + FetchEpisodeListTask task = (FetchEpisodeListTask) evt.getSource(); try { URL url = task.getSearchEngine().getEpisodeListUrl(task.getShowName(), task.getNumberOfSeason()); diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java index 7ebff6ca..cb48994b 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java @@ -25,7 +25,7 @@ class ChecksumTableModel extends AbstractTableModel { private List checksumColumnRoots = new ArrayList(); - private int checksumColumnsOffset = 2; + private final int checksumColumnsOffset = 2; @Override diff --git a/source/net/sourceforge/filebot/ui/transferablepolicies/BackgroundFileTransferablePolicy.java b/source/net/sourceforge/filebot/ui/transferablepolicies/BackgroundFileTransferablePolicy.java index e67c8f2e..d3b956ab 100644 --- a/source/net/sourceforge/filebot/ui/transferablepolicies/BackgroundFileTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/transferablepolicies/BackgroundFileTransferablePolicy.java @@ -79,7 +79,9 @@ public abstract class BackgroundFileTransferablePolicy extends FileTransferab * @param chunks */ protected final void publish(V... chunks) { - worker.publishChunks(chunks); + if (worker != null) { + worker.publishChunks(chunks); + } } diff --git a/source/net/sourceforge/filebot/web/AnidbClient.java b/source/net/sourceforge/filebot/web/AnidbClient.java index 41a34e3f..c85bc25f 100644 --- a/source/net/sourceforge/filebot/web/AnidbClient.java +++ b/source/net/sourceforge/filebot/web/AnidbClient.java @@ -9,10 +9,10 @@ import java.net.URLEncoder; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,7 +26,7 @@ import org.xml.sax.SAXException; public class AnidbClient extends EpisodeListClient { - private Map cache = Collections.synchronizedMap(new HashMap()); + private NavigableMap cache = new TreeMap(String.CASE_INSENSITIVE_ORDER); private String host = "anidb.info"; @@ -38,13 +38,17 @@ public class AnidbClient extends EpisodeListClient { @Override public List search(String searchterm) throws IOException, SAXException { - if (cache.containsKey(searchterm)) - return Arrays.asList(searchterm); + synchronized (cache) { + if (getFoundName(searchterm) != null) { + return Arrays.asList(getFoundName(searchterm)); + } + } Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm)); List nodes = XPathUtil.selectNodes("//TABLE[@class='anime_list']//TR//TD//ancestor::TR", dom); - ArrayList shows = new ArrayList(nodes.size()); + + LinkedHashMap searchResults = new LinkedHashMap(nodes.size()); if (!nodes.isEmpty()) for (Node node : nodes) { @@ -62,8 +66,7 @@ public class AnidbClient extends EpisodeListClient { try { URL url = new URL("http", host, file); - cache.put(title, url); - shows.add(title); + searchResults.put(title, url); } catch (MalformedURLException e) { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href); } @@ -71,20 +74,22 @@ public class AnidbClient extends EpisodeListClient { } else { // we might have been redirected to the episode list page directly - List results = XPathUtil.selectNodes("//TABLE[@class='eplist']", dom); + List list = XPathUtil.selectNodes("//TABLE[@class='eplist']", dom); - if (!results.isEmpty()) { + if (!list.isEmpty()) { // get show's name from the document String header = XPathUtil.selectString("//DIV[@id='layout-content']//H1[1]", dom); String title = header.replaceFirst("Anime:\\s*", ""); - cache.put(title, getSearchUrl(searchterm)); - shows.add(title); + searchResults.put(title, getSearchUrl(searchterm)); } - } - return shows; + synchronized (cache) { + cache.putAll(searchResults); + } + + return new ArrayList(searchResults.keySet()); } @@ -124,7 +129,21 @@ public class AnidbClient extends EpisodeListClient { @Override public URL getEpisodeListUrl(String showname, int season) { - return cache.get(showname); + synchronized (cache) { + return cache.get(showname); + } + } + + + @Override + public String getFoundName(String searchterm) { + synchronized (cache) { + if (cache.containsKey(searchterm)) { + return cache.floorKey(searchterm); + } + } + + return null; } diff --git a/source/net/sourceforge/filebot/web/EpisodeListClient.java b/source/net/sourceforge/filebot/web/EpisodeListClient.java index 99b9b0b9..3ba5741a 100644 --- a/source/net/sourceforge/filebot/web/EpisodeListClient.java +++ b/source/net/sourceforge/filebot/web/EpisodeListClient.java @@ -42,11 +42,12 @@ public abstract class EpisodeListClient { public abstract List search(String searchterm) throws Exception; + public abstract String getFoundName(String searchterm); + + /** * @param showname * @param season number of season, 0 for all seasons - * @return - * @throws Exception */ public abstract List getEpisodeList(String showname, int season) throws Exception; diff --git a/source/net/sourceforge/filebot/web/TVRageClient.java b/source/net/sourceforge/filebot/web/TVRageClient.java index 618ab699..9e2c1471 100644 --- a/source/net/sourceforge/filebot/web/TVRageClient.java +++ b/source/net/sourceforge/filebot/web/TVRageClient.java @@ -9,10 +9,10 @@ import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -28,7 +28,7 @@ import org.xml.sax.SAXException; public class TVRageClient extends EpisodeListClient { - private Map cache = Collections.synchronizedMap(new HashMap()); + private NavigableMap cache = new TreeMap(String.CASE_INSENSITIVE_ORDER); private String host = "www.tvrage.com"; @@ -40,15 +40,17 @@ public class TVRageClient extends EpisodeListClient { @Override public List search(String searchterm) throws IOException, SAXException { - if (cache.containsKey(searchterm)) { - return Arrays.asList(searchterm); + synchronized (cache) { + if (getFoundName(searchterm) != null) { + return Arrays.asList(getFoundName(searchterm)); + } } Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm)); List nodes = XPathUtil.selectNodes("id('search_begin')//TABLE[1]/*/TR/TD/A[1]", dom); - ArrayList shows = new ArrayList(nodes.size()); + LinkedHashMap searchResults = new LinkedHashMap(nodes.size()); for (Node node : nodes) { String href = XPathUtil.selectString("@href", node); @@ -56,14 +58,17 @@ public class TVRageClient extends EpisodeListClient { try { URL url = new URL(href); - cache.put(title, url); - shows.add(title); + searchResults.put(title, url); } catch (MalformedURLException e) { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e); } } - return shows; + synchronized (cache) { + cache.putAll(searchResults); + } + + return new ArrayList(searchResults.keySet()); } @@ -100,8 +105,8 @@ public class TVRageClient extends EpisodeListClient { } else { episodeNumber = seasonAndEpisodeNumber; } - episodes.add(new Episode(showname, seasonNumber, episodeNumber, title)); + } } } @@ -113,21 +118,39 @@ public class TVRageClient extends EpisodeListClient { @Override public URL getEpisodeListUrl(String showname, int season) { try { - URL baseUrl = cache.get(showname); + URL baseUrl = null; + + synchronized (cache) { + baseUrl = cache.get(showname); + } String seasonString = "all"; - if (season >= 1) + if (season >= 1) { seasonString = Integer.toString(season); + } String file = baseUrl.getFile() + "/episode_list/" + seasonString; + return new URL("http", host, file); } catch (Exception e) { - throw new RuntimeException("Cannot determine URL of episode listing for " + showname, e); + return null; } } + @Override + public String getFoundName(String searchterm) { + synchronized (cache) { + if (cache.containsKey(searchterm)) { + return cache.floorKey(searchterm); + } + } + + return null; + } + + private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException { String qs = URLEncoder.encode(searchterm, "UTF-8"); String file = "/search.php?search=" + qs; diff --git a/source/net/sourceforge/filebot/web/TvdotcomClient.java b/source/net/sourceforge/filebot/web/TvdotcomClient.java index 1509e57a..1bda1d14 100644 --- a/source/net/sourceforge/filebot/web/TvdotcomClient.java +++ b/source/net/sourceforge/filebot/web/TvdotcomClient.java @@ -10,10 +10,10 @@ import java.net.URLEncoder; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -27,7 +27,7 @@ import org.xml.sax.SAXException; public class TvdotcomClient extends EpisodeListClient { - private Map cache = Collections.synchronizedMap(new HashMap()); + private NavigableMap cache = new TreeMap(String.CASE_INSENSITIVE_ORDER); private String host = "www.tv.com"; @@ -39,15 +39,17 @@ public class TvdotcomClient extends EpisodeListClient { @Override public List search(String searchterm) throws UnsupportedEncodingException, MalformedURLException, IOException, SAXException { - if (cache.containsKey(searchterm)) { - return Arrays.asList(searchterm); + synchronized (cache) { + if (getFoundName(searchterm) != null) { + return Arrays.asList(getFoundName(searchterm)); + } } Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm)); List nodes = XPathUtil.selectNodes("id('search-results')//SPAN/A", dom); - ArrayList shows = new ArrayList(nodes.size()); + LinkedHashMap searchResults = new LinkedHashMap(nodes.size()); for (Node node : nodes) { String category = node.getParentNode().getTextContent(); @@ -60,15 +62,18 @@ public class TvdotcomClient extends EpisodeListClient { try { URL url = new URL(href); - cache.put(title, url); - shows.add(title); + searchResults.put(title, url); } catch (MalformedURLException e) { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e); } } } - return shows; + synchronized (cache) { + cache.putAll(searchResults); + } + + return new ArrayList(searchResults.keySet()); } @@ -121,18 +126,34 @@ public class TvdotcomClient extends EpisodeListClient { @Override public URL getEpisodeListUrl(String showname, int season) { try { - String summaryFile = cache.get(showname).getFile(); + String summaryFile = null; + + synchronized (cache) { + summaryFile = cache.get(showname).getFile(); + } String base = summaryFile.substring(0, summaryFile.indexOf("summary.html")); String episodelistFile = base + "episode_listings.html&season=" + season; return new URL("http", host, episodelistFile); } catch (Exception e) { - throw new RuntimeException("Cannot determine URL of episode listing for " + showname, e); + return null; } } + @Override + public String getFoundName(String searchterm) { + synchronized (cache) { + if (cache.containsKey(searchterm)) { + return cache.floorKey(searchterm); + } + } + + return null; + } + + private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException { String qs = URLEncoder.encode(searchterm, "UTF-8"); String file = "/search.php?qs=" + qs + "&type=11&stype=all"; diff --git a/source/net/sourceforge/tuned/ui/TextCompletion.java b/source/net/sourceforge/tuned/ui/TextCompletion.java index 58d9d975..0757532f 100644 --- a/source/net/sourceforge/tuned/ui/TextCompletion.java +++ b/source/net/sourceforge/tuned/ui/TextCompletion.java @@ -37,37 +37,37 @@ public class TextCompletion { } - public void addCompletionTerm(String term) { + public void addTerm(String term) { completionTerms.add(term); } - public void addCompletionTerms(Collection terms) { + public void addTerms(Collection terms) { completionTerms.addAll(terms); } - public void removeCompletionTerm(String term) { + public void removeTerm(String term) { completionTerms.remove(term); } - public void removeCompletionTerms(Collection terms) { + public void removeTerms(Collection terms) { completionTerms.removeAll(terms); } - public void setCompletionStartLength(int codeCompletionStartLength) { + public void setStartLength(int codeCompletionStartLength) { this.completionStartLength = codeCompletionStartLength; } - public Set getCompletionTerms() { + public Set getTerms() { return completionTerms; } - public int getCompletionStartLength() { + public int getStartLength() { return completionStartLength; }