Tizen 2.1 base
[platform/core/uifw/ise-engine-sunpinyin.git] / src / ime-core / imi_plugin.cpp
1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
5  *
6  * The contents of this file are subject to the terms of either the GNU Lesser
7  * General Public License Version 2.1 only ("LGPL") or the Common Development and
8  * Distribution License ("CDDL")(collectively, the "License"). You may not use this
9  * file except in compliance with the License. You can obtain a copy of the CDDL at
10  * http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
11  * http://www.opensource.org/licenses/lgpl-license.php. See the License for the
12  * specific language governing permissions and limitations under the License. When
13  * distributing the software, include this License Header Notice in each file and
14  * include the full text of the License in the License file as well as the
15  * following notice:
16  *
17  * NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
18  * (CDDL)
19  * For Covered Software in this distribution, this License shall be governed by the
20  * laws of the State of California (excluding conflict-of-law provisions).
21  * Any litigation relating to this License shall be subject to the jurisdiction of
22  * the Federal Courts of the Northern District of California and the state courts
23  * of the State of California, with venue lying in Santa Clara County, California.
24  *
25  * Contributor(s):
26  *
27  * If you wish your version of this file to be governed by only the CDDL or only
28  * the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
29  * include this software in this distribution under the [CDDL or LGPL Version 2.1]
30  * license." If you don't indicate a single choice of license, a recipient has the
31  * option to distribute your version of this file under either the CDDL or the LGPL
32  * Version 2.1, or to extend the choice of license to its licensees as provided
33  * above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
34  * Version 2 license, then the option applies only if the new code is made subject
35  * to such option by the copyright holder.
36  */
37
38 #include <Python.h>
39 #include <signal.h>
40 #include <sstream>
41
42 #include "portability.h"
43 #include "imi_plugin.h"
44
45 CIMIPlugin::CIMIPlugin(TPluginTypeEnum pluginType)
46     : m_pluginType(pluginType)
47 {}
48
49 CIMIPlugin::~CIMIPlugin()
50 {}
51
52 class CIMIPythonPlugin : public CIMIPlugin
53 {
54 public:
55     CIMIPythonPlugin(std::string filename);
56     virtual ~CIMIPythonPlugin();
57
58     virtual std::string getName() { return m_name; }
59     virtual std::string getAuthor() { return m_author; }
60     virtual std::string getDescription() { return m_description; }
61
62     virtual TPluginCandidates provide_candidates(const TPluginPreedit& str,
63                                                  int* waitTime);
64     virtual TPluginCandidate  translate_candidate(const TPluginCandidate& candi,
65                                                   int* waitTime);
66 private:
67     PyObject* m_module;
68     PyObject* m_provide_method;
69     PyObject* m_trans_method;
70
71     std::string m_name;
72     std::string m_author;
73     std::string m_description;
74 };
75
76 CIMIPythonPlugin::CIMIPythonPlugin(std::string filename)
77     : CIMIPlugin(CIMI_PLUGIN_PYTHON), m_module(NULL), m_provide_method(NULL),
78       m_trans_method(NULL)
79 {
80     // filename always ends with .py
81     std::string module_name = filename.substr(0, filename.length() - 3);
82     CIMIPluginManager& manager = AIMIPluginManager::instance();
83     PyObject* dict = NULL;
84     PyObject* name = NULL;
85     PyObject* author = NULL;
86     PyObject* description = NULL;
87
88     m_module = PyImport_ImportModule(module_name.c_str());
89     if (m_module == NULL) {
90         goto error;
91     }
92     dict = PyModule_GetDict(m_module);
93     if (dict == NULL) {
94         goto error;
95     }
96     m_provide_method = PyDict_GetItemString(dict, "provide_candidates");
97     m_trans_method = PyDict_GetItemString(dict, "translate_candidate");
98     name = PyDict_GetItemString(dict, "__NAME__");
99     author = PyDict_GetItemString(dict, "__AUTHOR__");
100     description = PyDict_GetItemString(dict, "__DESCRIPTION__");
101
102     if (name != NULL && PyString_Check(name)) {
103         m_name = PyString_AsString(name);
104     }
105     if (author != NULL && PyString_Check(author)) {
106         m_author = PyString_AsString(author);
107     }
108     if (description != NULL && PyString_Check(description)) {
109         m_description = PyString_AsString(description);
110     }
111     return;
112 error:
113     manager.setLastError("Error when loading Python module");
114     return;
115 }
116
117 CIMIPythonPlugin::~CIMIPythonPlugin()
118 {
119     Py_XDECREF(m_module);
120 }
121
122 static PyObject*
123 Py_Call1(PyObject* method, PyObject* obj)
124 {
125     PyObject* args = PyTuple_Pack(1, obj);
126     PyObject* ret = PyObject_CallObject(method, args);
127     Py_XDECREF(args);
128     if (ret == NULL) {
129         PyErr_PrintEx(2);
130     }
131     return ret;
132 }
133
134 static const size_t TWCharBufferSize = 2048;
135
136 static wstring
137 PyUnicode_AsWString(PyObject* obj)
138 {
139     TWCHAR* wide_str_buf = new TWCHAR[TWCharBufferSize];
140     wstring res;
141     memset(wide_str_buf, 0, sizeof(TWCHAR) * TWCharBufferSize);
142
143     Py_ssize_t size = PyUnicode_AsWideChar((PyUnicodeObject*) obj,
144                                            (wchar_t*) wide_str_buf,
145                                            TWCharBufferSize);
146     if (size > 0) {
147         res = wstring(wide_str_buf);
148     }
149     delete [] wide_str_buf;
150     return res;
151 }
152
153 static void
154 ExtractSequence(TPluginCandidates& result, PyObject* py_seq)
155 {
156     Py_ssize_t len = PySequence_Length(py_seq);
157     for (Py_ssize_t i = 0; i < len; i++) {
158         PyObject* tuple_item_obj = PySequence_GetItem(py_seq, i);
159         if (!PyTuple_Check(tuple_item_obj)) {
160             continue;
161         }
162         PyObject* rank_obj = PyTuple_GetItem(tuple_item_obj, 0);
163         PyObject* candi_obj = PyTuple_GetItem(tuple_item_obj, 1);
164         if (rank_obj == NULL || !PyInt_Check(rank_obj) || candi_obj == NULL
165             || !PyUnicode_Check(candi_obj)) {
166             continue;
167         }
168
169         result.push_back(TPluginCandidateItem((int) PyInt_AsLong(rank_obj),
170                                               PyUnicode_AsWString(candi_obj)));
171     }
172 }
173
174 TPluginCandidates
175 CIMIPythonPlugin::provide_candidates(const TPluginPreedit& str,
176                                      int* waitTime)
177 {
178     TPluginCandidates res;
179     *waitTime = 0;
180
181     if (m_provide_method == NULL) {
182         *waitTime = -1;
183         return res;
184     }
185
186     PyObject* str_obj = PyUnicode_FromWideChar((wchar_t*) str.c_str(),
187                                                str.size());
188     PyObject* ret_obj = Py_Call1(m_provide_method, str_obj);
189
190     if (ret_obj == NULL) {
191         *waitTime = -1;
192     } else if (PyInt_Check(ret_obj)) {
193         *waitTime = (int) PyInt_AsLong(ret_obj);
194     } else if (PyTuple_Check(ret_obj) && PyTuple_Size(ret_obj) == 2) {
195         PyObject* time_obj = PyTuple_GetItem(ret_obj, 0);
196         PyObject* seq_obj = PyTuple_GetItem(ret_obj, 1);
197         if (PyInt_Check(time_obj) && PyList_Check(seq_obj)) {
198             *waitTime = (int) PyInt_AsLong(time_obj);
199             ExtractSequence(res, seq_obj);
200         }
201     } else if (PyList_Check(ret_obj)) {
202         // extract all items inside this sequence.
203         ExtractSequence(res, ret_obj);
204     }
205     Py_XDECREF(str_obj);
206     Py_XDECREF(ret_obj);
207     return res;
208 }
209
210 TPluginCandidate
211 CIMIPythonPlugin::translate_candidate(const TPluginCandidate& candi,
212                                       int* waitTime)
213 {
214     TPluginCandidate res;
215     *waitTime = 0;
216
217     if (m_trans_method == NULL) {
218         *waitTime = -1;
219         return res;
220     }
221
222     PyObject* str_obj = PyUnicode_FromWideChar((wchar_t*) candi.c_str(),
223                                                candi.size());
224     PyObject* ret_obj = Py_Call1(m_trans_method, str_obj);
225     if (ret_obj == NULL) {
226         *waitTime = -1;
227     } else if (PyInt_Check(ret_obj)) {
228         *waitTime = (int) PyInt_AsLong(ret_obj);
229     } else if (PyUnicode_Check(ret_obj)) {
230         res = TPluginCandidate(PyUnicode_AsWString(ret_obj));
231     }
232     Py_XDECREF(str_obj);
233     Py_XDECREF(ret_obj);
234     return res;
235 }
236
237 static void
238 InitializePython()
239 {
240     if (Py_IsInitialized())
241         return;
242     std::stringstream eval_str;
243
244     // append plugin module path to default load path
245     Py_Initialize();
246     signal(SIGINT, SIG_DFL);
247
248     PyRun_SimpleString("import sys");
249     eval_str << "sys.path.append(r'" << getenv("HOME")
250              << "/.sunpinyin/plugins/" << "')";
251     PyRun_SimpleString(eval_str.str().c_str());
252 }
253
254 #define PLUGIN_LIST_FILE "/.sunpinyin/plugins.list";
255 #define PLUGIN_NAME_LEN 128
256
257 CIMIPluginManager::CIMIPluginManager()
258     : m_hasError(false), m_waitTime(0)
259 {
260     InitializePython();
261 }
262
263 CIMIPluginManager::~CIMIPluginManager()
264 {
265     for (size_t i = 0;  i < m_plugins.size(); i++) {
266         delete m_plugins[i];
267     }
268 }
269
270 void
271 CIMIPluginManager::initializePlugins()
272 {
273     // load configuration file which list all needed plugins
274     std::string plugin_list_path = getenv("HOME");
275     plugin_list_path += PLUGIN_LIST_FILE;
276     FILE* fp = fopen(plugin_list_path.c_str(), "r");
277     if (!fp) {
278         return;
279     }
280     while (true) {
281         char plugin_name[PLUGIN_NAME_LEN];
282         memset(plugin_name, 0, PLUGIN_NAME_LEN);
283         fgets(plugin_name, PLUGIN_NAME_LEN, fp);
284         if (strlen(plugin_name) == 0) {
285             break;
286         }
287         if (strlen(plugin_name) == 1) {
288             continue;
289         }
290         plugin_name[strlen(plugin_name) - 1] = 0; // remove the \n at the end
291         if (loadPlugin(plugin_name) == NULL) {
292             fprintf(stderr, "Error! Cannot load plugin %s\n", plugin_name);
293         }
294     }
295     fclose(fp);
296 }
297
298 TPluginTypeEnum
299 CIMIPluginManager::detectPluginType(std::string filename)
300 {
301     if (filename.length() >= 3
302         && filename.substr(filename.length() - 3) == ".py") {
303         return CIMI_PLUGIN_PYTHON;
304     } else {
305         return CIMI_PLUGIN_UNKNOWN;
306     }
307 }
308
309 CIMIPlugin*
310 CIMIPluginManager::loadPlugin(std::string filename)
311 {
312     TPluginTypeEnum type = detectPluginType(filename);
313     CIMIPlugin* plugin = createPlugin(filename, type);
314     std::stringstream error;
315
316     if (plugin == NULL) {
317         return NULL;
318     }
319     if (hasLastError()) {
320         delete plugin;
321         return NULL;
322     }
323
324     for (size_t i = 0; i < m_plugins.size(); i++) {
325         if (m_plugins[i]->getName() == plugin->getName()) {
326             error << "Plugin " << plugin->getName() << " has already loaded!";
327             setLastError(error.str());
328             delete plugin; // Reject duplicate plugins
329             return NULL;
330         }
331     }
332     m_plugins.push_back(plugin);
333     return plugin;
334 }
335
336 CIMIPlugin*
337 CIMIPluginManager::createPlugin(std::string filename,
338                                 TPluginTypeEnum pluginType)
339 {
340     std::stringstream error;
341     clearLastError();
342
343     switch (pluginType) {
344     case CIMI_PLUGIN_PYTHON:
345         return new CIMIPythonPlugin(filename);
346     case CIMI_PLUGIN_UNKNOWN:
347     default:
348         error << "Cannot detect type for " << filename;
349         setLastError(error.str());
350         return NULL;
351     }
352 }
353
354 void
355 CIMIPluginManager::setLastError(std::string desc)
356 {
357     m_hasError = true;
358     m_lastError = desc;
359 }
360
361 void
362 CIMIPluginManager::clearLastError()
363 {
364     m_hasError = false;
365     m_lastError = "";
366 }
367
368 void
369 CIMIPluginManager::markWaitTime(int waitTime)
370 {
371     if (waitTime <= 0)
372         return;
373
374     if (m_waitTime == 0) {
375         m_waitTime = waitTime;
376     } else if (waitTime < m_waitTime) {
377         m_waitTime = waitTime;
378     }
379 }