1 /***************************************************************************
2  *   Copyright (C) 2011~2012 by CSSlayer                                   *
3  *   wengxt@gmail.com                                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.              *
19  ***************************************************************************/
20 
21 #include "config.h"
22 
23 #include <errno.h>
24 #include <iconv.h>
25 #include <unistd.h>
26 
27 #include <curl/curl.h>
28 #include <fcntl.h>
29 
30 #include <fcitx/fcitx.h>
31 #include <fcitx/module.h>
32 #include <fcitx/hook.h>
33 #include <fcitx-utils/log.h>
34 #include <fcitx/candidate.h>
35 #include <fcitx-config/xdg.h>
36 #include <fcitx/module/pinyin/fcitx-pinyin.h>
37 #include <fcitx/module/freedesktop-notify/fcitx-freedesktop-notify.h>
38 
39 #include "cloudpinyin.h"
40 #include "fetch.h"
41 #include "parse.h"
42 
43 DEFINE_GET_ADDON("fcitx-sunpinyin", SunPinyin)
44 DEFINE_GET_ADDON("fcitx-libpinyin", LibPinyin)
45 DEFINE_GET_ADDON("fcitx-sogoupinyin", SogouPinyin)
46 DEFINE_GET_AND_INVOKE_FUNC(SunPinyin, GetFullPinyin, 0)
47 DEFINE_GET_AND_INVOKE_FUNC(SunPinyin, AddWord, 1)
48 
49 // Maybe not the right name, but doesn't matter....
50 DEFINE_GET_AND_INVOKE_FUNC(LibPinyin, AddWord, 0)
51 DEFINE_GET_AND_INVOKE_FUNC(SogouPinyin, AddWord, 0)
52 
53 #define CHECK_VALID_IM (im &&                                 \
54                         strcmp(im->langCode, "zh_CN") == 0 && \
55                         (strcmp(im->uniqueName, "pinyin") == 0 || \
56                         strcmp(im->uniqueName, "pinyin-libpinyin") == 0 || \
57                         strcmp(im->uniqueName, "shuangpin-libpinyin") == 0 || \
58                         strcmp(im->uniqueName, "googlepinyin") == 0 || \
59                         strcmp(im->uniqueName, "sunpinyin") == 0 || \
60                         strcmp(im->uniqueName, "shuangpin") == 0 || \
61                         strcmp(im->uniqueName, "sogou-pinyin") == 0))
62 
63 #define CLOUDPINYIN_CHECK_PAGE_NUMBER 3
64 
65 typedef struct _CloudCandWord {
66     boolean filled;
67     uint64_t timestamp;
68 } CloudCandWord;
69 
70 typedef struct _CloudPinyinEngine {
71     const char* RequestKey;
72     const char* RequestPinyin;
73     void (*ParseKey)(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue);
74     char* (*ParsePinyin)(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue);
75     boolean supportSeparator;
76 } CloudPinyinEngine;
77 
78 static void* CloudPinyinCreate(FcitxInstance* instance);
79 static void CloudPinyinSetFD(void* arg);
80 static void CloudPinyinProcessEvent(void* arg);
81 static void CloudPinyinDestroy(void* arg);
82 static void CloudPinyinReloadConfig(void* arg);
83 static void CloudPinyinAddCandidateWord(void* arg);
84 static void CloudPinyinRequestKey(FcitxCloudPinyin* cloudpinyin);
85 static void CloudPinyinAddInputRequest(FcitxCloudPinyin* cloudpinyin, const char* strPinyin);
86 static void CloudPinyinHandleRequest(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue);
87 static size_t CloudPinyinWriteFunction(char *ptr, size_t size, size_t nmemb, void *userdata);
88 static CloudPinyinCache* CloudPinyinCacheLookup(FcitxCloudPinyin* cloudpinyin, const char* pinyin);
89 static CloudPinyinCache* CloudPinyinAddToCache(FcitxCloudPinyin* cloudpinyin, const char* pinyin, char* string);
90 static INPUT_RETURN_VALUE CloudPinyinGetCandWord(void* arg, FcitxCandidateWord* candWord);
91 static void _CloudPinyinAddCandidateWord(FcitxCloudPinyin* cloudpinyin, const char* pinyin);
92 static void CloudPinyinFillCandidateWord(FcitxCloudPinyin* cloudpinyin, const char* pinyin);
93 static boolean CloudPinyinConfigLoad(FcitxCloudPinyinConfig* fs);
94 static void CloudPinyinConfigSave(FcitxCloudPinyinConfig* fs);
95 static char *GetCurrentString(FcitxCloudPinyin* cloudpinyin,
96                               char **ascii_part);
97 static void CloudPinyinHookForNewRequest(void* arg);
98 static CURL* CloudPinyinGetFreeCurlHandle(FcitxCloudPinyin* cloudpinyin);
99 static void CloudPinyinReleaseCurlHandle(FcitxCloudPinyin* cloudpinyin,
100                                          CURL* curl);
101 static INPUT_RETURN_VALUE CloudPinyinToggle(void* arg);
102 
103 CloudPinyinEngine engine[] =
104 {
105 #if 0
106     {
107         "http://web.pinyin.sogou.com/web_ime/patch.php",
108         "http://web.pinyin.sogou.com/api/py?key=%s&query=%s",
109         SogouParseKey,
110         SogouParsePinyin
111     },
112     {
113         "http://ime.qq.com/fcgi-bin/getkey",
114         "http://ime.qq.com/fcgi-bin/getword?key=%s&q=%s",
115         QQParseKey,
116         QQParsePinyin
117     },
118 #endif
119     {
120         NULL,
121         "https://www.google.com/inputtools/request?ime=pinyin&text=%s",
122         NULL,
123         GoogleParsePinyin,
124         true
125     },
126     {
127         NULL,
128         "https://olime.baidu.com/py?py=%s&rn=0&pn=1&ol=1",
129         NULL,
130         BaiduParsePinyin,
131         true
132     }
133 };
134 
135 
136 CONFIG_DESC_DEFINE(GetCloudPinyinConfigDesc, "fcitx-cloudpinyin.desc")
137 
138 FCITX_DEFINE_PLUGIN(fcitx_cloudpinyin, module, FcitxModule) = {
139     .Create = CloudPinyinCreate,
140     .Destroy = CloudPinyinDestroy,
141     .SetFD = CloudPinyinSetFD,
142     .ProcessEvent = CloudPinyinProcessEvent,
143     .ReloadConfig = CloudPinyinReloadConfig
144 };
145 
146 static uint64_t
CloudGetTimeStamp()147 CloudGetTimeStamp()
148 {
149     struct timeval current_time;
150     gettimeofday(&current_time, NULL);
151     return (((uint64_t)current_time.tv_sec * 1000)
152             + (current_time.tv_usec / 1000));
153 }
154 
155 static void
CloudSetClientPreedit(FcitxCloudPinyin * cloudpinyin,const char * str)156 CloudSetClientPreedit(FcitxCloudPinyin *cloudpinyin, const char *str)
157 {
158     FcitxInputState *input = FcitxInstanceGetInputState(cloudpinyin->owner);
159     FcitxMessages *message = FcitxInputStateGetClientPreedit(input);
160     char *py;
161     char *string = GetCurrentString(cloudpinyin, &py);
162     FcitxMessagesSetMessageCount(message, 0);
163     if (py) {
164         *py = '\0';
165         FcitxMessagesAddMessageAtLast(message, MSG_INPUT, "%s%s", string, str);
166     } else {
167         FcitxMessagesAddMessageAtLast(message, MSG_INPUT, "%s", str);
168     }
169     fcitx_utils_free(string);
170     FcitxInstanceUpdateClientSideUI(
171         cloudpinyin->owner, FcitxInstanceGetCurrentIC(cloudpinyin->owner));
172 }
173 
CloudPinyinCreate(FcitxInstance * instance)174 void* CloudPinyinCreate(FcitxInstance* instance)
175 {
176     FcitxCloudPinyin *cloudpinyin = fcitx_utils_new(FcitxCloudPinyin);
177     bindtextdomain("fcitx-cloudpinyin", LOCALEDIR);
178     bind_textdomain_codeset("fcitx-cloudpinyin", "UTF-8");
179     cloudpinyin->owner = instance;
180     int pipe1[2];
181     int pipe2[2];
182 
183     if (!CloudPinyinConfigLoad(&cloudpinyin->config))
184     {
185         free(cloudpinyin);
186         return NULL;
187     }
188 
189     if (pipe(pipe1) < 0)
190     {
191         free(cloudpinyin);
192         return NULL;
193     }
194 
195     if (pipe(pipe2) < 0) {
196         close(pipe1[0]);
197         close(pipe1[1]);
198         free(cloudpinyin);
199         return NULL;
200     }
201 
202     cloudpinyin->pipeRecv = pipe1[0];
203     cloudpinyin->pipeNotify = pipe2[1];
204 
205     fcntl(pipe1[0], F_SETFL, O_NONBLOCK);
206     fcntl(pipe1[1], F_SETFL, O_NONBLOCK);
207     fcntl(pipe2[0], F_SETFL, O_NONBLOCK);
208     fcntl(pipe2[1], F_SETFL, O_NONBLOCK);
209 
210     cloudpinyin->pendingQueue = fcitx_utils_malloc0(sizeof(CurlQueue));
211     cloudpinyin->finishQueue = fcitx_utils_malloc0(sizeof(CurlQueue));
212     pthread_mutex_init(&cloudpinyin->pendingQueueLock, NULL);
213     pthread_mutex_init(&cloudpinyin->finishQueueLock, NULL);
214 
215     FcitxFetchThread* fetch = fcitx_utils_malloc0(sizeof(FcitxFetchThread));
216     cloudpinyin->fetch = fetch;
217     fetch->owner = cloudpinyin;
218     fetch->pipeRecv = pipe2[0];
219     fetch->pipeNotify = pipe1[1];
220     fetch->pendingQueueLock = &cloudpinyin->pendingQueueLock;
221     fetch->finishQueueLock = &cloudpinyin->finishQueueLock;
222     fetch->queue = fcitx_utils_malloc0(sizeof(CurlQueue));
223 
224     FcitxIMEventHook hook;
225     hook.arg = cloudpinyin;
226     hook.func = CloudPinyinAddCandidateWord;
227 
228     FcitxInstanceRegisterUpdateCandidateWordHook(instance, hook);
229 
230     hook.arg = cloudpinyin;
231     hook.func = CloudPinyinHookForNewRequest;
232 
233     FcitxInstanceRegisterResetInputHook(instance, hook);
234     FcitxInstanceRegisterInputFocusHook(instance, hook);
235     FcitxInstanceRegisterInputUnFocusHook(instance, hook);
236     FcitxInstanceRegisterTriggerOnHook(instance, hook);
237 
238     FcitxHotkeyHook hkhook;
239     hkhook.arg = cloudpinyin;
240     hkhook.hotkey = cloudpinyin->config.hkToggle.hotkey;
241     hkhook.hotkeyhandle = CloudPinyinToggle;
242 
243     FcitxInstanceRegisterHotkeyFilter(instance, hkhook);
244 
245     pthread_create(&cloudpinyin->pid, NULL, FetchThread, fetch);
246 
247     CloudPinyinRequestKey(cloudpinyin);
248 
249     return cloudpinyin;
250 }
251 
CloudPinyinGetFreeCurlHandle(FcitxCloudPinyin * cloudpinyin)252 CURL* CloudPinyinGetFreeCurlHandle(FcitxCloudPinyin* cloudpinyin)
253 {
254     int i = 0;
255     for (i = 0; i < MAX_HANDLE; i ++) {
256         if (!cloudpinyin->freeList[i].used) {
257             cloudpinyin->freeList[i].used = true;
258             if (cloudpinyin->freeList[i].curl == NULL) {
259                 cloudpinyin->freeList[i].curl = curl_easy_init();
260             }
261             return cloudpinyin->freeList[i].curl;
262         }
263     }
264     return NULL;
265 }
266 
CloudPinyinReleaseCurlHandle(FcitxCloudPinyin * cloudpinyin,CURL * curl)267 void CloudPinyinReleaseCurlHandle(FcitxCloudPinyin* cloudpinyin, CURL* curl)
268 {
269     if (curl == NULL)
270         return;
271     int i = 0;
272     for (i = 0; i < MAX_HANDLE; i ++) {
273         if (cloudpinyin->freeList[i].curl == curl) {
274             cloudpinyin->freeList[i].used = false;
275             return;
276         }
277     }
278     /* if handle is stalled, free it */
279     curl_easy_cleanup(curl);
280 }
281 
282 
CloudPinyinAddCandidateWord(void * arg)283 void CloudPinyinAddCandidateWord(void* arg)
284 {
285     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
286     FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
287     FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
288 
289     if (!cloudpinyin->initialized || !cloudpinyin->config.bEnabled)
290         return;
291 
292     /* check whether the current im is pinyin */
293     if (CHECK_VALID_IM)
294     {
295         /* there is something pending input */
296         if (FcitxInputStateGetRawInputBufferSize(input) >= cloudpinyin->config.iMinimumPinyinLength)
297         {
298             char* strToFree = NULL, *inputString;
299             strToFree = GetCurrentString(cloudpinyin, &inputString);
300 
301             if (inputString) {
302                 CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, inputString);
303                 FcitxLog(DEBUG, "%s", inputString);
304                 if (cacheEntry == NULL)
305                     CloudPinyinAddInputRequest(cloudpinyin, inputString);
306                 _CloudPinyinAddCandidateWord(cloudpinyin, inputString);
307             }
308             if (strToFree)
309                 free(strToFree);
310         }
311     }
312 
313     return;
314 }
315 
CloudPinyinRequestKey(FcitxCloudPinyin * cloudpinyin)316 void CloudPinyinRequestKey(FcitxCloudPinyin* cloudpinyin)
317 {
318     if (cloudpinyin->isrequestkey)
319         return;
320 
321     cloudpinyin->isrequestkey = true;
322     if (engine[cloudpinyin->config.source].RequestKey == NULL)
323     {
324         cloudpinyin->initialized = true;
325         cloudpinyin->key[0] = '\0';
326         cloudpinyin->isrequestkey = false;
327         return;
328     }
329 
330     CURL* curl = CloudPinyinGetFreeCurlHandle(cloudpinyin);
331     if (!curl)
332         return;
333     CurlQueue* queue = fcitx_utils_malloc0(sizeof(CurlQueue)), *head = cloudpinyin->pendingQueue;
334     queue->curl = curl;
335     queue->next = NULL;
336     queue->type = RequestKey;
337     queue->source = cloudpinyin->config.source;
338 
339     curl_easy_setopt(curl, CURLOPT_URL, engine[cloudpinyin->config.source].RequestKey);
340     curl_easy_setopt(curl, CURLOPT_WRITEDATA, queue);
341     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CloudPinyinWriteFunction);
342     curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10l);
343     curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1l);
344 
345     /* push into pending queue */
346     pthread_mutex_lock(&cloudpinyin->pendingQueueLock);
347     while (head->next != NULL)
348         head = head->next;
349     head->next = queue;
350     pthread_mutex_unlock(&cloudpinyin->pendingQueueLock);
351 
352     char c = 0;
353     write(cloudpinyin->pipeNotify, &c, sizeof(char));
354 }
355 
356 
357 
CloudPinyinSetFD(void * arg)358 void CloudPinyinSetFD(void* arg)
359 {
360     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
361     FcitxInstance* instance = cloudpinyin->owner;
362     int maxfd = cloudpinyin->pipeRecv;
363     FD_SET(maxfd, FcitxInstanceGetReadFDSet(instance));
364     if (maxfd > FcitxInstanceGetMaxFD(instance))
365         FcitxInstanceSetMaxFD(instance, maxfd);
366 }
367 
CloudPinyinProcessEvent(void * arg)368 void CloudPinyinProcessEvent(void* arg)
369 {
370     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
371     FcitxInstance* instance = cloudpinyin->owner;
372     if (!FD_ISSET(cloudpinyin->pipeRecv, FcitxInstanceGetReadFDSet(instance)))
373         return;
374 
375     char c;
376     while (read(cloudpinyin->pipeRecv, &c, sizeof(char)) > 0);
377     pthread_mutex_lock(&cloudpinyin->finishQueueLock);
378     CurlQueue* queue;
379     queue = cloudpinyin->finishQueue;
380     /* this queue header is empty, so the check condition is "next" not null */
381     while (queue->next != NULL)
382     {
383         /* remove pivot from queue, thus pivot need to be free'd in HandleRequest */
384         CurlQueue* pivot = queue->next;
385         queue->next = queue->next->next;
386         CloudPinyinHandleRequest(cloudpinyin, pivot);
387     }
388     pthread_mutex_unlock(&cloudpinyin->finishQueueLock);
389 }
390 
CloudPinyinDestroy(void * arg)391 void CloudPinyinDestroy(void* arg)
392 {
393     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
394     char c = 1;
395     write(cloudpinyin->pipeNotify, &c, sizeof(char));
396     pthread_join(cloudpinyin->pid, NULL);
397     pthread_mutex_destroy(&cloudpinyin->pendingQueueLock);
398     pthread_mutex_destroy(&cloudpinyin->finishQueueLock);
399     while (cloudpinyin->cache)
400     {
401         CloudPinyinCache* head = cloudpinyin->cache;
402         HASH_DEL(cloudpinyin->cache, cloudpinyin->cache);
403         free(head->pinyin);
404         free(head->str);
405         free(head);
406     }
407 
408     close(cloudpinyin->pipeRecv);
409     close(cloudpinyin->pipeNotify);
410 
411     close(cloudpinyin->fetch->pipeRecv);
412     close(cloudpinyin->fetch->pipeNotify);
413     int i = 0;
414     for (i = 0; i < MAX_HANDLE; i ++) {
415         if (cloudpinyin->freeList[i].curl) {
416             curl_easy_cleanup(cloudpinyin->freeList[i].curl);
417         }
418     }
419 
420     curl_multi_cleanup(cloudpinyin->fetch->curlm);
421 #define _FREE_QUEUE(NAME) \
422     while(NAME) { \
423         CurlQueue* queue = NAME; \
424         NAME = NAME->next; \
425         fcitx_utils_free(queue->str); \
426         fcitx_utils_free(queue->pinyin); \
427         free(queue); \
428     }
429     _FREE_QUEUE(cloudpinyin->pendingQueue)
430     _FREE_QUEUE(cloudpinyin->finishQueue)
431     _FREE_QUEUE(cloudpinyin->fetch->queue)
432     FcitxConfigFree(&cloudpinyin->config.config);
433     free(cloudpinyin->fetch);
434     free(cloudpinyin);
435 }
436 
CloudPinyinReloadConfig(void * arg)437 void CloudPinyinReloadConfig(void* arg)
438 {
439     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
440     CloudPinyinSource previousSource = cloudpinyin->config.source;
441     CloudPinyinConfigLoad(&cloudpinyin->config);
442     if (previousSource != cloudpinyin->config.source)
443     {
444         cloudpinyin->initialized = false;
445         cloudpinyin->key[0] = '\0';
446     }
447 }
448 
CloudPinyinAddInputRequest(FcitxCloudPinyin * cloudpinyin,const char * strPinyin)449 void CloudPinyinAddInputRequest(FcitxCloudPinyin* cloudpinyin, const char* strPinyin)
450 {
451     CURL* curl = CloudPinyinGetFreeCurlHandle(cloudpinyin);
452     if (!curl)
453         return;
454     CurlQueue* queue = fcitx_utils_malloc0(sizeof(CurlQueue)), *head = cloudpinyin->pendingQueue;
455     queue->curl = curl;
456     queue->next = NULL;
457     queue->type = RequestPinyin;
458     queue->pinyin = strdup(strPinyin);
459     queue->source = cloudpinyin->config.source;
460     char* urlstring = curl_escape(strPinyin, strlen(strPinyin));
461     char *url = NULL;
462     if (engine[cloudpinyin->config.source].RequestKey)
463         asprintf(&url, engine[cloudpinyin->config.source].RequestPinyin, cloudpinyin->key, urlstring);
464     else
465         asprintf(&url, engine[cloudpinyin->config.source].RequestPinyin, urlstring);
466     curl_free(urlstring);
467 
468     curl_easy_setopt(curl, CURLOPT_URL, url);
469     curl_easy_setopt(curl, CURLOPT_WRITEDATA, queue);
470     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CloudPinyinWriteFunction);
471     curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10l);
472     curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1l);
473 
474     free(url);
475 
476     /* push into pending queue */
477     pthread_mutex_lock(&cloudpinyin->pendingQueueLock);
478     while (head->next != NULL)
479         head = head->next;
480     head->next = queue;
481     pthread_mutex_unlock(&cloudpinyin->pendingQueueLock);
482 
483     char c = 0;
484     write(cloudpinyin->pipeNotify, &c, sizeof(char));
485 }
486 
CloudPinyinHandleRequest(FcitxCloudPinyin * cloudpinyin,CurlQueue * queue)487 void CloudPinyinHandleRequest(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
488 {
489     if (queue->type == RequestKey)
490     {
491         cloudpinyin->isrequestkey = false;
492         if (queue->source != cloudpinyin->config.source)
493             return;
494 
495         if (queue->http_code == 200)
496         {
497             if (engine[cloudpinyin->config.source].ParseKey)
498                 engine[cloudpinyin->config.source].ParseKey(cloudpinyin, queue);
499         }
500     }
501     else if (queue->type == RequestPinyin)
502     {
503         if (queue->http_code == 200 && cloudpinyin->config.source == queue->source)
504         {
505             char *realstring = engine[cloudpinyin->config.source].ParsePinyin(cloudpinyin, queue);
506             if (realstring)
507             {
508                 CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, queue->pinyin);
509                 if (cacheEntry == NULL)
510                     cacheEntry = CloudPinyinAddToCache(cloudpinyin, queue->pinyin, realstring);
511 
512                 FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
513 
514                 char* strToFree = NULL, *inputString;
515                 strToFree = GetCurrentString(cloudpinyin, &inputString);
516 
517                 if (inputString) {
518                     FcitxLog(DEBUG, "fill: %s %s", inputString, queue->pinyin);
519                     if (strcmp(inputString, queue->pinyin) == 0)
520                     {
521                         if (CHECK_VALID_IM)
522                         {
523                             CloudPinyinFillCandidateWord(cloudpinyin, inputString);
524                         }
525                     }
526                 }
527                 if (strToFree)
528                     free(strToFree);
529                 free(realstring);
530             }
531         }
532 
533         if (queue->http_code != 200)
534         {
535             cloudpinyin->errorcount ++;
536             if (cloudpinyin->errorcount > MAX_ERROR)
537             {
538                 cloudpinyin->initialized = false;
539                 cloudpinyin->key[0] = '\0';
540                 cloudpinyin->errorcount = 0;
541             }
542         }
543     }
544     CloudPinyinReleaseCurlHandle(cloudpinyin, queue->curl);
545     fcitx_utils_free(queue->str);
546     fcitx_utils_free(queue->pinyin);
547     free(queue);
548 }
549 
CloudPinyinWriteFunction(char * ptr,size_t size,size_t nmemb,void * userdata)550 size_t CloudPinyinWriteFunction(char *ptr, size_t size, size_t nmemb, void *userdata)
551 {
552     CurlQueue* queue = (CurlQueue*) userdata;
553 
554     size_t realsize = size * nmemb;
555     /*
556      * We know that it isn't possible to overflow during multiplication if
557      * neither operand uses any of the most significant half of the bits in
558      * a size_t.
559      */
560 
561     if ((unsigned long long)((nmemb | size) &
562                              ((unsigned long long)SIZE_MAX << (sizeof(size_t) << 2))) &&
563             (realsize / size != nmemb))
564         return 0;
565 
566     if (SIZE_MAX - queue->size - 1 < realsize)
567         realsize = SIZE_MAX - queue->size - 1;
568 
569     if (queue->str != NULL)
570         queue->str = realloc(queue->str, queue->size + realsize + 1);
571     else
572         queue->str = fcitx_utils_malloc0(realsize + 1);
573 
574     if (queue->str != NULL) {
575         memcpy(&(queue->str[queue->size]), ptr, realsize);
576         queue->size += realsize;
577         queue->str[queue->size] = '\0';
578     }
579     return realsize;
580 }
581 
CloudPinyinCacheLookup(FcitxCloudPinyin * cloudpinyin,const char * pinyin)582 CloudPinyinCache* CloudPinyinCacheLookup(FcitxCloudPinyin* cloudpinyin, const char* pinyin)
583 {
584     CloudPinyinCache* cacheEntry = NULL;
585     HASH_FIND_STR(cloudpinyin->cache, pinyin, cacheEntry);
586     return cacheEntry;
587 }
588 
CloudPinyinAddToCache(FcitxCloudPinyin * cloudpinyin,const char * pinyin,char * string)589 CloudPinyinCache* CloudPinyinAddToCache(FcitxCloudPinyin* cloudpinyin, const char* pinyin, char* string)
590 {
591     CloudPinyinCache* cacheEntry = fcitx_utils_malloc0(sizeof(CloudPinyinCache));
592     cacheEntry->pinyin = strdup(pinyin);
593     cacheEntry->str = strdup(string);
594     HASH_ADD_KEYPTR(hh, cloudpinyin->cache, cacheEntry->pinyin, strlen(cacheEntry->pinyin), cacheEntry);
595 
596     /* if there is too much cached, remove the first one, though LRU might be a better algorithm */
597     if (HASH_COUNT(cloudpinyin->cache) > MAX_CACHE_ENTRY)
598     {
599         CloudPinyinCache* head = cloudpinyin->cache;
600         HASH_DEL(cloudpinyin->cache, cloudpinyin->cache);
601         free(head->pinyin);
602         free(head->str);
603         free(head);
604     }
605     return cacheEntry;
606 }
607 
CloudPinyinToggle(void * arg)608 INPUT_RETURN_VALUE CloudPinyinToggle(void* arg)
609 {
610     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
611     FcitxInstance* instance = cloudpinyin->owner;
612     FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
613 
614     if (CHECK_VALID_IM) {
615         cloudpinyin->config.bEnabled = !cloudpinyin->config.bEnabled;
616 
617         FcitxFreeDesktopNotifyShowAddonTip(
618             instance, "fcitx-cloudpinyin-toggle",
619             "fcitx",
620             _("Cloud Pinyin"),
621             cloudpinyin->config.bEnabled ? _("Cloud Pinyin is Enabled.") :
622                                     _("Cloud Pinyin is Disabled."));
623         CloudPinyinConfigSave(&cloudpinyin->config);
624         // TODO: add a notification here
625 
626         return IRV_DO_NOTHING;
627     }
628     return IRV_TO_PROCESS;
629 }
630 
_CloudPinyinAddCandidateWord(FcitxCloudPinyin * cloudpinyin,const char * pinyin)631 void _CloudPinyinAddCandidateWord(FcitxCloudPinyin* cloudpinyin, const char* pinyin)
632 {
633     CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, pinyin);
634     FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
635     FcitxCandidateWordList* candList = FcitxInputStateGetCandidateList(input);
636 
637     int order = (cloudpinyin->config.iCandidateOrder <= 2) ?
638         1 : (cloudpinyin->config.iCandidateOrder - 1);
639 
640     if (cacheEntry) {
641         FcitxCandidateWord* cand;
642         /* only check the first three page */
643         int pagesize = FcitxCandidateWordGetPageSize(candList);
644         int size = pagesize * CLOUDPINYIN_CHECK_PAGE_NUMBER;
645         int i;
646         if (cloudpinyin->config.iCandidateOrder <= 1) {
647             order = 0;
648         }
649         for (i = 0;i < size &&
650                  (cand = FcitxCandidateWordGetByTotalIndex(candList, i));i++) {
651             if (strcmp(cand->strWord, cacheEntry->str) == 0) {
652                 if (i > order && i >= pagesize) {
653                     FcitxCandidateWordMoveByWord(candList, cand, order);
654                     if (order == 0) {
655                         CloudSetClientPreedit(cloudpinyin, cacheEntry->str);
656                     }
657                 }
658                 return;
659             }
660         }
661         if (order == 0) {
662             CloudSetClientPreedit(cloudpinyin, cacheEntry->str);
663         }
664     }
665 
666     FcitxCandidateWord candWord;
667     CloudCandWord* cloudCand = fcitx_utils_malloc0(sizeof(CloudCandWord));
668     if (cacheEntry) {
669         cloudCand->filled = true;
670         cloudCand->timestamp = 0;
671         candWord.strWord = strdup(cacheEntry->str);
672     } else {
673         cloudCand->filled = false;
674         cloudCand->timestamp = CloudGetTimeStamp();
675         candWord.strWord = strdup("..");
676     }
677 
678     candWord.callback = CloudPinyinGetCandWord;
679     candWord.owner = cloudpinyin;
680     candWord.priv = cloudCand;
681     candWord.wordType = MSG_TIPS;
682     if (cloudpinyin->config.bDontShowSource)
683         candWord.strExtra = NULL;
684     else {
685         candWord.strExtra = strdup(_(" (via cloud)"));
686         candWord.extraType = MSG_TIPS;
687     }
688 
689     FcitxCandidateWordInsert(candList, &candWord, order);
690 }
691 
692 #define LOADING_TIME_QUICK_THRESHOLD 300
693 #define DUP_PLACE_HOLDER "\xe2\x98\xba"
694 
CloudPinyinFillCandidateWord(FcitxCloudPinyin * cloudpinyin,const char * pinyin)695 void CloudPinyinFillCandidateWord(FcitxCloudPinyin* cloudpinyin,
696                                   const char* pinyin)
697 {
698     CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, pinyin);
699     FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
700     FcitxCandidateWordList* candList = FcitxInputStateGetCandidateList(input);
701     if (cacheEntry) {
702         int cloudidx;
703         FcitxCandidateWord *candWord;
704         for (cloudidx = 0;
705              (candWord = FcitxCandidateWordGetByTotalIndex(candList, cloudidx));
706              cloudidx++) {
707             if (candWord->owner == cloudpinyin)
708                 break;
709         }
710 
711         if (candWord == NULL)
712             return;
713 
714         CloudCandWord* cloudCand = candWord->priv;
715         if (cloudCand->filled)
716             return;
717 
718         FcitxCandidateWord *cand;
719         int i;
720         int pagesize = FcitxCandidateWordGetPageSize(candList);
721         int size = pagesize * CLOUDPINYIN_CHECK_PAGE_NUMBER;
722         for (i = 0;i < size &&
723                  (cand = FcitxCandidateWordGetByTotalIndex(candList, i));i++) {
724             if (strcmp(cand->strWord, cacheEntry->str) == 0) {
725                 uint64_t ts = cloudCand->timestamp;
726                 uint64_t curTs = CloudGetTimeStamp();
727                 FcitxCandidateWordRemove(candList, candWord);
728                 /* if cloud word is not on the first page.. impossible */
729                 if (cloudidx < pagesize) {
730                     /* if the duplication before cloud word */
731                     if (i < cloudidx) {
732                         if (curTs - ts
733                             > LOADING_TIME_QUICK_THRESHOLD) {
734                             FcitxCandidateWordInsertPlaceHolder(candList, cloudidx);
735                             FcitxCandidateWord* placeHolder = FcitxCandidateWordGetByTotalIndex(candList, cloudidx);
736                             if (placeHolder && placeHolder->strWord == NULL)
737                                 placeHolder->strWord = strdup(DUP_PLACE_HOLDER);
738                         }
739                     } else {
740                         if (i >= pagesize) {
741                             FcitxCandidateWordMove(candList, i - 1, cloudidx);
742                         } else {
743                             if (curTs - ts > LOADING_TIME_QUICK_THRESHOLD) {
744                                 FcitxCandidateWordInsertPlaceHolder(candList, cloudidx);
745                                 FcitxCandidateWord* placeHolder = FcitxCandidateWordGetByTotalIndex(candList, cloudidx);
746                                 if (placeHolder && placeHolder->strWord == NULL)
747                                     placeHolder->strWord = strdup(DUP_PLACE_HOLDER);
748                             }
749                         }
750                     }
751                 }
752                 FcitxUIUpdateInputWindow(cloudpinyin->owner);
753                 candWord = NULL;
754                 break;
755             }
756         }
757 
758         if (candWord) {
759             if (cloudCand->filled == false) {
760                 cloudCand->filled = true;
761                 free(candWord->strWord);
762                 candWord->strWord = strdup(cacheEntry->str);
763                 if (cloudpinyin->config.iCandidateOrder <= 1 &&
764                     (CloudGetTimeStamp() - cloudCand->timestamp
765                      <= LOADING_TIME_QUICK_THRESHOLD)) {
766                     FcitxCandidateWordMoveByWord(candList, candWord, 0);
767                     CloudSetClientPreedit(cloudpinyin, cacheEntry->str);
768                 }
769                 FcitxUIUpdateInputWindow(cloudpinyin->owner);
770             }
771         }
772     }
773 }
774 
CloudPinyinGetCandWord(void * arg,FcitxCandidateWord * candWord)775 INPUT_RETURN_VALUE CloudPinyinGetCandWord(void* arg, FcitxCandidateWord* candWord)
776 {
777     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
778     CloudCandWord* cloudCand = candWord->priv;
779     FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
780     if (cloudCand->filled)
781     {
782         char *py;
783         char *string = GetCurrentString(cloudpinyin, &py);
784         if (py) {
785             *py = 0;
786 
787             snprintf(FcitxInputStateGetOutputString(input),
788                      MAX_USER_INPUT, "%s%s", string, candWord->strWord);
789 
790             FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
791             if (im) {
792                 char *output_string = FcitxInputStateGetOutputString(input);
793                 FCITX_DEF_MODULE_ARGS(args, output_string);
794                 if (strcmp(im->uniqueName, "sunpinyin") == 0) {
795                     FcitxSunPinyinInvokeAddWord(cloudpinyin->owner, args);
796                 } else if (strcmp(im->uniqueName, "shuangpin") == 0 ||
797                            strcmp(im->uniqueName, "pinyin") == 0) {
798                     FcitxPinyinInvokeAddUserPhrase(cloudpinyin->owner, args);
799                 } else if (strcmp(im->uniqueName, "pinyin-libpinyin") == 0 ||
800                            strcmp(im->uniqueName, "shuangpin-libpinyin") == 0) {
801                     FcitxLibPinyinInvokeAddWord(cloudpinyin->owner, args);
802                 }
803                 else if (strcmp(im->uniqueName, "sogou-pinyin") == 0)
804                 {
805                     FcitxSogouPinyinInvokeAddWord(cloudpinyin->owner, args);
806                 }
807             }
808         }
809         if (string)
810             free(string);
811         return IRV_COMMIT_STRING;
812     } else {
813         return IRV_DO_NOTHING;
814     }
815 }
816 
817 
818 /**
819  * @brief Load the config file for fcitx-cloudpinyin
820  *
821  * @param Bool is reload or not
822  **/
CloudPinyinConfigLoad(FcitxCloudPinyinConfig * fs)823 boolean CloudPinyinConfigLoad(FcitxCloudPinyinConfig* fs)
824 {
825     FcitxConfigFileDesc *configDesc = GetCloudPinyinConfigDesc();
826     if (configDesc == NULL)
827         return false;
828 
829     FILE *fp = FcitxXDGGetFileUserWithPrefix("conf", "fcitx-cloudpinyin.config", "r", NULL);
830 
831     if (!fp)
832     {
833         if (errno == ENOENT)
834             CloudPinyinConfigSave(fs);
835     }
836     FcitxConfigFile *cfile = FcitxConfigParseConfigFileFp(fp, configDesc);
837     FcitxCloudPinyinConfigConfigBind(fs, cfile, configDesc);
838     FcitxConfigBindSync(&fs->config);
839 
840     if (fp)
841         fclose(fp);
842 
843     return true;
844 }
845 
846 /**
847  * @brief Save the config
848  *
849  * @return void
850  **/
CloudPinyinConfigSave(FcitxCloudPinyinConfig * fs)851 void CloudPinyinConfigSave(FcitxCloudPinyinConfig* fs)
852 {
853     FcitxConfigFileDesc *configDesc = GetCloudPinyinConfigDesc();
854     FILE *fp = FcitxXDGGetFileUserWithPrefix("conf", "fcitx-cloudpinyin.config", "w", NULL);
855     FcitxConfigSaveConfigFileFp(fp, &fs->config, configDesc);
856     if (fp)
857         fclose(fp);
858 }
859 
GetCurrentString(FcitxCloudPinyin * cloudpinyin,char ** ascii_part)860 char *GetCurrentString(FcitxCloudPinyin* cloudpinyin, char **ascii_part)
861 {
862     FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
863     if (!im) {
864         *ascii_part = NULL;
865         return NULL;
866     }
867     FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
868     char* string = FcitxUIMessagesToCString(FcitxInputStateGetPreedit(input));
869     char p[MAX_USER_INPUT + 1], *pinyin, *lastpos;
870     pinyin = fcitx_utils_get_ascii_part(string);
871     lastpos = pinyin;
872     boolean endflag;
873     int hzlength = pinyin - string;
874     size_t plength = hzlength;
875     strncpy(p, string, hzlength);
876     p[hzlength] = '\0';
877     // lastpos points to the start of a pinyin
878     // pinyin points to the end of current pinyin
879     // for example
880     // xi'an
881     // | |
882     // l p
883     // and we check the separator by supportSeparator for each engine.
884     // shuangpin-libpinyin returns full pinyin in preedit, so we also treat space as separator.
885     do {
886         endflag = (*pinyin != '\0');
887         if (*pinyin == ' ' || *pinyin == '\'' || *pinyin == '\0') {
888             boolean isSeparator = false;
889 
890             // skip all continous separator
891             while (*pinyin == ' ' || *pinyin == '\'') {
892                 isSeparator = isSeparator || (*pinyin) == '\'' || (strcmp(im->uniqueName, "shuangpin-libpinyin") == 0 && (*pinyin) == ' ');
893                 *pinyin = 0;
894                 pinyin++;
895             }
896 
897             if (*lastpos != '\0') {
898                 char* result = NULL;
899                 boolean isshuangpin = false;
900                 if (strcmp(im->uniqueName, "sunpinyin") == 0) {
901                     FCITX_DEF_MODULE_ARGS(args, lastpos, &isshuangpin);
902                     result = FcitxSunPinyinInvokeGetFullPinyin(cloudpinyin->owner, args);
903                 } else if (strcmp(im->uniqueName, "shuangpin") == 0) {
904                     isshuangpin = true;
905                     result = FcitxPinyinSP2QP(cloudpinyin->owner, lastpos);
906                 }
907                 if (isshuangpin) {
908                     if (result) {
909                         if (plength + strlen(result) + (engine[cloudpinyin->config.source].supportSeparator ? 1 : 0) < MAX_USER_INPUT) {
910                             strcat(p + plength, result);
911                             plength += strlen(result);
912                             if (engine[cloudpinyin->config.source].supportSeparator) {
913                                 strcat(p + plength, "'");
914                                 plength += 1;
915                             }
916                             free(result);
917                         } else {
918                             p[hzlength] = '\0';
919                             break;
920                         }
921                     }
922                 } else {
923 #define PINYIN_USE_SEPARATOR_CASE (isSeparator && engine[cloudpinyin->config.source].supportSeparator)
924                     if (plength + strlen(lastpos) + (PINYIN_USE_SEPARATOR_CASE ? 1 : 0) < MAX_USER_INPUT) {
925                         strcat(p + plength, lastpos);
926                         plength += strlen(lastpos);
927                         if (PINYIN_USE_SEPARATOR_CASE) {
928                             strcat(p + plength, "'");
929                             plength += 1;
930                         }
931                     } else {
932                         p[hzlength] = '\0';
933                         break;
934                     }
935                 }
936 
937                 isSeparator = false;
938             }
939             lastpos = pinyin;
940         } else {
941             pinyin ++;
942         }
943     } while(endflag);
944     free(string);
945     /* no pinyin append, return NULL for off it */
946     if (p[hzlength] == '\0') {
947         *ascii_part = NULL;
948         return NULL;
949     } else {
950         if (plength >= 1 && p[plength - 1] == '\'') {
951             p[plength - 1] = '\0';
952         }
953         char *res = strdup(p);
954         *ascii_part = res + hzlength;
955         return res;
956     }
957 }
958 
CloudPinyinHookForNewRequest(void * arg)959 void CloudPinyinHookForNewRequest(void* arg)
960 {
961     FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
962     if (!cloudpinyin->initialized && !cloudpinyin->isrequestkey) {
963         CloudPinyinRequestKey(cloudpinyin);
964     }
965 }
966 
967 // kate: indent-mode cstyle; space-indent on; indent-width 0;
968