* ignore non-subtitles files when extracting archives
* download subtitle package on keystroke ENTER * renamed OpenSubtitlesClient to OpenSubtitlesXmlRpc * renamed OpenSubtitlesSubtitleClient to OpenSubtitlesClient * refactoring
This commit is contained in:
parent
19d5b576db
commit
f897837811
|
@ -3,7 +3,6 @@ package net.sourceforge.filebot.subtitle;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -40,8 +39,10 @@ public class MicroDVDReader extends SubtitleReader {
|
||||||
from = to + 1;
|
from = to + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.size() < 2)
|
if (properties.size() < 2) {
|
||||||
|
// ignore illegal lines
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
long startFrame = Long.parseLong(properties.get(0));
|
long startFrame = Long.parseLong(properties.get(0));
|
||||||
long endFrame = Long.parseLong(properties.get(1));
|
long endFrame = Long.parseLong(properties.get(1));
|
||||||
|
@ -56,7 +57,7 @@ public class MicroDVDReader extends SubtitleReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// translate '|' to new lines
|
// translate '|' to new lines
|
||||||
List<String> lines = Arrays.asList(text.split(Pattern.quote("|")));
|
String[] lines = text.split(Pattern.quote("|"));
|
||||||
|
|
||||||
// convert frame interval to time interval
|
// convert frame interval to time interval
|
||||||
return new SubtitleElement(Math.round(startFrame * fps), Math.round(endFrame * fps), join(lines, "\n"));
|
return new SubtitleElement(Math.round(startFrame * fps), Math.round(endFrame * fps), join(lines, "\n"));
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class SubRipReader extends SubtitleReader {
|
||||||
lines.add(line);
|
lines.add(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
return new SubtitleElement(t1, t2, join(lines.toArray(), "\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,9 +74,10 @@ public class SubStationAlphaReader extends SubtitleReader {
|
||||||
long end = timeFormat.parse(row[format.get("End")]).getTime();
|
long end = timeFormat.parse(row[format.get("End")]).getTime();
|
||||||
String text = row[format.get("Text")].trim();
|
String text = row[format.get("Text")].trim();
|
||||||
|
|
||||||
|
// translate "\\n" to new lines
|
||||||
String[] lines = Pattern.compile(Pattern.quote("\\N"), Pattern.CASE_INSENSITIVE).split(text);
|
String[] lines = Pattern.compile(Pattern.quote("\\N"), Pattern.CASE_INSENSITIVE).split(text);
|
||||||
|
|
||||||
return new SubtitleElement(start, end, join(Arrays.asList(lines), "\n"));
|
return new SubtitleElement(start, end, join(lines, "\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,7 @@ package net.sourceforge.filebot.subtitle;
|
||||||
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.InputMismatchException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -24,20 +23,22 @@ public class SubViewerReader extends SubtitleReader {
|
||||||
// element starts with interval (e.g. 00:42:16.33,00:42:19.39)
|
// element starts with interval (e.g. 00:42:16.33,00:42:19.39)
|
||||||
String[] interval = scanner.nextLine().split(",", 2);
|
String[] interval = scanner.nextLine().split(",", 2);
|
||||||
|
|
||||||
if (interval.length < 2 || interval[0].startsWith("["))
|
if (interval.length < 2 || interval[0].startsWith("[")) {
|
||||||
|
// ignore property lines
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
long t1 = timeFormat.parse(interval[0]).getTime();
|
long t1 = timeFormat.parse(interval[0]).getTime();
|
||||||
long t2 = timeFormat.parse(interval[1]).getTime();
|
long t2 = timeFormat.parse(interval[1]).getTime();
|
||||||
|
|
||||||
// append subtitle line
|
// translate [br] to new lines
|
||||||
List<String> lines = new ArrayList<String>(2);
|
String[] lines = scanner.nextLine().split(Pattern.quote("[br]"));
|
||||||
|
|
||||||
for (String text : scanner.nextLine().split(Pattern.quote("[br]"))) {
|
|
||||||
lines.add(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
||||||
|
} catch (InputMismatchException e) {
|
||||||
|
// can't parse interval, ignore line
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,14 +40,6 @@ public enum SubtitleFormat {
|
||||||
public SubtitleReader newReader(Readable readable) {
|
public SubtitleReader newReader(Readable readable) {
|
||||||
return new SubStationAlphaReader(new Scanner(readable));
|
return new SubStationAlphaReader(new Scanner(readable));
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
SAMI {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SubtitleReader newReader(Readable readable) {
|
|
||||||
throw new UnsupportedOperationException("SAMI reader not implemented");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public abstract SubtitleReader newReader(Readable readable);
|
public abstract SubtitleReader newReader(Readable readable);
|
||||||
|
|
|
@ -65,13 +65,13 @@ public abstract class SubtitleReader implements Iterator<SubtitleElement>, Close
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String join(Iterable<?> values, String delimiter) {
|
protected String join(Object[] values, String delimiter) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
|
for (int i = 0; i < values.length; i++) {
|
||||||
sb.append(iterator.next());
|
sb.append(values[i]);
|
||||||
|
|
||||||
if (iterator.hasNext()) {
|
if (i < values.length - 1) {
|
||||||
sb.append(delimiter);
|
sb.append(delimiter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.io.InputStreamReader;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
|
@ -46,6 +47,7 @@ import ca.odell.glazedlists.FilterList;
|
||||||
import ca.odell.glazedlists.GlazedLists;
|
import ca.odell.glazedlists.GlazedLists;
|
||||||
import ca.odell.glazedlists.ListSelection;
|
import ca.odell.glazedlists.ListSelection;
|
||||||
import ca.odell.glazedlists.ObservableElementList;
|
import ca.odell.glazedlists.ObservableElementList;
|
||||||
|
import ca.odell.glazedlists.SortedList;
|
||||||
import ca.odell.glazedlists.TextFilterator;
|
import ca.odell.glazedlists.TextFilterator;
|
||||||
import ca.odell.glazedlists.matchers.MatcherEditor;
|
import ca.odell.glazedlists.matchers.MatcherEditor;
|
||||||
import ca.odell.glazedlists.swing.EventListModel;
|
import ca.odell.glazedlists.swing.EventListModel;
|
||||||
|
@ -77,7 +79,7 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
|
|
||||||
|
|
||||||
public SubtitleDownloadComponent() {
|
public SubtitleDownloadComponent() {
|
||||||
JList packageList = new JList(createPackageListModel());
|
final JList packageList = new JList(createPackageListModel());
|
||||||
packageList.setFixedCellHeight(32);
|
packageList.setFixedCellHeight(32);
|
||||||
packageList.setCellRenderer(renderer);
|
packageList.setCellRenderer(renderer);
|
||||||
|
|
||||||
|
@ -122,6 +124,15 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
scrollPane.setViewportBorder(new LineBorder(fileList.getBackground()));
|
scrollPane.setViewportBorder(new LineBorder(fileList.getBackground()));
|
||||||
add(scrollPane, "newline, hmin max(80px, 30%)");
|
add(scrollPane, "newline, hmin max(80px, 30%)");
|
||||||
|
|
||||||
|
// install fetch action
|
||||||
|
TunedUtilities.installAction(packageList, KeyStroke.getKeyStroke("ENTER"), new AbstractAction("Fetch") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
fetchAll(packageList.getSelectedValues());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// install open action
|
// install open action
|
||||||
TunedUtilities.installAction(fileList, KeyStroke.getKeyStroke("ENTER"), new AbstractAction("Open") {
|
TunedUtilities.installAction(fileList, KeyStroke.getKeyStroke("ENTER"), new AbstractAction("Open") {
|
||||||
|
|
||||||
|
@ -159,8 +170,20 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
|
|
||||||
|
|
||||||
protected ListModel createFileListModel() {
|
protected ListModel createFileListModel() {
|
||||||
|
// source list
|
||||||
|
EventList<MemoryFile> source = getFileModel();
|
||||||
|
|
||||||
|
// sort by name
|
||||||
|
source = new SortedList<MemoryFile>(source, new Comparator<MemoryFile>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(MemoryFile m1, MemoryFile m2) {
|
||||||
|
return m1.getName().compareToIgnoreCase(m2.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// as list model
|
// as list model
|
||||||
return new EventListModel<MemoryFile>(getFileModel());
|
return new EventListModel<MemoryFile>(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,7 +256,12 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
private void open(Object[] selection) {
|
private void open(Object[] selection) {
|
||||||
try {
|
try {
|
||||||
for (Object object : selection) {
|
for (Object object : selection) {
|
||||||
open((MemoryFile) object);
|
MemoryFile file = (MemoryFile) object;
|
||||||
|
|
||||||
|
// only open subtitle files
|
||||||
|
if (SUBTITLE_FILES.accept(file.getName())) {
|
||||||
|
open(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
|
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
|
||||||
|
@ -260,7 +288,7 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (reader.hasNext()) {
|
if (reader.hasNext()) {
|
||||||
// correct format
|
// correct format found
|
||||||
List<SubtitleElement> list = new ArrayList<SubtitleElement>(500);
|
List<SubtitleElement> list = new ArrayList<SubtitleElement>(500);
|
||||||
|
|
||||||
// read subtitle file
|
// read subtitle file
|
||||||
|
@ -275,15 +303,13 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
viewer.setData(list);
|
viewer.setData(list);
|
||||||
viewer.setVisible(true);
|
viewer.setVisible(true);
|
||||||
|
|
||||||
// done
|
// we're done
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
reader.close();
|
reader.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IOException("Cannot read subtitle format");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -447,6 +473,7 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
|
|
||||||
final Object[] selection = list.getSelectedValues();
|
final Object[] selection = list.getSelectedValues();
|
||||||
|
|
||||||
|
if (selection.length > 0) {
|
||||||
JPopupMenu contextMenu = new JPopupMenu();
|
JPopupMenu contextMenu = new JPopupMenu();
|
||||||
|
|
||||||
// Open
|
// Open
|
||||||
|
@ -470,6 +497,7 @@ public class SubtitleDownloadComponent extends JComponent {
|
||||||
contextMenu.show(e.getComponent(), e.getX(), e.getY());
|
contextMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
import static java.util.Collections.*;
|
import static java.util.Collections.*;
|
||||||
|
import static net.sourceforge.filebot.FileBotUtilities.*;
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
@ -181,17 +182,19 @@ public class SubtitlePackage {
|
||||||
List<MemoryFile> vfs = new ArrayList<MemoryFile>();
|
List<MemoryFile> vfs = new ArrayList<MemoryFile>();
|
||||||
|
|
||||||
for (MemoryFile file : archiveType.fromData(data)) {
|
for (MemoryFile file : archiveType.fromData(data)) {
|
||||||
|
if (SUBTITLE_FILES.accept(file.getName())) {
|
||||||
|
// add subtitle files, ignore non-subtitle files
|
||||||
|
vfs.add(file);
|
||||||
|
} else {
|
||||||
// check if file is a supported archive
|
// check if file is a supported archive
|
||||||
ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName()));
|
ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName()));
|
||||||
|
|
||||||
if (type == ArchiveType.UNDEFINED) {
|
if (type != ArchiveType.UNDEFINED) {
|
||||||
// add subtitle file
|
|
||||||
vfs.add(file);
|
|
||||||
} else {
|
|
||||||
// extract nested archives recursively
|
// extract nested archives recursively
|
||||||
vfs.addAll(extract(type, file.getData()));
|
vfs.addAll(extract(type, file.getData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return vfs;
|
return vfs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import javax.swing.JComboBox;
|
||||||
import net.sourceforge.filebot.Settings;
|
import net.sourceforge.filebot.Settings;
|
||||||
import net.sourceforge.filebot.ui.AbstractSearchPanel;
|
import net.sourceforge.filebot.ui.AbstractSearchPanel;
|
||||||
import net.sourceforge.filebot.ui.SelectDialog;
|
import net.sourceforge.filebot.ui.SelectDialog;
|
||||||
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleClient;
|
import net.sourceforge.filebot.web.OpenSubtitlesClient;
|
||||||
import net.sourceforge.filebot.web.SearchResult;
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
import net.sourceforge.filebot.web.SublightSubtitleClient;
|
import net.sourceforge.filebot.web.SublightSubtitleClient;
|
||||||
import net.sourceforge.filebot.web.SubsceneSubtitleClient;
|
import net.sourceforge.filebot.web.SubsceneSubtitleClient;
|
||||||
|
@ -79,7 +79,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
||||||
@Override
|
@Override
|
||||||
protected SubtitleProvider[] createSearchEngines() {
|
protected SubtitleProvider[] createSearchEngines() {
|
||||||
return new SubtitleProvider[] {
|
return new SubtitleProvider[] {
|
||||||
new OpenSubtitlesSubtitleClient(String.format("%s v%s", getApplicationName(), getApplicationVersion())),
|
new OpenSubtitlesClient(String.format("%s %s", getApplicationName(), getApplicationVersion())),
|
||||||
new SubsceneSubtitleClient(),
|
new SubsceneSubtitleClient(),
|
||||||
new SublightSubtitleClient(getApplicationName(), Settings.userRoot().get("sublight.apikey")),
|
new SublightSubtitleClient(getApplicationName(), Settings.userRoot().get("sublight.apikey")),
|
||||||
new SubtitleSourceClient()
|
new SubtitleSourceClient()
|
||||||
|
|
|
@ -2,212 +2,101 @@
|
||||||
package net.sourceforge.filebot.web;
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Pattern;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property;
|
import javax.swing.Icon;
|
||||||
import redstone.xmlrpc.XmlRpcClient;
|
|
||||||
import redstone.xmlrpc.XmlRpcException;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
import redstone.xmlrpc.XmlRpcFault;
|
import net.sourceforge.tuned.Timer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client for the OpenSubtitles XML-RPC API.
|
* SubtitleClient for OpenSubtitles.
|
||||||
*/
|
*/
|
||||||
public class OpenSubtitlesClient {
|
public class OpenSubtitlesClient implements SubtitleProvider {
|
||||||
|
|
||||||
private static final String url = "http://www.opensubtitles.org/xml-rpc";
|
private final OpenSubtitlesXmlRpc xmlrpc;
|
||||||
|
|
||||||
private final String clientInfo;
|
|
||||||
|
|
||||||
private String token = null;
|
|
||||||
|
|
||||||
|
|
||||||
public OpenSubtitlesClient(String clientInfo) {
|
public OpenSubtitlesClient(String useragent) {
|
||||||
this.clientInfo = clientInfo;
|
this.xmlrpc = new OpenSubtitlesXmlRpc(useragent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Login as anonymous user
|
public String getName() {
|
||||||
*
|
return "OpenSubtitles";
|
||||||
* @throws XmlRpcFault
|
|
||||||
*/
|
|
||||||
public void loginAnonymous() throws XmlRpcFault {
|
|
||||||
login("", "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Login as user and use English as language
|
public Icon getIcon() {
|
||||||
*
|
return ResourceManager.getIcon("search.opensubtitles");
|
||||||
* @param username
|
|
||||||
* @param password
|
|
||||||
* @throws XmlRpcFault
|
|
||||||
*/
|
|
||||||
public void login(String username, String password) throws XmlRpcFault {
|
|
||||||
login(username, password, "en");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* This will login user. This method should be called always when starting talking with
|
public List<SearchResult> search(String query) throws Exception {
|
||||||
* server.
|
// require login
|
||||||
*
|
login();
|
||||||
* @param username username (blank for anonymous user)
|
|
||||||
* @param password password (blank for anonymous user)
|
|
||||||
* @param language <a
|
|
||||||
* href="http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes">ISO639</a>
|
|
||||||
* 2-letter codes as language and later communication will be done in this
|
|
||||||
* language if applicable (error codes and so on).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized void login(String username, String password, String language) throws XmlRpcFault {
|
List<SearchResult> results = (List) xmlrpc.searchMoviesOnIMDB(query);
|
||||||
|
|
||||||
Map<String, String> response = (Map<String, String>) invoke("LogIn", username, password, language, clientInfo);
|
return results;
|
||||||
checkStatus(response.get("status"));
|
|
||||||
|
|
||||||
token = response.get("token");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* This will logout user (ends session id). Good call this function is before ending
|
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws Exception {
|
||||||
* (closing) clients program.
|
// require login
|
||||||
*
|
login();
|
||||||
* @throws XmlRpcFault
|
|
||||||
*/
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized void logout() throws XmlRpcFault {
|
List<SubtitleDescriptor> subtitles = (List) xmlrpc.searchSubtitles(((MovieDescriptor) searchResult).getImdbId(), languageName);
|
||||||
|
|
||||||
|
return subtitles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getSubtitleListLink(SearchResult searchResult, String languageName) {
|
||||||
|
//TODO provide link
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected synchronized void login() throws Exception {
|
||||||
|
if (!xmlrpc.isLoggedOn()) {
|
||||||
|
xmlrpc.loginAnonymous();
|
||||||
|
}
|
||||||
|
|
||||||
|
logoutTimer.set(10, TimeUnit.MINUTES, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected synchronized void logout() {
|
||||||
|
if (xmlrpc.isLoggedOn()) {
|
||||||
try {
|
try {
|
||||||
invoke("LogOut", token);
|
xmlrpc.logout();
|
||||||
|
|
||||||
// anonymous users will always get a 401 Unauthorized when trying to logout
|
|
||||||
// do not check status for logout response
|
|
||||||
// checkStatus(response.get("status"));
|
|
||||||
} finally {
|
|
||||||
token = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized boolean isLoggedOn() {
|
|
||||||
return token != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether status is OK or not
|
|
||||||
*
|
|
||||||
* @param status status code and message (e.g. 200 OK, 401 Unauthorized, ...)
|
|
||||||
* @throws XmlRpcFault thrown if status code is not OK
|
|
||||||
*/
|
|
||||||
private void checkStatus(String status) throws XmlRpcFault {
|
|
||||||
if (status.equals("200 OK"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Matcher m = Pattern.compile("(\\d+).*").matcher(status);
|
|
||||||
|
|
||||||
if (!m.matches())
|
|
||||||
throw new XmlRpcException("Illegal status code: " + status);
|
|
||||||
|
|
||||||
throw new XmlRpcFault(Integer.parseInt(m.group(1)), status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Object invoke(String method, Object... arguments) throws XmlRpcFault {
|
|
||||||
try {
|
|
||||||
XmlRpcClient rpc = new XmlRpcClient(url, false);
|
|
||||||
return rpc.invoke(method, arguments);
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This simple function returns basic server info.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Map<String, String> getServerInfo() throws XmlRpcFault {
|
|
||||||
return (Map<String, String>) invoke("ServerInfo", token);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int imdbid, String languageName) throws XmlRpcFault {
|
|
||||||
|
|
||||||
Map<String, String> searchListEntry = new HashMap<String, String>(2);
|
|
||||||
|
|
||||||
// pad imdbId with zeros
|
|
||||||
//TODO needed???
|
|
||||||
searchListEntry.put("imdbid", String.format("%07d", imdbid));
|
|
||||||
searchListEntry.put("sublanguageid", getSubLanguageID(languageName));
|
|
||||||
|
|
||||||
List<Map<String, String>> searchList = Collections.singletonList(searchListEntry);
|
|
||||||
|
|
||||||
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, searchList);
|
|
||||||
|
|
||||||
List<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (Map<String, String> subtitleData : response.get("data")) {
|
|
||||||
subs.add(new OpenSubtitlesSubtitleDescriptor(Property.asEnumMap(subtitleData)));
|
|
||||||
}
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
// if the response is an error message, generic types won't match
|
|
||||||
throw new XmlRpcException("Illegal response: " + response.toString(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return subs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String getSubLanguageID(String languageName) {
|
|
||||||
//TODO test if sublanguageid is really ISO3 language code
|
|
||||||
throw new UnsupportedOperationException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
|
|
||||||
|
|
||||||
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchMoviesOnIMDB", token, query);
|
|
||||||
|
|
||||||
ArrayList<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
|
|
||||||
|
|
||||||
for (Map<String, String> movie : response.get("data")) {
|
|
||||||
String title = movie.get("title");
|
|
||||||
|
|
||||||
// get end index of first non-aka title (aka titles are separated by Â)
|
|
||||||
int endIndex = title.indexOf('\u00C2');
|
|
||||||
|
|
||||||
if (endIndex > 0) {
|
|
||||||
title = title.substring(0, endIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
movies.add(new MovieDescriptor(title, Integer.parseInt(movie.get("year")), Integer.parseInt(movie.get("id"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
return movies;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public boolean noOperation() {
|
|
||||||
try {
|
|
||||||
Map<String, String> response = (Map<String, String>) invoke("NoOperation", token);
|
|
||||||
checkStatus(response.get("status"));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Logout failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logoutTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected final Timer logoutTimer = new Timer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.web;
|
|
||||||
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.swing.Icon;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
|
||||||
import net.sourceforge.tuned.Timer;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SubtitleClient for OpenSubtitles.
|
|
||||||
*/
|
|
||||||
public class OpenSubtitlesSubtitleClient implements SubtitleProvider {
|
|
||||||
|
|
||||||
private final OpenSubtitlesClient client;
|
|
||||||
|
|
||||||
|
|
||||||
public OpenSubtitlesSubtitleClient(String clientInfo) {
|
|
||||||
this.client = new OpenSubtitlesClient(clientInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "OpenSubtitles";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Icon getIcon() {
|
|
||||||
return ResourceManager.getIcon("search.opensubtitles");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<SearchResult> search(String query) throws Exception {
|
|
||||||
// require login
|
|
||||||
login();
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<SearchResult> results = (List) client.searchMoviesOnIMDB(query);
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws Exception {
|
|
||||||
// require login
|
|
||||||
login();
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<SubtitleDescriptor> subtitles = (List) client.searchSubtitles(((MovieDescriptor) searchResult).getImdbId(), languageName);
|
|
||||||
|
|
||||||
return subtitles;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URI getSubtitleListLink(SearchResult searchResult, String languageName) {
|
|
||||||
//TODO provide link
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected synchronized void login() throws Exception {
|
|
||||||
if (!client.isLoggedOn()) {
|
|
||||||
client.loginAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
logoutTimer.set(10, TimeUnit.MINUTES, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected synchronized void logout() {
|
|
||||||
if (client.isLoggedOn()) {
|
|
||||||
try {
|
|
||||||
client.logout();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Logout failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logoutTimer.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected final Timer logoutTimer = new Timer() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
logout();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -2,18 +2,21 @@
|
||||||
package net.sourceforge.filebot.web;
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ByteBufferOutputStream;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a subtitle on OpenSubtitles.
|
* Describes a subtitle on OpenSubtitles.
|
||||||
*
|
*
|
||||||
* @see OpenSubtitlesClient
|
* @see OpenSubtitlesXmlRpc
|
||||||
*/
|
*/
|
||||||
public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
|
public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
|
||||||
|
|
||||||
|
@ -75,32 +78,49 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Map<Property, String> getProperties() {
|
public String getProperty(Property key) {
|
||||||
return Collections.unmodifiableMap(properties);
|
return properties.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return properties.get(Property.SubFileName);
|
return getProperty(Property.SubFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLanguageName() {
|
public String getLanguageName() {
|
||||||
return properties.get(Property.LanguageName);
|
return getProperty(Property.LanguageName);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBuffer fetch() throws Exception {
|
|
||||||
return WebRequest.fetch(new URL(properties.get(Property.ZipDownloadLink)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "zip";
|
return getProperty(Property.SubFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return Integer.parseInt(getProperty(Property.SubSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer fetch() throws Exception {
|
||||||
|
URL resource = new URL(getProperty(Property.SubDownloadLink));
|
||||||
|
InputStream stream = new GZIPInputStream(resource.openStream());
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBufferOutputStream buffer = new ByteBufferOutputStream(getSize());
|
||||||
|
|
||||||
|
// read all
|
||||||
|
buffer.transferFully(stream);
|
||||||
|
|
||||||
|
return buffer.getByteBuffer();
|
||||||
|
} finally {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.util.Collections.*;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.InputMismatchException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import redstone.xmlrpc.XmlRpcClient;
|
||||||
|
import redstone.xmlrpc.XmlRpcException;
|
||||||
|
import redstone.xmlrpc.XmlRpcFault;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for the OpenSubtitles XML-RPC API.
|
||||||
|
*/
|
||||||
|
public class OpenSubtitlesXmlRpc {
|
||||||
|
|
||||||
|
private static final String url = "http://www.opensubtitles.org/xml-rpc";
|
||||||
|
|
||||||
|
private final String useragent;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
|
||||||
|
public OpenSubtitlesXmlRpc(String useragent) {
|
||||||
|
this.useragent = useragent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login as anonymous user
|
||||||
|
*
|
||||||
|
* @throws XmlRpcFault
|
||||||
|
*/
|
||||||
|
public void loginAnonymous() throws XmlRpcFault {
|
||||||
|
login("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login as user and use English as language
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param password
|
||||||
|
* @throws XmlRpcFault
|
||||||
|
*/
|
||||||
|
public void login(String username, String password) throws XmlRpcFault {
|
||||||
|
login(username, password, "en");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will login user. This method should be called always when starting talking with
|
||||||
|
* server.
|
||||||
|
*
|
||||||
|
* @param username username (blank for anonymous user)
|
||||||
|
* @param password password (blank for anonymous user)
|
||||||
|
* @param language <a
|
||||||
|
* href="http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes">ISO639</a>
|
||||||
|
* 2-letter codes as language and later communication will be done in this
|
||||||
|
* language if applicable (error codes and so on).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public synchronized void login(String username, String password, String language) throws XmlRpcFault {
|
||||||
|
Map<String, String> response = (Map<String, String>) invoke("LogIn", username, password, language, useragent);
|
||||||
|
checkStatus(response.get("status"));
|
||||||
|
|
||||||
|
token = response.get("token");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will logout user (ends session id). Good call this function is before ending
|
||||||
|
* (closing) clients program.
|
||||||
|
*
|
||||||
|
* @throws XmlRpcFault
|
||||||
|
*/
|
||||||
|
public synchronized void logout() throws XmlRpcFault {
|
||||||
|
try {
|
||||||
|
invoke("LogOut", token);
|
||||||
|
|
||||||
|
// anonymous users will always get a 401 Unauthorized when trying to logout
|
||||||
|
// do not check status for logout response
|
||||||
|
// checkStatus(response.get("status"));
|
||||||
|
} finally {
|
||||||
|
token = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized boolean isLoggedOn() {
|
||||||
|
return token != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether status is OK or not
|
||||||
|
*
|
||||||
|
* @param status status code and message (e.g. 200 OK, 401 Unauthorized, ...)
|
||||||
|
* @throws XmlRpcFault thrown if status code is not OK
|
||||||
|
*/
|
||||||
|
private void checkStatus(String status) throws XmlRpcFault {
|
||||||
|
if (status.equals("200 OK"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Matcher m = Pattern.compile("(\\d+).*").matcher(status);
|
||||||
|
|
||||||
|
if (!m.matches())
|
||||||
|
throw new XmlRpcException("Illegal status code: " + status);
|
||||||
|
|
||||||
|
throw new XmlRpcFault(Integer.parseInt(m.group(1)), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Object invoke(String method, Object... arguments) throws XmlRpcFault {
|
||||||
|
try {
|
||||||
|
XmlRpcClient rpc = new XmlRpcClient(url, false);
|
||||||
|
return rpc.invoke(method, arguments);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This simple function returns basic server info.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, String> getServerInfo() throws XmlRpcFault {
|
||||||
|
return (Map<String, String>) invoke("ServerInfo", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int imdbid, String languageName) throws XmlRpcFault {
|
||||||
|
Map<String, String> query = new HashMap<String, String>(2);
|
||||||
|
|
||||||
|
query.put("imdbid", String.format("%07d", imdbid));
|
||||||
|
query.put("sublanguageid", getSubLanguageID(languageName));
|
||||||
|
|
||||||
|
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, singletonList(query));
|
||||||
|
|
||||||
|
List<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Map<String, String> subtitleData : response.get("data")) {
|
||||||
|
subs.add(new OpenSubtitlesSubtitleDescriptor(Property.asEnumMap(subtitleData)));
|
||||||
|
}
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// if the response is an error message, generic types won't match
|
||||||
|
throw new XmlRpcException("Illegal response: " + response.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return subs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final Map<String, String> languageMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public String getSubLanguageID(String languageName) throws XmlRpcFault {
|
||||||
|
synchronized (languageMap) {
|
||||||
|
if (languageMap.isEmpty()) {
|
||||||
|
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("GetSubLanguages", "en");
|
||||||
|
|
||||||
|
for (Map<String, String> language : response.get("data")) {
|
||||||
|
languageMap.put(language.get("LanguageName"), language.get("SubLanguageID"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return languageMap.get(languageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
|
||||||
|
|
||||||
|
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchMoviesOnIMDB", token, query);
|
||||||
|
|
||||||
|
List<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
|
||||||
|
Pattern moviePattern = Pattern.compile("(.+) \\((\\d{4})\\).*");
|
||||||
|
|
||||||
|
for (Map<String, String> movie : response.get("data")) {
|
||||||
|
try {
|
||||||
|
// get non-aka title (aka titles are separated by Â)
|
||||||
|
Scanner titleScanner = new Scanner(movie.get("title")).useDelimiter("\u00C2");
|
||||||
|
|
||||||
|
Matcher matcher = moviePattern.matcher(titleScanner.next().trim());
|
||||||
|
|
||||||
|
if (!matcher.matches())
|
||||||
|
throw new InputMismatchException("Cannot parse movie: " + movie);
|
||||||
|
|
||||||
|
String title = matcher.group(1);
|
||||||
|
String year = matcher.group(2);
|
||||||
|
|
||||||
|
movies.add(new MovieDescriptor(title, Integer.parseInt(year), Integer.parseInt(movie.get("id"))));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return movies;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public boolean noOperation() {
|
||||||
|
try {
|
||||||
|
Map<String, String> response = (Map<String, String>) invoke("NoOperation", token);
|
||||||
|
checkStatus(response.get("status"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue