2 * This file part of StarDict - A international dictionary for GNOME.
3 * http://stardict.sourceforge.net
4 * Copyright (C) 2006 Evgeniy <dushistov@mail.ru>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Library General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 * Based on RFC 2229 and gnome dictionary source code
31 #include "sockets.hpp"
33 #include "dict_client.hpp"
35 sigc::signal<void, const std::string&> DictClient::on_error_;
36 sigc::signal<void, const DictClient::IndexList&>
37 DictClient::on_simple_lookup_end_;
38 sigc::signal<void, const DictClient::StringList&>
39 DictClient::on_complex_lookup_end_;
41 /* Status codes as defined by RFC 2229 */
45 STATUS_N_DATABASES_PRESENT = 110,
46 STATUS_N_STRATEGIES_PRESENT = 111,
47 STATUS_DATABASE_INFO = 112,
48 STATUS_HELP_TEXT = 113,
49 STATUS_SERVER_INFO = 114,
50 STATUS_CHALLENGE = 130,
51 STATUS_N_DEFINITIONS_RETRIEVED = 150,
52 STATUS_WORD_DB_NAME = 151,
53 STATUS_N_MATCHES_FOUND = 152,
58 STATUS_SEND_RESPONSE = 330,
59 /* Connect response codes */
60 STATUS_SERVER_DOWN = 420,
61 STATUS_SHUTDOWN = 421,
63 STATUS_BAD_COMMAND = 500,
64 STATUS_BAD_PARAMETERS = 501,
65 STATUS_COMMAND_NOT_IMPLEMENTED = 502,
66 STATUS_PARAMETER_NOT_IMPLEMENTED = 503,
67 STATUS_NO_ACCESS = 530,
68 STATUS_USE_SHOW_INFO = 531,
69 STATUS_UNKNOWN_MECHANISM = 532,
70 STATUS_BAD_DATABASE = 550,
71 STATUS_BAD_STRATEGY = 551,
72 STATUS_NO_MATCH = 552,
73 STATUS_NO_DATABASES_PRESENT = 554,
74 STATUS_NO_STRATEGIES_PRESENT = 555
78 void DICT::Cmd::send(GIOChannel *channel, GError *&err)
83 g_io_channel_write_chars(channel,
86 if (res != G_IO_STATUS_NORMAL)
89 /* force flushing of the write buffer */
90 res = g_io_channel_flush(channel, &err);
92 if (res != G_IO_STATUS_NORMAL)
95 state_ = DICT::Cmd::DATA;
98 class DefineCmd : public DICT::Cmd {
100 DefineCmd(const gchar *database, const gchar *word) {
101 char *quote_word = g_shell_quote(word);
102 query_ = std::string("DEFINE ") + database + ' ' + quote_word +
106 bool parse(gchar *str, int code);
109 class MatchCmd : public DICT::Cmd {
111 MatchCmd(const gchar *database, const gchar *strategy,
118 const std::string& query() {
119 if (query_.empty()) {
121 char *quote_word = g_shell_quote(word_.c_str());
122 query_ = "MATCH " + database_ + " " + strategy_ +
123 ' ' + quote_word + "\r\n";
126 return DICT::Cmd::query();
128 bool parse(gchar *str, int code);
130 std::string database_;
131 std::string strategy_;
134 virtual void handle_word() {}
137 class LevCmd : public MatchCmd {
139 LevCmd(const gchar *database, const gchar *word) :
140 MatchCmd(database, "lev", word)
145 class RegexpCmd : public MatchCmd {
147 RegexpCmd(const gchar *database, const gchar *word) :
148 MatchCmd(database, "re", word)
155 void RegexpCmd::handle_word()
158 std::string::const_iterator it;
160 for (it = word_.begin(); it != word_.end(); ++it)
166 word_ = "^" + newword;
169 bool MatchCmd::parse(gchar *str, int code)
171 if (code == STATUS_N_MATCHES_FOUND) {
172 gchar *p = g_utf8_strchr(str, -1, ' ');
175 p = g_utf8_next_char (p);
176 g_debug("server replied: %d matches found\n", atoi (p));
177 } else if (0 == strcmp (str, "."))
180 gchar *word, *db_name, *p;
186 p = g_utf8_strchr(db_name, -1, ' ');
190 word = g_utf8_next_char (p);
193 word = g_utf8_next_char (word);
195 p = g_utf8_strchr (word, -1, '\"');
199 reslist_.push_back(DICT::Definition(word));
205 bool DefineCmd::parse(gchar *str, int code)
210 case STATUS_N_DEFINITIONS_RETRIEVED: {
211 gchar *p = g_utf8_strchr(str, -1, ' ');
213 p = g_utf8_next_char (p);
215 g_debug("server replied: %d definitions found\n", atoi(p));
218 case STATUS_WORD_DB_NAME: {
219 gchar *word, *db_name, *db_full, *p;
223 /* skip the status code */
224 word = g_utf8_strchr(word, -1, ' ');
225 word = g_utf8_next_char(word);
228 word = g_utf8_next_char(word);
230 p = g_utf8_strchr(word, -1, '\"');
234 p = g_utf8_next_char(p);
236 /* the database name is not protected by "" */
237 db_name = g_utf8_next_char(p);
241 p = g_utf8_strchr(db_name, -1, ' ');
245 p = g_utf8_next_char(p);
247 db_full = g_utf8_next_char(p);
251 if (db_full[0] == '\"')
252 db_full = g_utf8_next_char(db_full);
254 p = g_utf8_strchr(db_full, -1, '\"');
258 g_debug("{ word .= '%s', db_name .= '%s', db_full .= '%s' }\n",
259 word, db_name, db_full);
260 reslist_.push_back(DICT::Definition(word));
264 if (!reslist_.empty() && strcmp(".", str))
265 reslist_.back().data_ += std::string(str) + "\n";
273 DictClient::DictClient(const char *host, int port)
280 is_connected_ = false;
284 DictClient::~DictClient()
289 void DictClient::connect()
291 Socket::resolve(host_, this, on_resolved);
294 void DictClient::on_resolved(gpointer data, struct hostent *ret)
296 DictClient *oDictClient = (DictClient *)data;
297 oDictClient->sd_ = Socket::socket();
299 if (oDictClient->sd_ == -1) {
300 on_error_.emit("Can not create socket: " + Socket::get_error_msg());
305 oDictClient->channel_ = g_io_channel_win32_new_socket(oDictClient->sd_);
307 oDictClient->channel_ = g_io_channel_unix_new(oDictClient->sd_);
310 /* RFC2229 mandates the usage of UTF-8, so we force this encoding */
311 g_io_channel_set_encoding(oDictClient->channel_, "UTF-8", NULL);
313 g_io_channel_set_line_term(oDictClient->channel_, "\r\n", 2);
315 /* make sure that the channel is non-blocking */
316 int flags = g_io_channel_get_flags(oDictClient->channel_);
317 flags |= G_IO_FLAG_NONBLOCK;
319 g_io_channel_set_flags(oDictClient->channel_, GIOFlags(flags), &err);
321 g_io_channel_unref(oDictClient->channel_);
322 oDictClient->channel_ = NULL;
323 on_error_.emit("Unable to set the channel as non-blocking: " +
324 std::string(err->message));
329 if (!Socket::connect(oDictClient->sd_, ret, oDictClient->port_)) {
330 gchar *mes = g_strdup_printf("Can not connect to %s: %s\n",
331 oDictClient->host_.c_str(), Socket::get_error_msg().c_str());
337 oDictClient->source_id_ = g_io_add_watch(oDictClient->channel_, GIOCondition(G_IO_IN | G_IO_ERR),
338 on_io_event, oDictClient);
341 void DictClient::disconnect()
344 g_source_remove(source_id_);
349 g_io_channel_shutdown(channel_, TRUE, NULL);
350 g_io_channel_unref(channel_);
357 is_connected_ = false;
360 gboolean DictClient::on_io_event(GIOChannel *ch, GIOCondition cond,
363 DictClient *dict_client = static_cast<DictClient *>(user_data);
365 g_assert(dict_client);
367 if (!dict_client->channel_) {
368 g_warning("No channel available\n");
372 if (cond & G_IO_ERR) {
374 g_strdup_printf("Connection failed to the dictionary server at %s:%d",
375 dict_client->host_.c_str(), dict_client->port_);
387 if (!dict_client->channel_)
389 res = g_io_channel_read_line(dict_client->channel_, &line,
391 if (res == G_IO_STATUS_ERROR) {
393 on_error_.emit("Error while reading reply from server: " +
394 std::string(err->message));
397 dict_client->disconnect();
405 //truncate the line terminator before parsing
407 int status_code = get_status_code(line);
408 bool res = dict_client->parse(line, status_code);
411 dict_client->disconnect();
419 bool DictClient::parse(gchar *line, int status_code)
421 g_debug("get %s\n", line);
424 if (status_code == STATUS_CONNECT)
425 is_connected_ = true;
426 else if (status_code == STATUS_SERVER_DOWN ||
427 status_code == STATUS_SHUTDOWN) {
429 g_strdup_printf("Unable to connect to the "
430 "dictionary server at '%s:%d'. "
431 "The server replied with code"
433 host_.c_str(), port_,
440 g_strdup_printf("Unable to parse the dictionary"
441 " server reply: '%s'", line);
448 bool success = false;
450 switch (status_code) {
451 case STATUS_BAD_PARAMETERS:
453 gchar *mes = g_strdup_printf("Bad parameters for command '%s'",
454 cmd_->query().c_str());
457 cmd_->state_ = DICT::Cmd::FINISH;
460 case STATUS_BAD_COMMAND:
462 gchar *mes = g_strdup_printf("Bad command '%s'",
463 cmd_->query().c_str());
466 cmd_->state_ = DICT::Cmd::FINISH;
474 if (cmd_->state_ == DICT::Cmd::START) {
476 cmd_->send(channel_, err);
478 on_error_.emit(err->message);
485 if (status_code == STATUS_OK || cmd_->state_ == DICT::Cmd::FINISH ||
486 status_code == STATUS_NO_MATCH ||
487 status_code == STATUS_BAD_DATABASE ||
488 status_code == STATUS_BAD_STRATEGY ||
489 status_code == STATUS_NO_DATABASES_PRESENT ||
490 status_code == STATUS_NO_STRATEGIES_PRESENT) {
492 const DICT::DefList& res = cmd_->result();
493 if (simple_lookup_) {
494 IndexList ilist(res.size());
495 for (size_t i = 0; i < res.size(); ++i) {
496 ilist[i] = last_index_;
497 defmap_.insert(std::make_pair(last_index_++, res[i]));
502 on_simple_lookup_end_.emit(ilist);
505 for (size_t i = 0; i < res.size(); ++i)
506 slist.push_back(res[i].word_);
510 on_complex_lookup_end_.emit(slist);
516 if (!cmd_->parse(line, status_code))
523 /* retrieve the status code from the server response line */
524 int DictClient::get_status_code(gchar *line)
528 if (strlen (line) < 3)
531 if (!g_unichar_isdigit (line[0]) ||
532 !g_unichar_isdigit (line[1]) ||
533 !g_unichar_isdigit (line[2]))
546 void DictClient::lookup_simple(const gchar *word)
548 simple_lookup_ = true;
550 if (!word || !*word) {
551 on_simple_lookup_end_.emit(IndexList());
555 if (!channel_ || !source_id_)
559 cmd_.reset(new DefineCmd("*", word));
562 void DictClient::lookup_with_rule(const gchar *word)
564 simple_lookup_ = false;
566 if (!word || !*word) {
567 on_complex_lookup_end_.emit(StringList());
571 if (!channel_ || !source_id_)
575 cmd_.reset(new RegexpCmd("*", word));
578 void DictClient::lookup_with_fuzzy(const gchar *word)
580 simple_lookup_ = false;
582 if (!word || !*word) {
583 on_complex_lookup_end_.emit(StringList());
587 if (!channel_ || !source_id_)
591 cmd_.reset(new LevCmd("*", word));
594 const gchar *DictClient::get_word(size_t index) const
596 DefMap::const_iterator it = defmap_.find(index);
598 if (it == defmap_.end())
600 return it->second.word_.c_str();
603 const gchar *DictClient::get_word_data(size_t index) const
605 DefMap::const_iterator it = defmap_.find(index);
607 if (it == defmap_.end())
609 return it->second.data_.c_str();