1 #include "tmservice.h"
5 #include "tmdatabase.h"
9 #include "tmeditorwidget.h"
10 #include "tmcandidatewidget.h"
12 #include <QMessageBox>
17 #include <QMutexLocker>
19 #include <QPluginLoader>
21 #include <QJsonDocument>
22 #include <QJsonObject>
30 // WordringConnection ---------------------------------------------------------
32 TM::WordringConnection::WordringConnection(Settings *settings, Service *service)
34 , m_settings(settings)
37 , m_request_limit(1024)
41 bool TM::WordringConnection::find_sentence(sentence_data_type const &sdata)
43 if(m_request_limit < m_requests.size()) return false;
46 jobject["cmd"] = "find";
47 jobject["site"] = sdata.site;
48 jobject["scode"] = (int)sdata.scode;
49 jobject["tcode"] = (int)sdata.tcode;
51 jobject["ssentence"] = sdata.sentence;
52 jobject["previous_crc"] = (int)sdata.previous_crc;
53 jobject["next_crc"] = (int)sdata.next_crc;
57 QJsonDocument jdocument(jobject);
58 assert(jdocument.isObject());
59 QByteArray json = jdocument.toJson();
61 m_requests.enqueue(json);
67 bool TM::WordringConnection::insert_sentence(
68 sentence_data_type const &sdata,
69 sentence_data_type const & tdata)
71 if(m_request_limit < m_requests.size()) return false;
73 QString host = sdata.site;
74 qint32 scode = sdata.scode;
75 qint32 tcode = sdata.tcode;
77 QString sstring = sdata.sentence;
78 QString tstring = tdata.sentence;
79 QByteArray jdata = tdata.json;
83 jobject["cmd"] = "insert";
85 jobject["site"] = host;
86 jobject["scode"] = scode;
87 jobject["tcode"] = tcode;
89 jobject["ssentence"] = sstring;
90 jobject["previous_crc"] = (int)sdata.previous_crc;
91 jobject["next_crc"] = (int)sdata.next_crc;
93 jobject["tsentence"] = tstring;
97 QJsonParseError error;
98 QJsonDocument jlink = QJsonDocument::fromJson(jdata, &error);
100 assert(error.error == QJsonParseError::NoError);
101 if(error.error != QJsonParseError::NoError) return false;
102 jobject["json"] = jlink.object();
105 QJsonDocument jdocument(jobject);
106 assert(jdocument.isObject());
107 QByteArray json = jdocument.toJson(QJsonDocument::Compact);
109 m_requests.enqueue(json);
116 * \brief サーバから訳文を消去します。
118 bool TM::WordringConnection::remove_sentence(
119 sentence_data_type const &sdata,
120 sentence_data_type const &)
124 jobject["cmd"] = "remove";
126 jobject["site"] = sdata.site;
127 jobject["scode"] = (int)sdata.scode;
128 jobject["tcode"] = (int)sdata.tcode;
130 jobject["ssentence"] = sdata.sentence;
131 jobject["previous_crc"] = (int)sdata.previous_crc;
132 jobject["next_crc"] = (int)sdata.next_crc;
134 QJsonDocument jdocument(jobject);
135 assert(jdocument.isObject());
136 QByteArray json = jdocument.toJson(QJsonDocument::Compact);
138 m_requests.enqueue(json);
144 void TM::WordringConnection::onConnected()
146 qDebug() << "WordringConnection::onConnected()";
150 void TM::WordringConnection::onDisconnected()
152 if(m_socket) m_socket->deleteLater();
156 void TM::WordringConnection::onError(QAbstractSocket::SocketError e)
158 qDebug() << "WordringConnection::onError()";
159 if(m_socket) qDebug() << "WordringConnection::onError" << m_socket->errorString();
161 if(e == QAbstractSocket::RemoteHostClosedError) return;
162 //if(!m_request_limit) return;
166 static int error_count = 4;
167 if(m_request_limit == 0)
170 if(!error_count) return;
174 mb.setWindowTitle("wordring translation memory");
176 QString msg = "<h2>An error occured...</h2>";
177 msg += "<p>while connecting to the external server.</p>";
178 if(m_socket) msg += QString("<p>reason: ") + m_socket->errorString() + "</p>";
179 msg += "<p>The system goes into internal mode.</p>";
185 void TM::WordringConnection::onTextMessageReceived(QString const &message)
187 QJsonParseError jerror;
188 QJsonDocument jdocument = QJsonDocument::fromJson(message.toUtf8(), &jerror);
189 assert(jerror.error == QJsonParseError::NoError);
190 if(jerror.error != QJsonParseError::NoError)
192 qDebug() << jerror.errorString();
195 assert(jdocument.isObject());
196 if(!jdocument.isObject()) return;
198 QJsonObject json = jdocument.object();
199 QString cmd = json["cmd"].toString();
201 if(cmd == "found") sentence_found(json);
204 void TM::WordringConnection::onBinaryMessageReceived(QByteArray const &)
209 void TM::WordringConnection::open()
211 assert(m_settings->contains("WordringConnection/url"));
212 m_socket = new QWebSocket("", QWebSocketProtocol::VersionLatest, this);
214 connect(m_socket, SIGNAL(connected()), this, SLOT(onConnected()));
215 connect(m_socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
216 connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),
217 this, SLOT(onError(QAbstractSocket::SocketError)));
218 connect(m_socket, SIGNAL(textMessageReceived(QString)),
219 this, SLOT(onTextMessageReceived(QString)));
220 connect(m_socket, SIGNAL(binaryMessageReceived(QByteArray)),
221 this, SLOT(onBinaryMessageReceived(QByteArray)));
223 QString url = m_settings->value("WordringConnection/url").toString();
224 m_socket->open(QUrl(url));
227 void TM::WordringConnection::send()
229 if(m_request_limit < m_requests.size()) // 制限値を超えているか検査。
234 if(!m_socket) open();
235 if(!m_socket->isValid()) return;
237 while(!m_requests.isEmpty())
239 QByteArray json = m_requests.head();
240 m_socket->sendTextMessage(json);
241 m_requests.dequeue();
245 void TM::WordringConnection::sentence_found(QJsonObject json)
247 QString site = json["site"].toString();
248 int scode = json["scode"].toInt();
249 int tcode = json["tcode"].toInt();
251 QString ssentence = json["ssentence"].toString();
252 QString tsentence = json["tsentence"].toString();
254 quint32 user_id = json["user_id"].toInt();
255 qint32 token = json["token"].toInt();
257 quint32 sentence_id = 0;
258 quint32 source_id = 0;
260 quint32 previous_crc = 0;
261 quint32 next_crc = 0;
264 assert(!site.isEmpty());
265 assert(0 < scode && scode <= 0xFF);
266 assert(0 < tcode && tcode <= 0xFF);
267 assert(!ssentence.isEmpty());
268 assert(!tsentence.isEmpty());
272 if(json.contains("json"))
274 QJsonValue jv = json["json"];
275 assert(jv.isObject());
276 if(!jv.isObject()) return;
277 QJsonDocument jdocument(jv.toObject());
278 jdata = jdocument.toJson(QJsonDocument::Compact);
281 if(site.isEmpty()) return;
284 if(ssentence.isEmpty()) return;
285 if(tsentence.isEmpty()) return;
289 sentence_data_type result;
291 result.scode = scode;
292 result.tcode = tcode;
293 result.sentence_id = sentence_id;
294 result.source_id = source_id;
295 result.sentence = tsentence;
298 result.previous_crc = previous_crc;
299 result.next_crc = next_crc;
300 result.user_id = user_id;
303 m_service->sentence_found(ssentence, result);
306 // Service --------------------------------------------------------------------
308 TM::Service::Service(Settings *settings, QObject *parent)
310 , m_settings(settings)
311 , m_editor_widget(nullptr)
312 , m_candidate_widget(nullptr)
313 , m_database_thread(new QThread(this))
314 , m_database(new Database(settings, this))
315 , m_wordring(new WordringConnection(settings, this))
316 , m_current_connection(nullptr)
318 qRegisterMetaType<sentence_data_type>();
319 qRegisterMetaType<candidate_data_type>();
320 qRegisterMetaType<TextSentence::weak_pointer>();
324 m_database->moveToThread(m_database_thread);
325 connect(m_database_thread, SIGNAL(finished()), m_database, SLOT(deleteLater()));
326 m_database_thread->start();
327 QMetaObject::invokeMethod(
329 Qt::BlockingQueuedConnection);
332 TM::Service::~Service()
334 m_database_thread->quit();
335 m_database_thread->wait();
338 TM::EditorWidget* TM::Service::editor_widget() { return m_editor_widget; }
340 void TM::Service::set_editor_widget(EditorWidget *editor) { m_editor_widget = editor; }
342 void TM::Service::change_edit_mode(bool mode)
344 for(SocketConnection *connection : m_connections)
346 connection->set_edit_mode(mode);
350 TM::CandidateWidget* TM::Service::candidate_widget() { return m_candidate_widget; }
352 void TM::Service::set_candidate_widget(CandidateWidget *candidate) { m_candidate_widget = candidate; }
354 void TM::Service::attach(SocketConnection *connection)
356 m_connections.insert(connection);
359 void TM::Service::detach(SocketConnection *connection)
361 m_connections.remove(connection);
364 void TM::Service::set_current_connection(SocketConnection *connection)
366 m_current_connection = connection;
369 void TM::Service::setup_crc_table()
371 for (quint32 i = 0; i < 256; i++)
374 for (int j = 0; j < 8; j++)
375 c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1);
380 quint32 TM::Service::crc32(QString const &string)
382 quint32 c = 0xFFFFFFFF;
383 QByteArray const ba = string.toUtf8();
384 for (int i = 0; i < ba.size(); i++)
386 c = m_crc_table[(c ^ ba.at(i)) & 0xFF] ^ (c >> 8);
388 return c ^ 0xFFFFFFFF;
391 quint32 TM::Service::crc32(int code, QString const &string)
393 assert(m_languages.contains(code));
394 Language *language = m_languages[code];
395 QString s = language->normalize(string);
396 assert(!s.isEmpty());
400 void TM::Service::load_languages(QString const &path)
403 for(QString fname : pdir.entryList(QDir::Files))
405 QPluginLoader loader(pdir.absoluteFilePath(fname));
406 if(Language *language = qobject_cast<Language*>(loader.instance()))
408 int code = language->code();
409 QString name = language->name();
411 m_languages[code] = language;
412 language->set_settings(m_settings);
414 emit languageLoaded(code, name, language->icon());
415 QMetaObject::invokeMethod(
416 m_database, "open_word_database",
417 Qt::BlockingQueuedConnection,
419 Q_ARG(QString, name));
424 Language* TM::Service::find_language(int code)
426 assert(m_languages.contains(code));
427 return m_languages[code];
431 * \brief 引数として与えられたstringを文に分割します。
432 * \param code 分割に使用する言語コード。
433 * \param string 分割する文字列。
434 * \return 構造化テキストを返します。
436 Text::pointer TM::Service::divide_into_sentences(int code, QString string)
438 assert(m_languages.contains(code));
439 return m_languages[code]->divide_into_sentences(string);
443 * \brief 引数として与えられた文を単語に分割します。
445 Text::pointer TM::Service::divide_into_words(int code, Text::pointer sentence)
447 assert(m_languages.contains(code));
448 return m_languages[code]->divide_into_words(sentence);
451 QString TM::Service::normalize(int code, QString string)
453 assert(m_languages.contains(code));
454 return m_languages[code]->normalize(string);
458 * \brief 引数として与えられたサイトに対応するIDを返します。
460 quint32 TM::Service::find_site_id(QString host)
463 QMetaObject::invokeMethod(
464 m_database, "find_site_id",
465 Qt::BlockingQueuedConnection,
466 Q_RETURN_ARG(quint32, result),
467 Q_ARG(QString, host));
472 * \brief 原文から訳文を検索します。
473 * \param sentence 原文。
475 void TM::Service::find_sentence(TextSentence::pointer sentence)
477 sentence_data_type sdata = sentence->ssentence_data();
479 sdata.sentence = normalize(sdata.scode, sdata.sentence);
482 QMetaObject::invokeMethod(
483 m_database, "find_sentence",
484 Qt::QueuedConnection,
485 Q_ARG(sentence_data_type, sdata),
486 Q_ARG(TextSentence::weak_pointer, sentence));
490 * \brief 原文から訳文候補を検索します。
491 * \param sentence 原文。
493 void TM::Service::find_candidates(TextSentence::pointer sentence)
496 sentence->clear_candidates();
498 sentence_data_type sdata = sentence->ssentence_data();
500 sdata.sentence = normalize(sdata.scode, sdata.sentence);
503 QMetaObject::invokeMethod(
504 m_database, "find_candidates",
505 Qt::QueuedConnection,
506 Q_ARG(sentence_data_type, sdata),
507 Q_ARG(TextSentence::weak_pointer, sentence));
511 * \brief 原文と訳文の対をデータベースとサーバへ登録します。
512 * \param sentence 挿入する原文、訳文の対。
514 void TM::Service::insert_sentence(TextSentence::pointer sentence)
516 sentence_data_type sdata = sentence->ssentence_data();
517 sentence_data_type tdata = sentence->tsentence_data();
520 sdata.sentence = normalize(sdata.scode, sdata.sentence);
522 // サーバへ登録(文のポインタを共有するため、非同期で動くデータベースへは、後から登録する)。
523 m_wordring->insert_sentence(sdata, tdata);
526 QMetaObject::invokeMethod(
527 m_database, "insert_sentence",
528 Qt::QueuedConnection,
529 Q_ARG(sentence_data_type, sdata),
530 Q_ARG(sentence_data_type, tdata),
531 Q_ARG(TextSentence::weak_pointer, sentence));
535 * \brief 訳文をデータベースとサーバから消去します。
537 void TM::Service::remove_sentence(TextSentence::pointer sentence)
539 sentence_data_type sdata = sentence->ssentence_data();
540 sentence_data_type tdata = sentence->tsentence_data();
543 sdata.sentence = normalize(sdata.scode, sdata.sentence);
545 m_wordring->remove_sentence(sdata, tdata);
548 QMetaObject::invokeMethod(
549 m_database, "remove_sentence",
550 Qt::QueuedConnection,
551 Q_ARG(sentence_data_type, sdata),
552 Q_ARG(sentence_data_type, tdata));
556 * \brief データベースからの検索結果。
557 * \param result 検索結果。
558 * \param token connectionを示すトークン。
560 * 検索に使った文字列は、tokenから取れる。
562 void TM::Service::sentence_found(sentence_data_type result,
563 TextSentence::weak_pointer token)
565 if(token.expired()) return;
567 TextSentence::pointer sentence = token.lock();
569 TextSegmentList::pointer segments = sentence->segment_list();
571 SocketConnection* connection = segments->connection();
573 connection->sentence_found(result, sentence);
576 // データベースからの検索結果が無かった場合、サーバから検索する。
577 void TM::Service::sentence_not_found(TextSentence::weak_pointer token)
579 if(token.expired()) return;
582 TextSentence::pointer sentence = token.lock();
583 sentence_data_type sdata = sentence->ssentence_data();
584 sdata.sentence = normalize(sdata.scode, sdata.sentence);
586 m_wordring->find_sentence(sdata);
590 * \brief データベースからの挿入結果通知。
595 void TM::Service::sentence_inserted(quint32 source_id, quint32 target_id,
596 TextSentence::weak_pointer token)
598 if(token.expired()) return;
600 TextSentence::pointer sentence = token.lock();
601 sentence->set_source_id(source_id);
602 sentence->set_target_id(target_id);
604 sentence->segment_list()->connection()->sentence_inserted(sentence);
607 void TM::Service::candidate_found(candidate_data_type candidate,
608 TextSentence::weak_pointer token)
610 if(token.expired()) return;
612 TextSentence::pointer sentence = token.lock();
613 bool ret = sentence->append_candidate(candidate);
615 if(m_current_connection != sentence->segment_list()->connection())
620 QString s = candidate.source.sentence;
622 s += candidate.target.sentence;
624 m_candidate_widget->append(s);
629 * \brief サーバからの検索結果通知。
631 void TM::Service::sentence_found(
632 QString sstring, sentence_data_type const &result)
635 sstring = normalize(result.scode, sstring);
638 for(SocketConnection *connection : m_connections)
640 TextSegmentList::pointer segments = connection->segment_list();
641 QList<TextSentence::pointer> i = segments->find_sentences(sstring);
642 for(TextSentence::pointer j : i) connection->sentence_found(result, j);
646 sentence_data_type source;
647 source.sentence = sstring;
649 assert(m_languages.contains(result.scode));
650 Language *language = m_languages[result.scode];
652 Text::pointer sentences = language->divide_into_sentences(sstring);
653 assert(sentences->size() == 1);
654 Text::pointer words = language->divide_into_words(sentences->begin());
656 for(Text::pointer word = words->begin(); word; word = word->next())
658 QString w = word->to_string();
659 w = language->stem(w);
660 source.words.insert(w);
664 QMetaObject::invokeMethod(
665 m_database, "insert_sentence",
666 Qt::QueuedConnection,
667 Q_ARG(sentence_data_type, source),
668 Q_ARG(sentence_data_type, result),
669 Q_ARG(TextSentence::weak_pointer, TextSentence::pointer()));