OSDN Git Service

データ構造決定記念バックアップ♪
[wordring-tm/wordring-tm.git] / proxy / tmsocket.cpp
1 #include "tmsocket.h"
2 #include "tmeditorwidget.h"
3 #include "tmservice.h"
4
5 #include "settings.h"
6 #include "html.h"
7 #include "htmltag.h"
8
9 #include <QMutex>
10 #include <QMutexLocker>
11
12 #include <QJsonDocument>
13 #include <QJsonObject>
14
15 #include "debug.h"
16
17
18 // HtmlData -------------------------------------------------------------------
19
20 TM::HtmlData::HtmlData(HtmlNode node_, int begin_, int tail_)
21         : RangeData(begin_, tail_)
22         , m_node(node_)
23 {
24 }
25
26 int TM::HtmlData::type() const { return Type; }
27
28 HtmlNode TM::HtmlData::node() { return m_node; }
29
30 QString TM::HtmlData::debug_dump() const
31 {
32         QString result;
33         result += "[HtmlData:";
34         result += m_node.name() + ",";
35         result += QString::number(begin()) + ",";
36         result += QString::number(tail()) + "]";
37         return result;
38 }
39
40 TM::HtmlData::pointer TM::HtmlData::create(HtmlNode node_, int begin_, int tail_)
41 {
42         return pointer(new HtmlData(node_, begin_, tail_));
43 }
44
45 // TextConverter --------------------------------------------------------------
46
47 /*!
48  * \brief 引数として与えられたHtmlの範囲から構造化テキストを作成します。
49  */
50 Text::pointer TM::TextConverter::to_text(HtmlRange range)
51 {
52         m_state = 0;
53         Text::pointer result = Text::create();
54         int begin = 0;
55
56         for(HtmlNode const &node : range)
57         {
58                 if(node.type() != HtmlNode::Text) continue;
59                 Text::pointer p = stuff_text(result, node.value());
60                 if(!p) continue;
61                 int num = p->string().size();
62                 HtmlData::pointer hd = HtmlData::create(node, begin, begin + num - 1);
63                 p->set_data(hd);
64                 begin += num;
65         }
66         return result;
67 }
68
69 /*!
70  * \brief 引数として与えられたノードから構造化テキストを作成します。
71  *
72  * 連続する空白文字を一つにまとめます。
73  * 無効なノードが与えられた場合、空の構造化テキストを返します。
74  */
75 Text::pointer TM::TextConverter::stuff_text(Text::pointer parent, QString const &string)
76 {
77         QString outstring;
78         for(QChar const &ch : string)
79         {
80                 if(is_ignorable_white_space(ch)) continue;
81                 if(is_white_space(ch)) outstring.append(' ');
82                 else outstring.append(ch);
83         }
84         if(outstring.isEmpty()) return Text::pointer();
85
86         Text::pointer result = Text::create(parent, outstring);
87         parent->append(result);
88         return result;
89 }
90
91 /*!
92  * \brief 連続する空白文字を検出します。
93  *
94  * 最初の空白文字、あるいは空白文字以外は、falseを返します。
95  * 連続する空白文字の二番目以降に対してのみtrueを返します。
96  */
97 bool TM::TextConverter::is_ignorable_white_space(const QChar &ch)
98 {
99         if(!is_white_space(ch))
100         {
101                 m_state = 0;
102                 return false;
103         }
104         if(m_state == 0)
105         {
106                 m_state = 1;
107                 return false;
108         }
109         return true;
110 }
111
112 /*!
113  * \brief 引数として与えられたchが空白文字の場合true、それ以外の場合falseを返します。
114  */
115 bool TM::TextConverter::is_white_space(QChar const &ch)
116 {
117         switch(ch.unicode())
118         {
119         case 0x9:
120         case 0xA:
121         case 0xC:
122         case 0xD:
123         case 0x20:
124         case 0x200B:
125                 return true;
126         }
127         return false;
128 }
129
130 // HtmlConverter --------------------------------------------------------------
131
132
133 void TM::HtmlConverter::append(QString string)
134 {
135         HtmlText::pointer text = HtmlText::create(HtmlText::weak_pointer());
136         text->set_value(string);
137         m_nodes.append(text);
138 }
139
140 void TM::HtmlConverter::append(HtmlNode::pointer node, QString string)
141 {
142         QList<HtmlNode::pointer> left, right;
143         for(HtmlNode hn = node->parent(); hn; hn = hn.parent())
144         {
145                 if(hn.tname() == "body") break;
146                 left.prepend(hn.lself());
147                 right.append(hn.ltail());
148         }
149
150         m_nodes.append(left);
151         append(string);
152         m_nodes.append(right);
153 }
154
155 QString TM::HtmlConverter::to_string()
156 {
157         QString result;
158
159         adjust();
160         for(Html::pointer node : m_nodes) result += node->to_string();
161
162         return result;
163 }
164
165 void TM::HtmlConverter::adjust()
166 {
167         int adjusted = 0;
168         do
169         {
170                 adjusted = 0;
171                 for(int i = 0; i < m_nodes.size() - 1; i++)
172                 {
173                         HtmlNode::pointer p1 = m_nodes.at(i);
174                         if(p1->type() != Html::Element || p1->place() != Html::Close) continue;
175                         HtmlNode::pointer p2 = m_nodes.at(i + 1);
176                         if(p2->type() != Html::Element || p2->place() != Html::Open) continue;
177
178                         if(p1->lbegin() == p2->lbegin())
179                         {
180                                 m_nodes.removeAt(i);
181                                 m_nodes.removeAt(i);
182                                 ++adjusted;
183                         }
184                 }
185         }
186         while(adjusted);
187 }
188
189 // TextSegment ----------------------------------------------------------------
190
191 TM::TextSegment::TextSegment(
192                 Service *service, int scode, int segment_id, QString source)
193         : m_segment_id(segment_id)
194         , m_document(source.toUtf8())
195 {
196         HtmlNode body = m_document.first("html").first("body");
197         TextConverter tc;
198         m_text = tc.to_text(HtmlRange(body, body));
199
200         Text::pointer sentences = service->divide_into_sentences(scode, m_text->to_string());
201         for(Text::pointer s = sentences->begin(); s; s = s->next()) // s: sentence
202         {
203                 Text::pointer words = service->divide_into_words(scode, s);
204                 m_sentences.append(TextSentence::create(segment_id, words));
205         }
206 }
207
208 int TM::TextSegment::segment_id() const { return m_segment_id; }
209
210 /*!
211  * \brief 引数で指定されたインデックスの文について、CRC32を返します。
212  * \param service サービスへのポインタ。
213  * \param code 言語コード。
214  * \param index 文のインデックス。
215  * \return CRC32。
216  *
217  * このメンバは、不正なインデックスを指定した場合、0を返します。
218  */
219 quint32 TM::TextSegment::crc32(Service *service, int code, int index)
220 {
221         if(index < 0 || size() <= index) return 0;
222
223         TextSentence::pointer sentence = at(index);
224         quint32 result = sentence->crc32();
225         if(!result)
226         {
227                 result = service->crc32(code, sentence->source_sentence()->to_string());
228                 sentence->set_crc32(result);
229         }
230         return result;
231 }
232
233 int TM::TextSegment::size() const { return m_sentences.size(); }
234
235 TM::TextSentence::pointer TM::TextSegment::at(int index)
236 {
237         assert(0 <= index && index < m_sentences.size());
238         return m_sentences.at(index);
239 }
240
241 TM::TextSegment::iterator TM::TextSegment::begin() { return m_sentences.begin(); }
242
243 TM::TextSegment::iterator TM::TextSegment::end() { return m_sentences.end(); }
244
245 HtmlNode::pointer TM::TextSegment::find_html_node_by_offset(int offset)
246 {
247         HtmlNode::pointer result;
248
249         for(Text::pointer p = m_text->begin(); p; p = p->next())
250         {
251                 UserData::pointer ud = p->data();
252                 assert(ud->type() == HtmlData::Type);
253                 HtmlData *hd = static_cast<HtmlData*>(ud.get());
254                 if(hd->begin() <= offset && offset <= hd->tail())
255                 {
256                         result = hd->node().lself();
257                         break;
258                 }
259         }
260         return result;
261 }
262
263 /*!
264  * \brief セグメント全体をHTML文字列に変換します。
265  */
266 QString TM::TextSegment::to_html()
267 {
268         QString result;
269         for(TextSentence::pointer sentence : m_sentences)
270         {
271                 if(!sentence->target_sentence())
272                         result += to_html_from_source(sentence);
273                 else result += to_html_from_target(sentence);
274                 result += "\r\n";
275         }
276         return result;
277 }
278
279 /*!
280  * \brief 原文をHTMLに変換します。
281  *
282  * 訳文のついていない文のために在ります。
283  */
284 QString TM::TextSegment::to_html_from_source(TextSentence::pointer sentence)
285 {
286         HtmlConverter hc;
287
288         UserData *ud = sentence->source_sentence()->data().get();
289         assert(ud->type() == RangeData::Type);
290         RangeData *rd = static_cast<RangeData*>(ud);
291         int sentence_offset = rd->begin();
292
293         Text::pointer source_sentence = sentence->source_sentence();
294         if(!source_sentence) return "";
295
296         for(Text::pointer word = source_sentence->begin(); word; word = word->next())
297         {
298                 QString string = word->to_string();
299                 UserData *ud = word->data().get();
300                 assert(ud->type() == RangeData::Type);
301                 RangeData *rd = static_cast<RangeData*>(ud);
302                 int word_offset = sentence_offset + rd->begin();
303                 HtmlNode::pointer node = find_html_node_by_offset(word_offset);
304                 if(node) hc.append(node, string);
305                 else hc.append(string);
306         }
307         return hc.to_string();
308 }
309
310 /*!
311  * \brief 訳文をHTMLに変換します。
312  */
313 QString TM::TextSegment::to_html_from_target(TextSentence::pointer sentence)
314 {
315         HtmlConverter hc;
316
317         WordLinker* linker = sentence->linker();
318         UserData *ud = sentence->source_sentence()->data().get();
319         assert(ud->type() == RangeData::Type);
320         RangeData *rd = static_cast<RangeData*>(ud);
321         int sentence_offset = rd->begin();
322
323         Text::pointer target_sentence = sentence->target_sentence();
324         if(!target_sentence) return "";
325
326         for(Text::pointer word = target_sentence->begin(); word; word = word->next())
327         {
328                 QString string = word->to_string();
329                 WordLink::pointer link = linker->find(WordLink::Target, word);
330                 if(!link) hc.append(string);
331                 else
332                 {
333                         UserData::pointer ud = link->sources()->at(0)->data();
334                         assert(ud->type() == RangeData::Type);
335                         RangeData *rd = static_cast<RangeData*>(ud.get());
336                         int word_offset = sentence_offset + rd->begin();
337                         HtmlNode::pointer node = find_html_node_by_offset(word_offset);
338                         if(node) hc.append(node, string);
339                         else hc.append(string);
340                 }
341         }
342         return hc.to_string();
343 }
344
345 TM::TextSegment::pointer TM::TextSegment::create(
346                 Service *service, int scode, int segment_id, QString source)
347 {
348         return pointer(new TextSegment(service, scode, segment_id, source));
349 }
350
351 // SocketConnection -----------------------------------------------------------
352
353 TM::SocketConnection::SocketConnection(Settings *settings, Service *service,
354                                                                         EditorWidget *editor_widget, QWebSocket *socket)
355         : QObject(socket)
356         , m_settings(settings)
357         , m_service(service)
358         , m_mutex(QMutex::Recursive)
359         , m_editor_widget(editor_widget)
360         , m_site_id(0)
361         , m_edit_mode(false)
362         , m_source_language_code(0)
363         , m_target_language_code(0)
364 {
365         connect(socket, SIGNAL(textMessageReceived(QString const&)),
366                         this, SLOT(onTextMessageReceived(QString const&)));
367         connect(socket, SIGNAL(binaryMessageReceived(QByteArray const&)),
368                         this, SLOT(onBinaryMessageReceived(QByteArray const&)));
369 }
370
371 TM::SocketConnection::~SocketConnection()
372 {
373         m_editor_widget->detach(this);
374 }
375
376 QWebSocket* TM::SocketConnection::socket()
377 {
378         QWebSocket * result = qobject_cast<QWebSocket*>(parent());
379         return result;
380 }
381
382 void TM::SocketConnection::send_message(QString const &message)
383 {
384         QWebSocket *ws = socket();
385         if(ws) ws->sendTextMessage(message);
386 }
387
388 void TM::SocketConnection::send_message(QJsonObject const &json)
389 {
390         QJsonDocument doc;
391         doc.setObject(json);
392         send_message(doc.toJson().data());
393 }
394
395 /*!
396  * \brief データベースへセンテンスの登録とブラウザへの反映を行います。
397  * \param segment_id セグメントのID。
398  * \param index センテンスのインデックス。
399  */
400 void TM::SocketConnection::save_sentence(int segment_id, int index)
401 {
402         assert(m_site_id);
403
404         // セグメントの検索。
405         segment_map_iterator it = m_segments.find(segment_id);
406         assert(it != m_segments.end());
407         TextSegment::pointer segment = it.value();
408
409         // センテンスの検索。
410         TextSentence::pointer sentence = segment->at(index);
411         assert(sentence->source_sentence());
412         if(!sentence->target_sentence()) return;
413
414         // 言語コード。
415         int scode = m_editor_widget->source_language();
416         int tcode = m_editor_widget->target_language();
417
418         // ソースのCRC
419         quint32 previous_crc = segment->crc32(m_service, scode, index - 1);
420         quint32 next_crc = segment->crc32(m_service, scode, index + 1);
421
422         // データベースへ登録。
423         m_service->insert_sentence(m_site_id, scode, tcode, sentence, previous_crc, next_crc);
424
425         // ブラウザへ反映。
426         set_segment(segment_id, segment->to_html());
427 }
428
429 void TM::SocketConnection::changeEditMode(bool edit_mode)
430 {
431         set_edit_mode(edit_mode);
432 }
433
434 /*!
435  * \brief ブラウザに編集モードを反映します。
436  *
437  * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。
438  * 編集モードではリンクのクリックは無効です。
439  */
440 void TM::SocketConnection::set_edit_mode(bool edit_mode)
441 {
442         if(edit_mode == m_edit_mode) return;
443
444         m_edit_mode = edit_mode;
445         QJsonObject json;
446         json["cmd"] = "set_edit_mode";
447         json["edit_mode"] = edit_mode;
448         send_message(json);
449 }
450
451 /*!
452  * \brief ウェブブラウザへセグメントの訳文をセットします。
453  * \param segment_id セグメントのID。
454  * \param html セグメントに対応するHTML文字列。
455  */
456 void TM::SocketConnection::set_segment(int segment_id, QString html)
457 {
458         QJsonObject json;
459         json["cmd"] = "set_segment";
460         json["segment_id"] = segment_id;
461         json["html"] = html;
462         send_message(json);
463 }
464
465 /*!
466  * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに
467  * 呼び出されます。
468  */
469 void TM::SocketConnection::do_edit_segment(QJsonObject const &json)
470 {
471         assert(json.contains("segment_id"));
472         int segment_id = json["segment_id"].toString().toInt();
473
474         segment_map_iterator it = m_segments.find(segment_id);
475         assert(it != m_segments.end());
476         TextSegment::pointer segment = it.value();
477
478         if(segment != m_current_segment)
479         {
480                 m_current_segment = segment;
481                 m_editor_widget->set_segment(m_current_segment);
482         }
483 }
484
485 /*!
486  * \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、
487  * focusコマンドが発行されたときに呼び出されます。
488  */
489 void TM::SocketConnection::do_focus(QJsonObject const &)
490 {
491         m_editor_widget->attach(this);
492
493         m_editor_widget->set_edit_mode(m_edit_mode);
494         if(m_current_segment) m_editor_widget->set_segment(m_current_segment);
495 }
496
497 void TM::SocketConnection::do_blur(QJsonObject const &)
498 {
499         //m_editor_widget->detach(this);
500 }
501
502 void TM::SocketConnection::do_load(QJsonObject const &json)
503 {
504         m_editor_widget->attach(this);
505
506         set_edit_mode(m_editor_widget->edit_mode());
507
508         m_source_language_code = m_editor_widget->source_language();
509         m_target_language_code = m_editor_widget->target_language();
510         m_url = QUrl(json["url"].toString());
511         m_site_id = m_service->find_site_id(m_url.host());
512 }
513
514 /*!
515  * \brief ブラウザがセグメントを読み込んだ結果呼び出されます。
516  *
517  * この処理の中で、セグメントの設定、完全一致訳文の検索を行います。
518  * セグメント全体の検索が終わった時点でブラウザへsegment_loaded応答を返します。
519  * ブラウザは、segment_loadedを受けて、そのセグメントをクリック可能にします。
520  */
521 void TM::SocketConnection::do_load_segment(QJsonObject const &json)
522 {
523         assert(json.contains("segment_id"));
524         int segment_id = json["segment_id"].toString().toInt();
525         assert(!m_segments.contains(segment_id));
526         assert(json.contains("html"));
527         QString html = json["html"].toString();
528
529         // セグメントの挿入。
530         TextSegment::pointer segment = TextSegment::create(
531                 m_service, m_source_language_code, segment_id, html);
532         m_segments.insert(segment_id, segment);
533
534
535         // センテンス完全一致訳文の検索。
536         //m_service->find_sentence(m_site_id,)
537
538         // セグメントの挿
539         //QJsonObject json;
540         //json["cmd"] = "set_edit_mode";
541         //json["edit_mode"] = edit_mode;
542         //send_message(json);
543 }
544
545 void TM::SocketConnection::onTextMessageReceived(QString const &message)
546 {
547         QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
548         QString cmd = json["cmd"].toString();
549         if(cmd == "edit_segment") do_edit_segment(json);
550         else if(cmd == "focus") do_focus(json);
551         else if(cmd == "blur") do_blur(json);
552         else if(cmd == "load") do_load(json);
553         else if(cmd == "load_segment") do_load_segment(json);
554 }
555
556 void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message)
557 {
558         qDebug() << message;
559 }
560
561 // SocketServer ---------------------------------------------------------------
562
563 TM::SocketServer::SocketServer(Settings *settings, Service* service,
564                                                         EditorWidget *editor_widget, QObject *parent)
565         : QObject(parent)
566         , m_settings(settings)
567         , m_service(service)
568         , m_server(new QWebSocketServer(QStringLiteral("wordring websocket"),
569                                                                         QWebSocketServer::NonSecureMode, this))
570         , m_editor_widget(editor_widget)
571 {
572         connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
573
574         quint16 port = m_settings->value("SocketServer/port").toUInt();
575         bool result = m_server->listen(QHostAddress::LocalHost, port);
576         if(!result) result = m_server->listen(QHostAddress::LocalHost, 0);
577         if(!result) qFatal("An error occured in SocketServer().");
578 }
579
580 TM::SocketServer::~SocketServer()
581 {
582 }
583
584 quint16 TM::SocketServer::port() const
585 {
586         return m_server->serverPort();
587 }
588
589 void TM::SocketServer::abort()
590 {
591         for(QWebSocket *ws : m_sockets)
592         {
593                 ws->abort();
594                 ws->deleteLater();
595         }
596         m_sockets.clear();
597 }
598
599 void TM::SocketServer::onNewConnection()
600 {
601         QWebSocket *socket = m_server->nextPendingConnection();
602         connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
603         new SocketConnection(m_settings, m_service, m_editor_widget, socket);
604         m_sockets.push_back(socket);
605 }
606
607 void TM::SocketServer::onDisconnected()
608 {
609         QWebSocket *socket = qobject_cast<QWebSocket*>(sender());
610         m_sockets.removeOne(socket);
611         socket->deleteLater();
612 }
613
614
615
616
617
618