Imported version 0.2-1
[mstardict] / src / lib / stardict_client.cpp
diff --git a/src/lib/stardict_client.cpp b/src/lib/stardict_client.cpp
new file mode 100644 (file)
index 0000000..dcae5ce
--- /dev/null
@@ -0,0 +1,1263 @@
+/*
+ * This file part of StarDict - A international dictionary for GNOME.
+ * http://stardict.sourceforge.net
+ * Copyright (C) 2006 Hu Zheng <huzheng_001@163.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "sockets.hpp"
+#include "md5.h"
+#include "getuint32.h"
+
+#include "stardict_client.hpp"
+
+#define PROTOCOL_VERSION "0.3"
+
+#define CODE_HELLO                   220 /* text msg-id */
+#define CODE_GOODBYE                 221 /* Closing Connection */
+#define CODE_OK                      250 /* ok */
+#define CODE_TEMPORARILY_UNAVAILABLE 420 /* server unavailable */
+#define CODE_SYNTAX_ERROR            500 /* syntax, command not recognized */
+#define CODE_DENIED                  521
+#define CODE_DICTMASK_NOTSET         522
+
+unsigned int STARDICT::Cmd::next_seq = 1;
+
+sigc::signal<void, const char *> StarDictClient::on_error_;
+sigc::signal<void, const struct STARDICT::LookupResponse *, unsigned int> StarDictClient::on_lookup_end_;
+sigc::signal<void, const struct STARDICT::LookupResponse *, unsigned int> StarDictClient::on_floatwin_lookup_end_;
+sigc::signal<void, const char *> StarDictClient::on_register_end_;
+sigc::signal<void, const char *> StarDictClient::on_getdictmask_end_;
+sigc::signal<void, const char *> StarDictClient::on_dirinfo_end_;
+sigc::signal<void, const char *> StarDictClient::on_dictinfo_end_;
+sigc::signal<void, int> StarDictClient::on_maxdictcount_end_;
+sigc::signal<void, std::list<char *> *> StarDictClient::on_previous_end_;
+sigc::signal<void, std::list<char *> *> StarDictClient::on_next_end_;
+
+static void arg_escape(std::string &earg, const char *arg)
+{
+    earg.clear();
+    while (*arg) {
+        if (*arg=='\\') {
+            earg+="\\\\";
+        } else if (*arg==' ') {
+            earg+="\\ ";
+        } else if (*arg=='\n') {
+            earg+="\\n";
+        } else {
+            earg+=*arg;
+        }
+        arg++;
+    }
+}
+
+STARDICT::Cmd::Cmd(int cmd, ...)
+{
+       this->seq = this->next_seq;
+       this->next_seq++;
+       this->reading_status = 0;
+       this->command = cmd;
+       va_list    ap;
+       va_start( ap, cmd );
+       switch (cmd) {
+       case CMD_CLIENT:
+       {
+               const char *client_name = va_arg( ap, const char * );
+               std::string earg1, earg2;
+               arg_escape(earg1, PROTOCOL_VERSION);
+               arg_escape(earg2, client_name);
+               this->data = g_strdup_printf("client %s %s\n", earg1.c_str(), earg2.c_str());
+               break;
+       }
+       case CMD_REGISTER:
+       {
+               const char *user = va_arg( ap, const char * );
+               const char *passwd = va_arg( ap, const char * );
+               const char *email = va_arg( ap, const char * );
+               std::string earg1, earg2, earg3;
+               arg_escape(earg1, user);
+               arg_escape(earg2, passwd);
+               arg_escape(earg3, email);
+               this->data = g_strdup_printf("register %s %s %s\n", earg1.c_str(), earg2.c_str(), earg3.c_str());
+               break;
+       }
+       /*case CMD_CHANGE_PASSWD:
+       {
+               const char *user = va_arg( ap, const char * );
+               const char *old_passwd = va_arg( ap, const char * );
+               const char *new_passwd = va_arg( ap, const char * );
+               std::string earg1, earg2, earg3;
+               arg_escape(earg1, user);
+               arg_escape(earg2, old_passwd);
+               arg_escape(earg3, new_passwd);
+               this->data = g_strdup_printf("change_password %s %s %s\n", earg1.c_str(), earg2.c_str(), earg3.c_str());
+               break;
+       }*/
+       case CMD_AUTH:
+        this->auth = new AuthInfo();
+               this->auth->user = va_arg( ap, const char * );
+               this->auth->passwd = va_arg( ap, const char * );
+               break;
+       case CMD_LOOKUP:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("lookup %s 30\n", earg.c_str());
+               this->lookup_response = NULL;
+               break;
+       }
+       case CMD_PREVIOUS:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("previous %s 15\n", earg.c_str());
+        this->wordlist_response = NULL;
+               break;
+       }
+       case CMD_NEXT:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("next %s 30\n", earg.c_str());
+               this->wordlist_response = NULL;
+               break;
+       }
+       /*case CMD_QUERY:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("query %s\n", earg.c_str());
+               this->lookup_response = NULL;
+               break;
+       }*/
+       case CMD_SELECT_QUERY:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("selectquery %s\n", earg.c_str());
+               this->lookup_response = NULL;
+               break;
+       }
+       case CMD_SMART_QUERY:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               int BeginPos = va_arg( ap, int );
+               this->data = g_strdup_printf("smartquery %s %d\n", earg.c_str(), BeginPos);
+               this->lookup_response = NULL;
+               break;
+       }
+       case CMD_DEFINE:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("define %s\n", earg.c_str());
+               this->lookup_response = NULL;
+               break;
+       }
+       case CMD_SET_DICT_MASK:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("setdictmask %s\n", earg.c_str());
+               break;
+       }
+       case CMD_GET_DICT_MASK:
+               this->data = g_strdup("getdictmask\n");
+               break;
+       /*case CMD_SET_COLLATE_FUNC:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("setcollatefunc %s\n", earg.c_str());
+               break;
+       }
+       case CMD_GET_COLLATE_FUNC:
+               this->data = g_strdup("getcollatefunc\n");
+               break;
+       case CMD_SET_LANGUAGE:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("setlanguage %s\n", earg.c_str());
+               break;
+       }
+       case CMD_GET_LANGUAGE:
+               this->data = g_strdup("getlanguage\n");
+               break;
+       case CMD_SET_EMAIL:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("setemail %s\n", earg.c_str());
+               break;
+       }
+       case CMD_GET_EMAIL:
+               this->data = g_strdup("getemail\n");
+               break;
+       case CMD_GET_USER_LEVEL:
+               this->data = g_strdup("getuserlevel\n");
+               break;*/
+       case CMD_MAX_DICT_COUNT:
+               this->data = g_strdup("maxdictcount\n");
+               break;
+       case CMD_DIR_INFO:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("dirinfo %s\n", earg.c_str());
+               break;
+       }
+       case CMD_DICT_INFO:
+       {
+               std::string earg;
+               arg_escape(earg, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("dictinfo %s\n", earg.c_str());
+               break;
+       }
+       /*case CMD_USER_LEVEL:
+       {
+               std::string earg1, earg2, earg3;
+               arg_escape(earg1, va_arg( ap, const char * ));
+               arg_escape(earg2, va_arg( ap, const char * ));
+               arg_escape(earg3, va_arg( ap, const char * ));
+               this->data = g_strdup_printf("userlevel %s %s %s\n", earg1.c_str(), earg2.c_str(), earg3.c_str());
+               break;
+       }*/
+       case CMD_QUIT:
+               this->data = g_strdup("quit\n");
+               break;
+       }
+       va_end( ap );
+}
+
+STARDICT::LookupResponse::DictResponse::DictResponse()
+{
+       oword = NULL;
+}
+
+STARDICT::LookupResponse::DictResponse::~DictResponse()
+{
+       g_free(oword);
+       for (std::list<DictResult *>::iterator i = dict_result_list.begin(); i != dict_result_list.end(); ++i) {
+               delete *i;
+       }
+}
+
+STARDICT::LookupResponse::DictResponse::DictResult::DictResult()
+{
+       bookname = NULL;
+}
+
+STARDICT::LookupResponse::DictResponse::DictResult::~DictResult()
+{
+       g_free(bookname);
+       for (std::list<WordResult *>::iterator i = word_result_list.begin(); i != word_result_list.end(); ++i) {
+               delete *i;
+       }
+}
+
+STARDICT::LookupResponse::DictResponse::DictResult::WordResult::WordResult()
+{
+       word = NULL;
+}
+
+STARDICT::LookupResponse::DictResponse::DictResult::WordResult::~WordResult()
+{
+       g_free(word);
+       for (std::list<char *>::iterator i = datalist.begin(); i != datalist.end(); ++i) {
+               g_free(*i);
+       }
+}
+
+STARDICT::LookupResponse::~LookupResponse()
+{
+    if (listtype == ListType_List) {
+       for (std::list<char *>::iterator i = wordlist->begin(); i != wordlist->end(); ++i) {
+               g_free(*i);
+           }
+        delete wordlist;
+    } else if (listtype == ListType_Tree) {
+        for (std::list<WordTreeElement *>::iterator i = wordtree->begin(); i != wordtree->end(); ++i) {
+            g_free((*i)->bookname);
+            for (std::list<char *>::iterator j = (*i)->wordlist.begin(); j != (*i)->wordlist.end(); ++j) {
+                g_free(*j);
+            }
+            delete *i;
+        }
+        delete wordtree;
+    }
+}
+
+STARDICT::Cmd::~Cmd()
+{
+    if (this->command == CMD_AUTH) {
+        delete this->auth;
+    } else {
+        g_free(this->data);
+    }
+    if (this->command == CMD_LOOKUP || this->command == CMD_DEFINE || this->command == CMD_SELECT_QUERY || this->command == CMD_SMART_QUERY) {
+        delete this->lookup_response;
+    } else if (this->command == CMD_PREVIOUS || this->command == CMD_NEXT) {
+        if (this->wordlist_response) {
+            for (std::list<char *>::iterator i = this->wordlist_response->begin(); i != this->wordlist_response->end(); ++i) {
+                g_free(*i);
+            }
+            delete this->wordlist_response;
+        }
+    }
+}
+
+StarDictCache::StarDictCache()
+{
+    str_pool.resize(str_pool_size);
+    for (size_t i = 0; i< str_pool_size; i++) {
+        str_pool[i] = NULL;
+    }
+    cur_str_pool_pos = 0;
+
+    lookup_response_pool.resize(lookup_response_pool_size);
+    for (size_t i = 0; i< lookup_response_pool_size; i++) {
+        lookup_response_pool[i] = NULL;
+    }
+    cur_lookup_response_pool_pos = 0;
+}
+
+StarDictCache::~StarDictCache()
+{
+    clean_all_cache();
+}
+
+void StarDictCache::clean_all_cache()
+{
+    for (std::vector<StrElement *>::iterator i = str_pool.begin(); i != str_pool.end(); ++i) {
+        if (*i) {
+            g_free((*i)->data);
+            delete *i;
+            *i = NULL;
+        }
+    }
+    clean_cache_lookup_response();
+}
+
+void StarDictCache::clean_cache_lookup_response()
+{
+    for (std::vector<LookupResponseElement *>::iterator i = lookup_response_pool.begin(); i != lookup_response_pool.end(); ++i) {
+        if (*i) {
+            delete ((*i)->lookup_response);
+            delete *i;
+            *i = NULL;
+        }
+    }
+}
+
+char *StarDictCache::get_cache_str(const char *key_str)
+{
+    for (std::vector<StrElement *>::iterator i = str_pool.begin(); i != str_pool.end(); ++i) {
+        if (*i) {
+            if ((*i)->key == key_str)
+                return (*i)->data;
+        }
+    }
+    return NULL;
+}
+
+STARDICT::LookupResponse *StarDictCache::get_cache_lookup_response(const char *key_str)
+{
+    for (std::vector<LookupResponseElement *>::iterator i = lookup_response_pool.begin(); i != lookup_response_pool.end(); ++i) {
+        if (*i) {
+            if ((*i)->key == key_str)
+                return (*i)->lookup_response;
+        }
+    }
+    return NULL;
+}
+
+void StarDictCache::clean_cache_str(const char *key_str)
+{
+    for (std::vector<StrElement *>::iterator i = str_pool.begin(); i != str_pool.end(); ++i) {
+        if (*i) {
+            if ((*i)->key == key_str) {
+                g_free((*i)->data);
+                delete *i;
+                *i = NULL;
+                //return;
+            }
+        }
+    }
+}
+
+void StarDictCache::save_cache_str(const char *key_str, char *data)
+{
+    if (str_pool[cur_str_pool_pos]) {
+        g_free(str_pool[cur_str_pool_pos]->data);
+        delete str_pool[cur_str_pool_pos];
+    }
+    str_pool[cur_str_pool_pos] = new StrElement();
+    str_pool[cur_str_pool_pos]->key = key_str;
+    str_pool[cur_str_pool_pos]->data = data;
+    cur_str_pool_pos++;
+    if (cur_str_pool_pos == str_pool_size)
+        cur_str_pool_pos = 0;
+}
+
+void StarDictCache::save_cache_lookup_response(const char *key_str, STARDICT::LookupResponse *lookup_response)
+{
+    if (lookup_response_pool[cur_lookup_response_pool_pos]) {
+        delete lookup_response_pool[cur_lookup_response_pool_pos]->lookup_response;
+        delete lookup_response_pool[cur_lookup_response_pool_pos];
+    }
+    lookup_response_pool[cur_lookup_response_pool_pos] = new LookupResponseElement();
+    lookup_response_pool[cur_lookup_response_pool_pos]->key = key_str;
+    lookup_response_pool[cur_lookup_response_pool_pos]->lookup_response = lookup_response;
+    cur_lookup_response_pool_pos++;
+    if (cur_lookup_response_pool_pos == lookup_response_pool_size)
+        cur_lookup_response_pool_pos = 0;
+}
+
+StarDictClient::StarDictClient()
+{
+       sd_ = -1;
+    channel_ = NULL;
+    in_source_id_ = 0;
+    out_source_id_ = 0;
+    is_connected_ = false;
+}
+
+StarDictClient::~StarDictClient()
+{
+    disconnect();
+}
+
+void StarDictClient::set_server(const char *host, int port)
+{
+    if (host_ != host || port_ != port) {
+        host_ = host;
+        port_ = port;
+       host_resolved = false;
+        clean_all_cache();
+    }
+}
+
+void StarDictClient::set_auth(const char *user, const char *md5passwd)
+{
+    if (user_ != user || md5passwd_ != md5passwd) {
+        user_ = user;
+        md5passwd_ = md5passwd;
+        clean_all_cache();
+    }
+}
+
+bool StarDictClient::try_cache(STARDICT::Cmd *c)
+{
+    if (c->command == STARDICT::CMD_LOOKUP || c->command == STARDICT::CMD_DEFINE || c->command == STARDICT::CMD_SELECT_QUERY || c->command == STARDICT::CMD_SMART_QUERY) {
+        STARDICT::LookupResponse *res = get_cache_lookup_response(c->data);
+        if (res) {
+            if (c->command == STARDICT::CMD_LOOKUP || c->command == STARDICT::CMD_DEFINE)
+                on_lookup_end_.emit(res, 0);
+            else if (c->command == STARDICT::CMD_SELECT_QUERY || c->command == STARDICT::CMD_SMART_QUERY)
+                on_floatwin_lookup_end_.emit(res, 0);
+            delete c;
+            return true;
+        } else {
+            return false;
+        }
+    }
+    if (c->command == STARDICT::CMD_PREVIOUS || c->command == STARDICT::CMD_NEXT) {
+        // Not implemented yet.
+        return false;
+    }
+    char *data = get_cache_str(c->data);
+    if (data) {
+        switch (c->command) {
+            case STARDICT::CMD_DIR_INFO:
+                on_dirinfo_end_.emit(data);
+                break;
+            case STARDICT::CMD_DICT_INFO:
+                on_dictinfo_end_.emit(data);
+                break;
+            case STARDICT::CMD_GET_DICT_MASK:
+                on_getdictmask_end_.emit(data);
+                break;
+            case STARDICT::CMD_MAX_DICT_COUNT:
+                on_maxdictcount_end_.emit(atoi(data));
+                break;
+        }
+        delete c;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void StarDictClient::send_commands(int num, ...)
+{
+    STARDICT::Cmd *c;
+    if (!is_connected_) {
+#ifdef _WIN32
+        c = new STARDICT::Cmd(STARDICT::CMD_CLIENT, "StarDict Windows");
+#else
+        c = new STARDICT::Cmd(STARDICT::CMD_CLIENT, "StarDict Linux");
+#endif
+        cmdlist.push_back(c);
+        if (!user_.empty() && !md5passwd_.empty()) {
+            c = new STARDICT::Cmd(STARDICT::CMD_AUTH, user_.c_str(), md5passwd_.c_str());
+            cmdlist.push_back(c);
+        }
+    }
+    va_list    ap;
+    va_start( ap, num);
+    for (int i = 0; i< num; i++) {
+        c = va_arg( ap, STARDICT::Cmd *);
+           cmdlist.push_back(c);
+    }
+    va_end( ap );
+    if (!is_connected_) {
+        waiting_banner_ = true;
+        connect();
+    }
+}
+
+void StarDictClient::try_cache_or_send_commands(int num, ...)
+{
+    STARDICT::Cmd *c;
+    std::list<STARDICT::Cmd *> send_cmdlist;
+    va_list    ap;
+    va_start( ap, num);
+    for (int i = 0; i< num; i++) {
+        c = va_arg( ap, STARDICT::Cmd *);
+        if (!try_cache(c)) {
+            send_cmdlist.push_back(c);
+        }
+    }
+    va_end( ap );
+    if (send_cmdlist.empty())
+        return;
+
+    if (!is_connected_) {
+#ifdef _WIN32
+        c = new STARDICT::Cmd(STARDICT::CMD_CLIENT, "StarDict Windows");
+#else
+        c = new STARDICT::Cmd(STARDICT::CMD_CLIENT, "StarDict Linux");
+#endif
+        cmdlist.push_back(c);
+        if (!user_.empty() && !md5passwd_.empty()) {
+            c = new STARDICT::Cmd(STARDICT::CMD_AUTH, user_.c_str(), md5passwd_.c_str());
+            cmdlist.push_back(c);
+        }
+    }
+    for (std::list<STARDICT::Cmd *>::iterator i = send_cmdlist.begin(); i!= send_cmdlist.end(); ++i) {
+           cmdlist.push_back(*i);
+    }
+    if (!is_connected_) {
+        waiting_banner_ = true;
+        connect();
+    }
+}
+
+void StarDictClient::write_str(const char *str, GError **err)
+{
+    int len = strlen(str);
+    int left_byte = len;
+    GIOStatus res;
+    gsize bytes_written;
+    while (left_byte) {
+        res = g_io_channel_write_chars(channel_, str+(len - left_byte), left_byte, &bytes_written, err);
+        if (res == G_IO_STATUS_ERROR) {
+            disconnect();
+            return;
+        }
+        left_byte -= bytes_written;
+    }
+    res = g_io_channel_flush(channel_, err);
+    if (res == G_IO_STATUS_ERROR) {
+        disconnect();
+    }
+       if (out_source_id_ == 0)
+               out_source_id_ = g_io_add_watch(channel_, GIOCondition(G_IO_OUT), on_io_out_event, this);
+}
+
+void StarDictClient::request_command()
+{
+    reading_type_ = READ_LINE;
+    if (cmdlist.empty()) {
+        cmdlist.push_back(new STARDICT::Cmd(STARDICT::CMD_QUIT));
+    }
+    STARDICT::Cmd *c = cmdlist.front();
+       switch (c->command) {
+        case STARDICT::CMD_AUTH:
+               {
+                       struct MD5Context ctx;
+                       unsigned char digest[16];
+                       char hex[33];
+                       int i;
+                       MD5Init(&ctx);
+                       MD5Update(&ctx, (const unsigned char*)cmd_reply.daemonStamp.c_str(), cmd_reply.daemonStamp.length());
+                       MD5Update(&ctx, (const unsigned char*)(c->auth->passwd.c_str()), c->auth->passwd.length());
+                       MD5Final(digest, &ctx );
+                       for (i = 0; i < 16; i++)
+                               sprintf( hex+2*i, "%02x", digest[i] );
+                       hex[32] = '\0';
+                       std::string earg1, earg2;
+                       arg_escape(earg1, c->auth->user.c_str());
+                       arg_escape(earg2, hex);
+                       char *data = g_strdup_printf("auth %s %s\n", earg1.c_str(), earg2.c_str());
+            GError *err = NULL;
+            write_str(data, &err);
+                       g_free(data);
+                       if (err) {
+                on_error_.emit(err->message);
+                g_error_free(err);
+                               return;
+                       }
+                       break;
+               }
+               default:
+        {
+            GError *err = NULL;
+            write_str(c->data, &err);
+                       if (err) {
+                on_error_.emit(err->message);
+                g_error_free(err);
+                               return;
+            }
+                       break;
+        }
+       }
+       return;
+}
+
+void StarDictClient::clean_command()
+{
+       for (std::list<STARDICT::Cmd *>::iterator i=cmdlist.begin(); i!=cmdlist.end(); ++i) {
+               delete *i;
+       }
+       cmdlist.clear();
+}
+
+void StarDictClient::connect()
+{
+    if (host_resolved) {
+           on_resolved(this, true, sa);
+    } else {
+        Socket::resolve(host_, this, on_resolved);
+    }
+}
+
+void StarDictClient::on_resolved(gpointer data, bool resolved, in_addr_t sa_)
+{
+    StarDictClient *oStarDictClient = (StarDictClient *)data;
+    if (!resolved) {
+       static bool showed_once = false;
+       if (!showed_once) {
+               showed_once = true;
+               gchar *mes = g_strdup_printf("Can not reslove %s: %s\n",
+                         oStarDictClient->host_.c_str(), Socket::get_error_msg().c_str());
+               on_error_.emit(mes);
+               g_free(mes);
+       }
+        return;
+    }
+
+    if (oStarDictClient->host_resolved == false) {
+            oStarDictClient->sa = sa_;
+           oStarDictClient->host_resolved = true;
+    }
+
+    oStarDictClient->sd_ = Socket::socket();
+
+    if (oStarDictClient->sd_ == -1) {
+        std::string str = "Can not create socket: " + Socket::get_error_msg();
+        on_error_.emit(str.c_str());
+        return;
+    }
+    Socket::connect(oStarDictClient->sd_, sa_, oStarDictClient->port_, oStarDictClient, on_connected);
+}
+
+void StarDictClient::on_connected(gpointer data, bool succeeded)
+{
+    StarDictClient *oStarDictClient = (StarDictClient *)data;
+    if (!succeeded) {
+       static bool showed_once = false;
+       if (!showed_once) {
+               showed_once = true;
+               gchar *mes = g_strdup_printf("Can not connect to %s: %s\n",
+                         oStarDictClient->host_.c_str(), Socket::get_error_msg().c_str());
+               on_error_.emit(mes);
+               g_free(mes);
+       }
+        return;
+    }
+#ifdef _WIN32
+    oStarDictClient->channel_ = g_io_channel_win32_new_socket(oStarDictClient->sd_);
+#else
+    oStarDictClient->channel_ = g_io_channel_unix_new(oStarDictClient->sd_);
+#endif
+
+    g_io_channel_set_encoding(oStarDictClient->channel_, NULL, NULL);
+
+    /* make sure that the channel is non-blocking */
+    int flags = g_io_channel_get_flags(oStarDictClient->channel_);
+    flags |= G_IO_FLAG_NONBLOCK;
+    GError *err = NULL;
+    g_io_channel_set_flags(oStarDictClient->channel_, GIOFlags(flags), &err);
+    if (err) {
+        g_io_channel_unref(oStarDictClient->channel_);
+        oStarDictClient->channel_ = NULL;
+        gchar *str = g_strdup_printf("Unable to set the channel as non-blocking: %s", err->message);
+        on_error_.emit(str);
+        g_free(str);
+        g_error_free(err);
+        return;
+    }
+
+    oStarDictClient->is_connected_ = true;
+    oStarDictClient->waiting_banner_ = true;
+    oStarDictClient->reading_type_ = READ_LINE;
+    oStarDictClient->in_source_id_ = g_io_add_watch(oStarDictClient->channel_, GIOCondition(G_IO_IN | G_IO_ERR), on_io_in_event, oStarDictClient);
+}
+
+void StarDictClient::disconnect()
+{
+    clean_command();
+    if (in_source_id_) {
+        g_source_remove(in_source_id_);
+        in_source_id_ = 0;
+    }
+    if (out_source_id_) {
+        g_source_remove(out_source_id_);
+        out_source_id_ = 0;
+    }
+
+    if (channel_) {
+        g_io_channel_shutdown(channel_, TRUE, NULL);
+        g_io_channel_unref(channel_);
+        channel_ = NULL;
+    }
+       if (sd_ != -1) {
+               Socket::close(sd_);
+               sd_ = -1;
+       }
+    is_connected_ = false;
+}
+
+gboolean StarDictClient::on_io_out_event(GIOChannel *ch, GIOCondition cond,
+                 gpointer user_data)
+{
+       StarDictClient *stardict_client = static_cast<StarDictClient *>(user_data);
+       GError *err = NULL;
+       GIOStatus res = g_io_channel_flush(stardict_client->channel_, &err);
+       if (res == G_IO_STATUS_AGAIN) {
+               return TRUE;
+       } else if (err) {
+               on_error_.emit(err->message);
+               g_error_free(err);
+       }
+       stardict_client->out_source_id_ = 0;
+       return FALSE;
+}
+
+gboolean StarDictClient::on_io_in_event(GIOChannel *ch, GIOCondition cond,
+                 gpointer user_data)
+{
+    StarDictClient *stardict_client = static_cast<StarDictClient *>(user_data);
+
+    if (!stardict_client->channel_) {
+        //g_warning("No channel available\n");
+        return FALSE;
+    }
+    if (cond & G_IO_ERR) {
+        /*gchar *mes =
+            g_strdup_printf("Connection failed to the dictionary server at %s:%d",
+                    stardict_client->host_.c_str(), stardict_client->port_);
+        on_error_.emit(mes);
+        g_free(mes);*/
+        stardict_client->disconnect();
+        return FALSE;
+    }
+    GError *err = NULL;
+    gsize term, len;
+    gchar *line;
+    GIOStatus res;
+
+    for (;;) {
+        if (!stardict_client->channel_)
+            break;
+        bool result;
+        if (stardict_client->reading_type_ == READ_SIZE) {
+            gsize bytes_read;
+            res = g_io_channel_read_chars(stardict_client->channel_, stardict_client->size_data+(stardict_client->size_count-stardict_client->size_left), stardict_client->size_left, &bytes_read, &err);
+            if (res == G_IO_STATUS_ERROR || res == G_IO_STATUS_EOF) {
+                if (err) {
+                    gchar *str = g_strdup_printf("Error while reading reply from server: %s", err->message);
+                    on_error_.emit(str);
+                    g_free(str);
+                    g_error_free(err);
+                }
+                stardict_client->disconnect();
+
+                return FALSE;
+            }
+            stardict_client->size_left -= bytes_read;
+            if (stardict_client->size_left == 0)
+                result = stardict_client->parse(stardict_client->size_data);
+            else
+                break;
+        } else {
+            if (stardict_client->reading_type_ == READ_LINE)
+                g_io_channel_set_line_term(stardict_client->channel_, "\n", 1);
+            else if (stardict_client->reading_type_ == READ_STRING)
+                g_io_channel_set_line_term(stardict_client->channel_, "", 1);
+
+            res = g_io_channel_read_line(stardict_client->channel_, &line,
+                             &len, &term, &err);
+            if (res == G_IO_STATUS_ERROR || res == G_IO_STATUS_EOF) {
+                if (err) {
+                    gchar *str = g_strdup_printf("Error while reading reply from server: %s", err->message);
+                    on_error_.emit(str);
+                    g_free(str);
+                    g_error_free(err);
+                }
+                stardict_client->disconnect();
+
+                return FALSE;
+            }
+
+            if (!len)
+                break;
+
+            //truncate the line terminator before parsing
+            line[term] = '\0';
+            result = stardict_client->parse(line);
+        }
+        if (!result) {
+            stardict_client->disconnect();
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+int StarDictClient::parse_banner(gchar *line)
+{
+    int status;
+    status = atoi(line);
+    if (status != CODE_HELLO) {
+        if (status == CODE_TEMPORARILY_UNAVAILABLE) {
+            printf("Server temporarily unavailable!\n");
+        } else {
+            printf("Unexpected status code %d\n", status);
+        }
+        return 0;
+    }
+    char *p;
+    p = strrchr(line, ' ');
+    if (p) {
+        p++;
+        cmd_reply.daemonStamp = p;
+    }
+    return 1;
+}
+
+int StarDictClient::parse_command_client(gchar *line)
+{
+    int status;
+    status = atoi(line);
+    if (status != CODE_OK) {
+       gchar *str = g_strdup_printf("Client denied: %s", line);
+       on_error_.emit(str);
+       g_free(str);
+        return 0;
+    }
+    return 1;
+}
+
+int StarDictClient::parse_command_auth(gchar *line)
+{
+    int status;
+    status = atoi(line);
+    if (status != CODE_OK) {
+        gchar *str = g_strdup_printf(_("Authentication denied: %s"), line);
+        on_error_.emit(str);
+        g_free(str);
+        return 0;
+    }
+    return 1;
+}
+
+int StarDictClient::parse_command_register(gchar *line)
+{
+    int status;
+    status = atoi(line);
+    if (status != CODE_OK) {
+        gchar *str = g_strdup_printf(_("Register failed: %s"), line);
+        on_error_.emit(str);
+        g_free(str);
+        return 0;
+    }
+    on_register_end_.emit(_("Register success!"));
+    return 1;
+}
+
+int StarDictClient::parse_command_quit(gchar *line)
+{
+    int status;
+    status = atoi(line);
+    if (status != CODE_GOODBYE) {
+    }
+    return 0;
+}
+
+int StarDictClient::parse_command_setdictmask(gchar *line)
+{
+    int status;
+    status = atoi(line);
+    if (status != CODE_OK) {
+        gchar *str = g_strdup_printf("Set Dict Mask failed: %s", line);
+        on_error_.emit(str);
+        g_free(str);
+        return 0;
+    }
+    clean_cache_str("getdictmask\n");
+    clean_cache_lookup_response();
+    return 1;
+}
+
+int StarDictClient::parse_command_getdictmask(STARDICT::Cmd* cmd, gchar *buf)
+{
+    if (cmd->reading_status == 0) {
+        int status;
+        status = atoi(buf);
+        if (status != CODE_OK) {
+           g_free(buf);
+            on_error_.emit(_("You haven't setup the account. Please open the \"Net Dict\" page in the Preferences dialog and register an account first."));
+            return 0;
+        }
+       g_free(buf);
+       cmd->reading_status = 1;
+       reading_type_ = READ_STRING;
+    } else if (cmd->reading_status == 1) {
+        on_getdictmask_end_.emit(buf);
+        save_cache_str(cmd->data, buf);
+        return 1;
+    }
+    return 2;
+}
+
+int StarDictClient::parse_command_dirinfo(STARDICT::Cmd* cmd, gchar *buf)
+{
+    if (cmd->reading_status == 0) {
+        int status;
+        status = atoi(buf);
+        if (status != CODE_OK) {
+            gchar *str = g_strdup_printf("Get dir info failed: %s", buf);
+            g_free(buf);
+            on_error_.emit(str);
+            g_free(str);
+            return 0;
+        }
+        g_free(buf);
+        cmd->reading_status = 1;
+        reading_type_ = READ_STRING;
+    } else if (cmd->reading_status == 1) {
+        on_dirinfo_end_.emit(buf);
+        save_cache_str(cmd->data, buf);
+        return 1;
+    }
+    return 2;
+}
+
+int StarDictClient::parse_command_dictinfo(STARDICT::Cmd* cmd, gchar *buf)
+{
+    if (cmd->reading_status == 0) {
+        int status;
+        status = atoi(buf);
+        if (status != CODE_OK) {
+            gchar *str = g_strdup_printf("Get dict info failed: %s", buf);
+            g_free(buf);
+            on_error_.emit(str);
+            g_free(str);
+            return 0;
+        }
+        g_free(buf);
+        cmd->reading_status = 1;
+        reading_type_ = READ_STRING;
+    } else if (cmd->reading_status == 1) {
+        on_dictinfo_end_.emit(buf);
+        save_cache_str(cmd->data, buf);
+        return 1;
+    }
+    return 2;
+}
+
+int StarDictClient::parse_command_maxdictcount(STARDICT::Cmd* cmd, gchar *buf)
+{
+    if (cmd->reading_status == 0) {
+        int status;
+        status = atoi(buf);
+        if (status != CODE_OK) {
+            gchar *str = g_strdup_printf("Get max dict count failed: %s", buf);
+            g_free(buf);
+            on_error_.emit(str);
+            g_free(str);
+            return 0;
+        }
+        g_free(buf);
+        cmd->reading_status = 1;
+        reading_type_ = READ_STRING;
+    } else if (cmd->reading_status == 1) {
+        on_maxdictcount_end_.emit(atoi(buf));
+        save_cache_str(cmd->data, buf);
+        return 1;
+    }
+    return 2;
+}
+
+int StarDictClient::parse_wordlist(STARDICT::Cmd* cmd, gchar *buf)
+{
+    if (cmd->reading_status == 0) { // Read code.
+        int status;
+        status = atoi(buf);
+        g_free(buf);
+        if (status != CODE_OK) {
+            return 0;
+        }
+        cmd->wordlist_response = new std::list<char *>;
+        cmd->reading_status = 1;
+        reading_type_ = READ_STRING;
+    } else if (cmd->reading_status == 1) {
+        if (*buf == '\0') {
+            g_free(buf);
+            if (cmd->command == STARDICT::CMD_PREVIOUS) {
+                on_previous_end_.emit(cmd->wordlist_response);
+                return 1;
+            } else {
+                on_next_end_.emit(cmd->wordlist_response);
+                return 1;
+            }
+        } else {
+            cmd->wordlist_response->push_back(buf);
+        }
+    }
+    return 2;
+}
+
+int StarDictClient::parse_dict_result(STARDICT::Cmd* cmd, gchar *buf)
+{
+    if (cmd->reading_status == 0) { // Read code.
+        int status;
+        status = atoi(buf);
+        g_free(buf);
+        if (status != CODE_OK) {
+            if (status == CODE_DICTMASK_NOTSET) {
+                on_error_.emit(_("You haven't chosen any dictionaries, please choose some by clicking \"Manage Dict\"->\"Network dictionaries\"->\"Add\"."));
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+        cmd->lookup_response = new STARDICT::LookupResponse();
+        cmd->lookup_response->listtype = STARDICT::LookupResponse::ListType_None;
+        cmd->reading_status = 1;
+        reading_type_ = READ_STRING;
+    } else if (cmd->reading_status == 1) { // Read original word.
+        cmd->lookup_response->dict_response.oword = buf;
+        cmd->reading_status = 2;
+    } else if (cmd->reading_status == 2) { // Read book name.
+        if (*buf == '\0') {
+            g_free(buf);
+            if (cmd->command == STARDICT::CMD_DEFINE) {
+                on_lookup_end_.emit(cmd->lookup_response, cmd->seq);
+                save_cache_lookup_response(cmd->data, cmd->lookup_response);
+                cmd->lookup_response = NULL;
+                return 1;
+            } else if ( cmd->command == STARDICT::CMD_SELECT_QUERY || cmd->command == STARDICT::CMD_SMART_QUERY) {
+                on_floatwin_lookup_end_.emit(cmd->lookup_response, cmd->seq);
+                save_cache_lookup_response(cmd->data, cmd->lookup_response);
+                cmd->lookup_response = NULL;
+                return 1;
+            }
+            cmd->reading_status = 6;
+            reading_type_ = READ_STRING;
+        } else {
+            struct STARDICT::LookupResponse::DictResponse::DictResult *dict_result = new STARDICT::LookupResponse::DictResponse::DictResult();
+            dict_result->bookname = buf;
+            cmd->lookup_response->dict_response.dict_result_list.push_back(dict_result);
+            cmd->reading_status = 3;
+        }
+    } else if (cmd->reading_status == 3) { // Read word.
+        if (*buf == '\0') {
+            g_free(buf);
+            cmd->reading_status = 2;
+        } else {
+            struct STARDICT::LookupResponse::DictResponse::DictResult::WordResult *word_result = new STARDICT::LookupResponse::DictResponse::DictResult::WordResult();
+            word_result->word = buf;
+            cmd->lookup_response->dict_response.dict_result_list.back()->word_result_list.push_back(word_result);;
+            cmd->reading_status = 4;
+            reading_type_ = READ_SIZE;
+           size_data = (char *)g_malloc(sizeof(guint32));
+           size_count = size_left = sizeof(guint32);
+        }
+    } else if (cmd->reading_status == 4) {
+        guint32 datasize = g_ntohl(get_uint32(buf));
+       memcpy(buf, &datasize, sizeof(guint32));
+        if (datasize == 0) {
+           g_free(buf);
+            cmd->reading_status = 3;
+            reading_type_ = READ_STRING;
+        } else {
+           cmd->reading_status = 5;
+           size_data = (char *)g_realloc(buf, datasize + sizeof(guint32));
+           size_count = datasize + sizeof(guint32);
+           size_left = datasize;
+        }
+    } else if (cmd->reading_status == 5) {
+            cmd->lookup_response->dict_response.dict_result_list.back()->word_result_list.back()->datalist.push_back(buf);
+           cmd->reading_status = 4;
+           size_data = (char *)g_malloc(sizeof(guint32));
+           size_count = size_left = sizeof(guint32);
+    } else if (cmd->reading_status == 6) {
+        if (strcmp(buf, "d") == 0) {
+            cmd->reading_status = 8;
+            cmd->lookup_response->listtype = STARDICT::LookupResponse::ListType_Tree;
+            cmd->lookup_response->wordtree = new std::list<STARDICT::LookupResponse::WordTreeElement *>;
+        } else {
+            cmd->reading_status = 7;
+            if (strcmp(buf, "r") == 0)
+                cmd->lookup_response->listtype = STARDICT::LookupResponse::ListType_Rule_List;
+            else if (strcmp(buf, "g") == 0)
+                cmd->lookup_response->listtype = STARDICT::LookupResponse::ListType_Regex_List;
+            else if (strcmp(buf, "f") == 0)
+                cmd->lookup_response->listtype = STARDICT::LookupResponse::ListType_Fuzzy_List;
+            else 
+                cmd->lookup_response->listtype = STARDICT::LookupResponse::ListType_List;
+            cmd->lookup_response->wordlist = new std::list<char *>;
+        }
+        g_free(buf);
+    } else if (cmd->reading_status == 7) {
+        if (*buf == '\0') {
+            g_free(buf);
+            on_lookup_end_.emit(cmd->lookup_response, cmd->seq);
+            save_cache_lookup_response(cmd->data, cmd->lookup_response);
+            cmd->lookup_response = NULL;
+            return 1;
+        } else {
+            cmd->lookup_response->wordlist->push_back(buf);
+        }
+    } else if (cmd->reading_status == 8) {
+        if (*buf == '\0') {
+            g_free(buf);
+            on_lookup_end_.emit(cmd->lookup_response, cmd->seq);
+            save_cache_lookup_response(cmd->data, cmd->lookup_response);
+            cmd->lookup_response = NULL;
+            return 1;
+        } else {
+            STARDICT::LookupResponse::WordTreeElement *element = new STARDICT::LookupResponse::WordTreeElement();
+            element->bookname = buf;
+            cmd->lookup_response->wordtree->push_back(element);
+            cmd->reading_status = 9;
+        }
+    } else if (cmd->reading_status == 9) {
+        if (*buf == '\0') {
+            g_free(buf);
+            cmd->reading_status = 8;
+        } else {
+            cmd->lookup_response->wordtree->back()->wordlist.push_back(buf);
+        }
+    }
+    return 2;
+}
+
+bool StarDictClient::parse(gchar *line)
+{
+    int result;
+    if (waiting_banner_) {
+        waiting_banner_ = false;
+        result = parse_banner(line);
+        g_free(line);
+        if (!result)
+            return false;
+        request_command();
+        return true;
+    }
+    STARDICT::Cmd* cmd = cmdlist.front();
+    switch (cmd->command) {
+        case STARDICT::CMD_CLIENT:
+            result = parse_command_client(line);
+            g_free(line);
+            break;
+        case STARDICT::CMD_AUTH:
+            result = parse_command_auth(line);
+            g_free(line);
+            break;
+        case STARDICT::CMD_REGISTER:
+            result = parse_command_register(line);
+            g_free(line);
+            break;
+        case STARDICT::CMD_GET_DICT_MASK:
+            result = parse_command_getdictmask(cmd, line);
+            break;
+        case STARDICT::CMD_SET_DICT_MASK:
+            result = parse_command_setdictmask(line);
+            break;
+        case STARDICT::CMD_DIR_INFO:
+            result = parse_command_dirinfo(cmd, line);
+            break;
+        case STARDICT::CMD_DICT_INFO:
+            result = parse_command_dictinfo(cmd, line);
+            break;
+        case STARDICT::CMD_MAX_DICT_COUNT:
+            result = parse_command_maxdictcount(cmd, line);
+            break;
+        case STARDICT::CMD_DEFINE:
+        case STARDICT::CMD_LOOKUP:
+        case STARDICT::CMD_SELECT_QUERY:
+        case STARDICT::CMD_SMART_QUERY:
+            result = parse_dict_result(cmd, line);
+            break;
+        case STARDICT::CMD_PREVIOUS:
+        case STARDICT::CMD_NEXT:
+            result = parse_wordlist(cmd, line);
+            break;
+        case STARDICT::CMD_QUIT:
+            result = parse_command_quit(line);
+            g_free(line);
+            break;
+        default:
+            result = 0;
+            g_free(line);
+            break;
+    }
+    if (result == 0)
+        return false;
+    if (result == 1) {
+        delete cmd;
+        cmdlist.pop_front();
+        if (cmdlist.empty()) {
+            cmdlist.push_back(new STARDICT::Cmd(STARDICT::CMD_QUIT));
+        }
+        request_command();
+    }
+    return true;
+}