/**
* MIT License
*
* Copyright (c) 2017 zgqq
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package mah.plugin.support.translation;
import mah.common.util.IoUtils;
import mah.common.util.StringUtils;
import mah.common.util.XmlUtils;
import mah.plugin.PluginException;
import mah.plugin.command.PluginCommandSupport;
import mah.plugin.config.XmlConfigurable;
import mah.plugin.support.translation.parser.WordParser;
import mah.plugin.support.translation.parser.YoudaoParser;
import mah.plugin.support.translation.repo.JsonUtils;
import mah.plugin.support.translation.repo.WordRepository;
import mah.ui.icon.Icon;
import mah.ui.layout.ClassicItemListLayout;
import mah.ui.pane.item.FullItemImpl;
import mah.ui.pane.item.TextItemImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by zgq on 16-12-24.
*/
public class TranslationCommand extends PluginCommandSupport implements XmlConfigurable {
private ClassicItemListLayout layout;
private String keyfrom ;
private String apiKey;
private static final Logger LOGGER = LoggerFactory.getLogger(TranslationCommand.class);
private volatile WordRepository repository;
private final WordParser parser = new YoudaoParser(JsonUtils.getJsonFactory());
private String lastWord;
public TranslationCommand() {
init();
}
private void init() {
addInitializeEventHandler(event -> {
layout = getLayoutFactory().createClassicItemListLayout();
localWordReader();
});
addCommonFilterEventHandler(event -> {
filterHook(event.getContent());
});
addTriggerEventHandler(event -> {
triggerHook();
});
}
private void localWordReader() {
String pluginDataFile = "";
try {
pluginDataFile = getPluginDataDir("local_words.json");
repository = new WordRepository(pluginDataFile);
repository.init();
} catch (Exception e) {
if (e.getCause() instanceof FileNotFoundException) {
try {
IoUtils.createFileIfNotExists(pluginDataFile);
} catch (Exception e2) {
LOGGER.error("Fail to create new file " + pluginDataFile, e2);
}
} else {
LOGGER.error("Fail to initialize repository", e);
}
}
}
private FullItemImpl createTranslationResult(String name, String description) {
FullItemImpl fullItem = new FullItemImpl.Builder(name)
.description(description)
.icon(Icon.valueOf("translation/translate.png"))
.build();
return fullItem;
}
private FullItemImpl createTriggerResult() {
return createTranslationResult("英汉互译", "Thanks to youdao api");
}
private void showConfigError() {
TextItemImpl tip = new TextItemImpl.Builder("To use translation command,"
+ "you are expected to provide own keyfrom and apikey "
).build();
TextItemImpl tips2 = new TextItemImpl
.Builder("You can go to http://fanyi.youdao.com/openapi.do to apply for them")
.build();
layout.updateItems(tip,tips2);
}
private boolean checkConfig() {
if (StringUtils.isEmpty(this.apiKey) || StringUtils.isEmpty(this.keyfrom)) {
showConfigError();
return false;
}
return true;
}
public void triggerHook() throws Exception {
if (checkConfig()) {
layout.updateItems(createTriggerResult());
}
}
@Override
public String getName() {
return "Translation";
}
public void filterHook(String content) throws Exception {
if (checkConfig()) {
if (content.equals(" ")) {
triggerHook();
}
String word = content;
if (word.equals(this.lastWord)) {
return;
}
this.lastWord = word;
submit(new Queryer(keyfrom, apiKey, word));
}
}
private Word parseWord(String content) {
return parser.parseWord(content);
}
private String getLastWord() {
return lastWord;
}
private void update(Word word) {
List<String> translations = word.getTranslations();
if (translations == null) {
layout.updateItems(createTranslationResult("查询出错", "找不到这个词"));
return;
}
List<String> explains = word.getExplains();
String description = "";
if (explains != null) {
StringBuilder explainStr = new StringBuilder();
for (Object explain : explains) {
String expl = ((String) explain);
explainStr.append(expl + ",");
}
explainStr.deleteCharAt(explainStr.length() - 1);
description = explainStr.toString();
}
List<FullItemImpl> items = new ArrayList<>();
items.add(createTranslationResult(buildDescription(translations), description));
List<Word.MeaningPair> meaningPairs = word.getMeaningPairs();
if (meaningPairs != null) {
for (Word.MeaningPair meaningPair : meaningPairs) {
items.add(createTranslationResult(meaningPair.getKey(), buildDescription(meaningPair.getValue())));
}
}
layout.updateItems(items);
}
private String buildDescription(List<String> items) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
sb.append(items.get(i));
if (i != items.size() - 1) {
sb.append(",");
}
}
return sb.toString();
}
private Word getWord(String word) {
return repository.findWord(word);
}
@Override
public void configure(Node node) throws Exception {
if (node == null) {
return;
}
String keyfrom = XmlUtils.getChildNodeText(node, "keyfrom");
String apikey = XmlUtils.getChildNodeText(node, "apikey");
this.keyfrom = keyfrom;
this.apiKey = apikey;
}
public ClassicItemListLayout getLayout() {
return layout;
}
class Queryer implements Runnable {
private final String keyfrom;
private final String apiKey;
private final String word;
public Queryer(String keyfrom, String apiKey, String word) {
this.keyfrom = keyfrom;
this.apiKey = apiKey;
this.word = word;
}
private boolean currentWord() {
return getLastWord().equals(word);
}
private void updateUi(Word word) {
if (currentWord()) {
update(word);
}
}
private boolean qualifySaveWord() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new PluginException(e);
}
return currentWord();
}
private void delayToUpdateWord(Word word) {
if (!qualifySaveWord()) {
return;
}
LOGGER.info("save word {}", word);
word.incrementQueryCount();
word.updateQueryTime();
repository.save();
LOGGER.info("saved word", word);
}
@Override
public void run() {
try {
if (StringUtils.isEmpty(word)) {
layout.clear();
return;
}
Word wordObj = getWord(word);
if (wordObj != null) {
updateUi(wordObj);
delayToUpdateWord(wordObj);
return;
}
layout.showFirstItem(createTranslationResult(word,"正在查询中..."));
Thread.sleep(300);
if (!currentWord()) {
return;
}
String apiUrl = "http://fanyi.youdao.com/openapi.do";
Map<String, String> params = new HashMap<>();
params.put("keyfrom", keyfrom);
params.put("key", apiKey);
params.put("type", "data");
params.put("doctype", "json");
params.put("version", "1.1");
params.put("q", this.word);
StringBuilder paramsStr = new StringBuilder();
params.forEach((s, s2) -> {
try {
paramsStr.append(s + "=" + URLEncoder.encode(s2, "UTF-8") + "&");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
});
paramsStr.deleteCharAt(paramsStr.length() - 1);
HttpURLConnection urlConnection = null;
URL url = new URL(apiUrl + "?" + paramsStr.toString());
LOGGER.debug("fetch " + url);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.connect();
InputStream inputStream = urlConnection.getInputStream();
String content = IoUtils.toString(inputStream);
wordObj = parseWord(content);
updateUi(wordObj);
if (urlConnection != null) {
urlConnection.disconnect();
}
if (qualifySaveWord()) {
if (wordObj.getExplains() != null) {
repository.addWord(wordObj);
}
}
} catch (Exception e) {
showFailure();
LOGGER.error("query fail", e);
}
}
private void showFailure() {
layout.updateItems(new FullItemImpl.Builder("查询失败,请检查网络是否连接").build());
}
}
}