OSDN Git Service

HTTPリクエストパーサがフィールド値のクォートで遷移を間違えていた問題を修正。
[wordring-tm/wordring-tm.git] / proxy / tmservice.cpp
1 #include "tmservice.h"
2
3 #include "language.h"
4 #include "settings.h"
5 #include "tmdatabase.h"
6
7 #include "tmsocket.h"
8
9 #include "tmeditorwidget.h"
10 #include "tmcandidatewidget.h"
11
12 #include <QMessageBox>
13
14 #include <QDir>
15
16 #include <QMutex>
17 #include <QMutexLocker>
18
19 #include <QPluginLoader>
20
21 #include <QJsonDocument>
22 #include <QJsonObject>
23 #include <QJsonValue>
24 #include <QJsonArray>
25
26 #include <QUrl>
27
28 #include "debug.h"
29
30 // WordringConnection ---------------------------------------------------------
31
32 TM::WordringConnection::WordringConnection(Settings *settings, Service *service)
33         : QObject(service)
34         , m_settings(settings)
35         , m_socket(nullptr)
36         , m_service(service)
37         , m_request_limit(1024)
38 {
39 }
40
41 bool TM::WordringConnection::find_sentence(sentence_data_type const &sdata)
42 {
43         if(m_request_limit < m_requests.size()) return false;
44
45         QJsonObject jobject;
46         jobject["cmd"] = "find";
47         jobject["site"] = sdata.site;
48         jobject["scode"] = (int)sdata.scode;
49         jobject["tcode"] = (int)sdata.tcode;
50
51         jobject["ssentence"] = sdata.sentence;
52         jobject["previous_crc"] = (int)sdata.previous_crc;
53         jobject["next_crc"] = (int)sdata.next_crc;
54
55         jobject["token"] = 1;
56
57         QJsonDocument jdocument(jobject);
58         assert(jdocument.isObject());
59         QByteArray json = jdocument.toJson();
60
61         m_requests.enqueue(json);
62         send();
63
64         return true;
65 }
66
67 bool TM::WordringConnection::insert_sentence(
68                 sentence_data_type const &sdata,
69                 sentence_data_type const & tdata)
70 {
71         if(m_request_limit < m_requests.size()) return false;
72
73         QString host = sdata.site;
74         qint32 scode = sdata.scode;
75         qint32 tcode = sdata.tcode;
76
77         QString sstring = sdata.sentence;
78         QString tstring = tdata.sentence;
79         QByteArray jdata = tdata.json;
80
81         QJsonObject jobject;
82
83         jobject["cmd"] = "insert";
84
85         jobject["site"] = host;
86         jobject["scode"] = scode;
87         jobject["tcode"] = tcode;
88
89         jobject["ssentence"] = sstring;
90         jobject["previous_crc"] = (int)sdata.previous_crc;
91         jobject["next_crc"] = (int)sdata.next_crc;
92
93         jobject["tsentence"] = tstring;
94
95         if(!jdata.isEmpty())
96         {
97                 QJsonParseError error;
98                 QJsonDocument jlink = QJsonDocument::fromJson(jdata, &error);
99
100                 assert(error.error == QJsonParseError::NoError);
101                 if(error.error != QJsonParseError::NoError) return false;
102                 jobject["json"] = jlink.object();
103         }
104
105         QJsonDocument jdocument(jobject);
106         assert(jdocument.isObject());
107         QByteArray json = jdocument.toJson(QJsonDocument::Compact);
108
109         m_requests.enqueue(json);
110         send();
111
112         return true;
113 }
114
115 /*!
116  * \brief サーバから訳文を消去します。
117  */
118 bool TM::WordringConnection::remove_sentence(
119                 sentence_data_type const &sdata,
120                 sentence_data_type const &)
121 {
122         QJsonObject jobject;
123
124         jobject["cmd"] = "remove";
125
126         jobject["site"] = sdata.site;
127         jobject["scode"] = (int)sdata.scode;
128         jobject["tcode"] = (int)sdata.tcode;
129
130         jobject["ssentence"] = sdata.sentence;
131         jobject["previous_crc"] = (int)sdata.previous_crc;
132         jobject["next_crc"] = (int)sdata.next_crc;
133
134         QJsonDocument jdocument(jobject);
135         assert(jdocument.isObject());
136         QByteArray json = jdocument.toJson(QJsonDocument::Compact);
137
138         m_requests.enqueue(json);
139         send();
140
141         return true;
142 }
143
144 void TM::WordringConnection::onConnected()
145 {
146         qDebug() << "WordringConnection::onConnected()";
147         send();
148 }
149
150 void TM::WordringConnection::onDisconnected()
151 {
152         if(m_socket) m_socket->deleteLater();
153         m_socket = nullptr;
154 }
155
156 void TM::WordringConnection::onError(QAbstractSocket::SocketError e)
157 {
158         qDebug() << "WordringConnection::onError()";
159         if(m_socket) qDebug() << "WordringConnection::onError" << m_socket->errorString();
160
161         if(e == QAbstractSocket::RemoteHostClosedError) return;
162         //if(!m_request_limit) return;
163
164         /*
165         m_request_limit = 0;
166         static int error_count = 4;
167         if(m_request_limit == 0)
168         {
169                 --error_count;
170                 if(!error_count) return;
171         }
172
173         QMessageBox mb;
174         mb.setWindowTitle("wordring translation memory");
175
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>";
180         mb.setText(msg);
181         mb.exec();
182         */
183 }
184
185 void TM::WordringConnection::onTextMessageReceived(QString const &message)
186 {
187         QJsonParseError jerror;
188         QJsonDocument jdocument = QJsonDocument::fromJson(message.toUtf8(), &jerror);
189         assert(jerror.error == QJsonParseError::NoError);
190         if(jerror.error != QJsonParseError::NoError)
191         {
192                 qDebug() << jerror.errorString();
193                 return;
194         }
195         assert(jdocument.isObject());
196         if(!jdocument.isObject()) return;
197
198         QJsonObject json = jdocument.object();
199         QString cmd = json["cmd"].toString();
200
201         if(cmd == "found") sentence_found(json);
202 }
203
204 void TM::WordringConnection::onBinaryMessageReceived(QByteArray const &)
205 {
206         assert(false);
207 }
208
209 void TM::WordringConnection::open()
210 {
211         assert(m_settings->contains("WordringConnection/url"));
212         m_socket = new QWebSocket("", QWebSocketProtocol::VersionLatest, this);
213
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)));
222
223         QString url = m_settings->value("WordringConnection/url").toString();
224         m_socket->open(QUrl(url));
225 }
226
227 void TM::WordringConnection::send()
228 {
229         if(m_request_limit < m_requests.size()) // 制限値を超えているか検査。
230         {
231                 m_requests.clear();
232         }
233
234         if(!m_socket) open();
235         if(!m_socket->isValid()) return;
236
237         while(!m_requests.isEmpty())
238         {
239                 QByteArray json = m_requests.head();
240                 m_socket->sendTextMessage(json);
241                 m_requests.dequeue();
242         }
243 }
244
245 void TM::WordringConnection::sentence_found(QJsonObject json)
246 {
247         QString site      = json["site"].toString();
248         int     scode     = json["scode"].toInt();
249         int     tcode     = json["tcode"].toInt();
250
251         QString ssentence = json["ssentence"].toString();
252         QString tsentence = json["tsentence"].toString();
253         QByteArray jdata;
254         quint32 user_id   = json["user_id"].toInt();
255         qint32  token     = json["token"].toInt();
256
257         quint32 sentence_id = 0;
258         quint32 source_id = 0;
259         quint32 crc = 0;
260         quint32 previous_crc = 0;
261         quint32 next_crc = 0;
262         quint64 time = 0;
263
264         assert(!site.isEmpty());
265         assert(0 < scode && scode <= 0xFF);
266         assert(0 < tcode && tcode <= 0xFF);
267         assert(!ssentence.isEmpty());
268         assert(!tsentence.isEmpty());
269         assert(user_id);
270         assert(token);
271
272         if(json.contains("json"))
273         {
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);
279         }
280
281         if(site.isEmpty()) return;
282         if(!scode) return;
283         if(!tcode) return;
284         if(ssentence.isEmpty()) return;
285         if(tsentence.isEmpty()) return;
286         if(!user_id) return;
287         if(!token) return;
288
289         sentence_data_type result;
290         result.site = site;
291         result.scode = scode;
292         result.tcode = tcode;
293         result.sentence_id = sentence_id;
294         result.source_id = source_id;
295         result.sentence = tsentence;
296         result.json = jdata;
297         result.crc = crc;
298         result.previous_crc = previous_crc;
299         result.next_crc = next_crc;
300         result.user_id = user_id;
301         result.time = time;
302
303         m_service->sentence_found(ssentence, result);
304 }
305
306 // Service --------------------------------------------------------------------
307
308 TM::Service::Service(Settings *settings, QObject *parent)
309         : 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)
317 {
318         qRegisterMetaType<sentence_data_type>();
319         qRegisterMetaType<candidate_data_type>();
320         qRegisterMetaType<TextSentence::weak_pointer>();
321
322         setup_crc_table();
323
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(
328                                 m_database, "setup",
329                                 Qt::BlockingQueuedConnection);
330 }
331
332 TM::Service::~Service()
333 {
334         m_database_thread->quit();
335         m_database_thread->wait();
336 }
337
338 TM::EditorWidget* TM::Service::editor_widget() { return m_editor_widget; }
339
340 void TM::Service::set_editor_widget(EditorWidget *editor) { m_editor_widget = editor; }
341
342 void TM::Service::change_edit_mode(bool mode)
343 {
344         for(SocketConnection *connection : m_connections)
345         {
346                 connection->set_edit_mode(mode);
347         }
348 }
349
350 TM::CandidateWidget* TM::Service::candidate_widget() { return m_candidate_widget; }
351
352 void TM::Service::set_candidate_widget(CandidateWidget *candidate) { m_candidate_widget = candidate; }
353
354 void TM::Service::attach(SocketConnection *connection)
355 {
356         m_connections.insert(connection);
357 }
358
359 void TM::Service::detach(SocketConnection *connection)
360 {
361         m_connections.remove(connection);
362 }
363
364 void TM::Service::set_current_connection(SocketConnection *connection)
365 {
366         m_current_connection = connection;
367 }
368
369 void TM::Service::setup_crc_table()
370 {
371         for (quint32 i = 0; i < 256; i++)
372         {
373                 quint32 c = i;
374                 for (int j = 0; j < 8; j++)
375                         c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1);
376                 m_crc_table[i] = c;
377         }
378 }
379
380 quint32 TM::Service::crc32(QString const &string)
381 {
382         quint32 c = 0xFFFFFFFF;
383         QByteArray const ba = string.toUtf8();
384         for (int i = 0; i < ba.size(); i++)
385         {
386                 c = m_crc_table[(c ^ ba.at(i)) & 0xFF] ^ (c >> 8);
387         }
388         return c ^ 0xFFFFFFFF;
389 }
390
391 quint32 TM::Service::crc32(int code, QString const &string)
392 {
393         assert(m_languages.contains(code));
394         Language *language = m_languages[code];
395         QString s = language->normalize(string);
396         assert(!s.isEmpty());
397         return crc32(s);
398 }
399
400 void TM::Service::load_languages(QString const &path)
401 {
402         QDir pdir(path);
403         for(QString fname : pdir.entryList(QDir::Files))
404         {
405                 QPluginLoader loader(pdir.absoluteFilePath(fname));
406                 if(Language *language = qobject_cast<Language*>(loader.instance()))
407                 {
408                         int code = language->code();
409                         QString name = language->name();
410
411                         m_languages[code] = language;
412                         language->set_settings(m_settings);
413
414                         emit languageLoaded(code, name, language->icon());
415                         QMetaObject::invokeMethod(
416                                                 m_database, "open_word_database",
417                                                 Qt::BlockingQueuedConnection,
418                                                 Q_ARG(int, code),
419                                                 Q_ARG(QString, name));
420                 }
421         }
422 }
423
424 Language* TM::Service::find_language(int code)
425 {
426         assert(m_languages.contains(code));
427         return m_languages[code];
428 }
429
430 /*!
431  * \brief 引数として与えられたstringを文に分割します。
432  * \param code 分割に使用する言語コード。
433  * \param string 分割する文字列。
434  * \return 構造化テキストを返します。
435  */
436 Text::pointer TM::Service::divide_into_sentences(int code, QString string)
437 {
438         assert(m_languages.contains(code));
439         return m_languages[code]->divide_into_sentences(string);
440 }
441
442 /*!
443  * \brief 引数として与えられた文を単語に分割します。
444  */
445 Text::pointer TM::Service::divide_into_words(int code, Text::pointer sentence)
446 {
447         assert(m_languages.contains(code));
448         return m_languages[code]->divide_into_words(sentence);
449 }
450
451 QString TM::Service::normalize(int code, QString string)
452 {
453         assert(m_languages.contains(code));
454         return m_languages[code]->normalize(string);
455 }
456
457 /*!
458  * \brief 引数として与えられたサイトに対応するIDを返します。
459  */
460 quint32 TM::Service::find_site_id(QString host)
461 {
462         quint32 result;
463         QMetaObject::invokeMethod(
464                                 m_database, "find_site_id",
465                                 Qt::BlockingQueuedConnection,
466                                 Q_RETURN_ARG(quint32, result),
467                                 Q_ARG(QString, host));
468         return result;
469 }
470
471 /*!
472  * \brief 原文から訳文を検索します。
473  * \param sentence 原文。
474  */
475 void TM::Service::find_sentence(TextSentence::pointer sentence)
476 {
477         sentence_data_type sdata = sentence->ssentence_data();
478         // 原文を正規化。
479         sdata.sentence = normalize(sdata.scode, sdata.sentence);
480
481         // データベース呼び出し。
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));
487 }
488
489 /*!
490  * \brief 原文から訳文候補を検索します。
491  * \param sentence 原文。
492  */
493 void TM::Service::find_candidates(TextSentence::pointer sentence)
494 {
495         // 現在の候補をクリア。
496         sentence->clear_candidates();
497
498         sentence_data_type sdata = sentence->ssentence_data();
499         // 原文を正規化。
500         sdata.sentence = normalize(sdata.scode, sdata.sentence);
501
502         // データベース呼び出し。
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));
508 }
509
510 /*!
511  * \brief 原文と訳文の対をデータベースとサーバへ登録します。
512  * \param sentence 挿入する原文、訳文の対。
513  */
514 void TM::Service::insert_sentence(TextSentence::pointer sentence)
515 {
516         sentence_data_type sdata = sentence->ssentence_data();
517         sentence_data_type tdata = sentence->tsentence_data();
518
519         // 原文を正規化。
520         sdata.sentence = normalize(sdata.scode, sdata.sentence);
521
522         // サーバへ登録(文のポインタを共有するため、非同期で動くデータベースへは、後から登録する)。
523         m_wordring->insert_sentence(sdata, tdata);
524
525         // データベースへ登録。
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));
532 }
533
534 /*!
535  * \brief 訳文をデータベースとサーバから消去します。
536  */
537 void TM::Service::remove_sentence(TextSentence::pointer sentence)
538 {
539         sentence_data_type sdata = sentence->ssentence_data();
540         sentence_data_type tdata = sentence->tsentence_data();
541
542         // 原文を正規化。
543         sdata.sentence = normalize(sdata.scode, sdata.sentence);
544
545         m_wordring->remove_sentence(sdata, tdata);
546
547         // データベースから消去。
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));
553 }
554
555 /*!
556  * \brief データベースからの検索結果。
557  * \param result 検索結果。
558  * \param token connectionを示すトークン。
559  *
560  * 検索に使った文字列は、tokenから取れる。
561  */
562 void TM::Service::sentence_found(sentence_data_type result,
563                                                                  TextSentence::weak_pointer token)
564 {
565         if(token.expired()) return;
566
567         TextSentence::pointer sentence = token.lock();
568         // ブラウザに結果を送る。
569         TextSegmentList::pointer segments = sentence->segment_list();
570         assert(segments);
571         SocketConnection* connection = segments->connection();
572         assert(connection);
573         connection->sentence_found(result, sentence);
574 }
575
576 // データベースからの検索結果が無かった場合、サーバから検索する。
577 void TM::Service::sentence_not_found(TextSentence::weak_pointer token)
578 {
579         if(token.expired()) return;
580
581         // サーバから検索。
582         TextSentence::pointer sentence = token.lock();
583         sentence_data_type sdata = sentence->ssentence_data();
584         sdata.sentence = normalize(sdata.scode, sdata.sentence);
585
586         m_wordring->find_sentence(sdata);
587 }
588
589 /*!
590  * \brief データベースからの挿入結果通知。
591  * \param source_id
592  * \param target_id
593  * \param token
594  */
595 void TM::Service::sentence_inserted(quint32 source_id, quint32 target_id,
596                                                                         TextSentence::weak_pointer token)
597 {
598         if(token.expired()) return;
599
600         TextSentence::pointer sentence = token.lock();
601         sentence->set_source_id(source_id);
602         sentence->set_target_id(target_id);
603         // ブラウザへ通知。
604         sentence->segment_list()->connection()->sentence_inserted(sentence);
605 }
606
607 void TM::Service::candidate_found(candidate_data_type candidate,
608                                          TextSentence::weak_pointer token)
609 {
610         if(token.expired()) return;
611
612         TextSentence::pointer sentence = token.lock();
613         bool ret = sentence->append_candidate(candidate);
614
615         if(m_current_connection != sentence->segment_list()->connection())
616                 return;
617
618         if(ret)
619         {
620                 QString s = candidate.source.sentence;
621                 s += "\r\n";
622                 s += candidate.target.sentence;
623                 s += "\r\n";
624                 m_candidate_widget->append(s);
625         }
626 }
627
628 /*!
629  * \brief サーバからの検索結果通知。
630  */
631 void TM::Service::sentence_found(
632                 QString sstring, sentence_data_type const &result)
633 {
634         // 原文を正規化。
635         sstring = normalize(result.scode, sstring);
636
637         // ブラウザへ通知。
638         for(SocketConnection *connection : m_connections)
639         {
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);
643         }
644
645         // 原文を埋める。
646         sentence_data_type source;
647         source.sentence = sstring;
648         // 原文の単語情報を埋める。
649         assert(m_languages.contains(result.scode));
650         Language *language = m_languages[result.scode];
651
652         Text::pointer sentences = language->divide_into_sentences(sstring);
653         assert(sentences->size() == 1);
654         Text::pointer words = language->divide_into_words(sentences->begin());
655         // 単語を正規化。
656         for(Text::pointer word = words->begin(); word; word = word->next())
657         {
658                 QString w = word->to_string();
659                 w = language->stem(w);
660                 source.words.insert(w);
661         }
662
663         // データベースへ保存。
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()));
670 }
671
672
673
674
675
676