Update the project to the newest version.
[framework/appfw/shortcut.git] / lib / src / main.c
1 /*
2  * Copyright 2012  Samsung Electronics Co., Ltd
3  *
4  * Licensed under the Flora License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.tizenopensource.org/license
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdlib.h>
18 #include <errno.h>
19 #include <unistd.h>
20 #include <fcntl.h>
21 #include <string.h>
22 #include <sys/socket.h>
23 #include <sys/ioctl.h>
24 #include <libgen.h>
25
26 #include <dlog.h>
27 #include <glib.h>
28 #include <db-util.h>
29 #include <vconf.h>
30 #include <vconf-keys.h>
31
32 #include <packet.h>
33 #include <com-core.h>
34 #include <com-core_packet.h>
35
36 #include "shortcut.h"
37
38 #if !defined(FLOG)
39 #define DbgPrint(format, arg...)        LOGD("[\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg)
40 #define ErrPrint(format, arg...)        LOGE("[\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg)
41 #else
42 extern FILE *__file_log_fp;
43 #define DbgPrint(format, arg...) do { fprintf(__file_log_fp, "[LOG] [\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg); fflush(__file_log_fp); } while (0)
44
45 #define ErrPrint(format, arg...) do { fprintf(__file_log_fp, "[ERR] [\e[32m%s/%s\e[0m:%d] " format, basename(__FILE__), __func__, __LINE__, ##arg); fflush(__file_log_fp); } while (0)
46 #endif
47
48 #define EAPI __attribute__((visibility("default")))
49
50 int errno;
51
52
53
54 static struct info {
55         const char *dbfile;
56         sqlite3 *handle;
57         int server_fd;
58         int client_fd;
59         const char *socket_file;
60         struct {
61                 int (*request_cb)(const char *appid, const char *name, int type, const char *content, const char *icon, pid_t pid, double period, void *data);
62                 void *data;
63         } server_cb;
64         int initialized;
65         int db_opened;
66 } s_info = {
67         .server_fd = -1,
68         .client_fd = -1,
69         .socket_file = "/tmp/.shortcut",
70         .dbfile = "/opt/dbspace/.shortcut_service.db",
71         .handle = NULL,
72         .initialized = 0,
73         .db_opened = 0,
74 };
75
76
77
78 static struct packet *add_shortcut_handler(pid_t pid, int handle, const struct packet *packet)
79 {
80         const char *appid;
81         const char *name;
82         int type;
83         const char *content;
84         const char *icon;
85         int ret;
86
87         if (!packet)
88                 return NULL;
89
90         if (packet_get(packet, "ssiss", &appid, &name, &type, &content, &icon) != 5) {
91                 ErrPrint("Invalid packet\n");
92                 return NULL;
93         }
94
95         DbgPrint("appid[%s], name[%s], type[0x%x], content[%s], icon[%s]\n", appid, name, type, content, icon);
96
97         if (s_info.server_cb.request_cb)
98                 ret = s_info.server_cb.request_cb(appid, name, type, content, icon, pid, -1.0f, s_info.server_cb.data);
99         else
100                 ret = 0;
101
102         return packet_create_reply(packet, "i", ret);
103 }
104
105
106
107 static struct packet *add_livebox_handler(pid_t pid, int handle, const struct packet *packet)
108 {
109         const char *appid;
110         const char *name;
111         int type;
112         const char *content;
113         const char *icon;
114         double period;
115         int ret;
116
117         if (!packet)
118                 return NULL;
119
120         if (packet_get(packet, "ssissd", &appid, &name, &type, &content, &icon, &period) != 6) {
121                 ErrPrint("Invalid packet\n");
122                 return NULL;
123         }
124
125         DbgPrint("appid[%s], name[%s], type[0x%x], content[%s], icon[%s], period[%lf]\n", appid, name, type, content, icon, period);
126
127         if (s_info.server_cb.request_cb)
128                 ret = s_info.server_cb.request_cb(appid, name, type, content, icon, pid, period, s_info.server_cb.data);
129         else
130                 ret = 0;
131
132         return packet_create_reply(packet, "i", ret);
133 }
134
135
136
137 EAPI int shortcut_set_request_cb(request_cb_t request_cb, void *data)
138 {
139         s_info.server_cb.request_cb = request_cb;
140         s_info.server_cb.data = data;
141
142         if (s_info.server_fd < 0) {
143                 static struct method service_table[] = {
144                         {
145                                 .cmd = "add_shortcut",
146                                 .handler = add_shortcut_handler,
147                         },
148                         {
149                                 .cmd = "add_livebox",
150                                 .handler = add_livebox_handler,
151                         },
152                         {
153                                 .cmd = NULL,
154                                 .handler = NULL,
155                         },
156                 };
157
158                 unlink(s_info.socket_file);     /* Delete previous socket file for creating a new server socket */
159                 s_info.server_fd = com_core_packet_server_init(s_info.socket_file, service_table);
160         }
161
162         DbgPrint("Server FD: %d\n", s_info.server_fd);
163
164         return s_info.server_fd > 0 ? 0 : s_info.server_fd;
165 }
166
167
168
169 struct result_cb_item {
170         result_cb_t result_cb;
171         void *data;
172 };
173
174
175
176 static int shortcut_send_cb(pid_t pid, int handle, const struct packet *packet, void *data)
177 {
178         struct result_cb_item *item = data;
179         int ret;
180
181         if (!packet) {
182                 ErrPrint("Packet is not valid\n");
183                 ret = -EFAULT;
184         } else if (packet_get(packet, "i", &ret) != 1) {
185                 ErrPrint("Packet is not valid\n");
186                 ret = -EINVAL;
187         }
188
189         if (item->result_cb)
190                 ret = item->result_cb(ret, pid, item->data);
191         else
192                 ret = 0;
193         free(item);
194         return ret;
195 }
196
197
198
199 static int livebox_send_cb(pid_t pid, int handle, const struct packet *packet, void *data)
200 {
201         struct result_cb_item *item = data;
202         int ret;
203
204         if (!packet) {
205                 ErrPrint("Packet is not valid\n");
206                 ret = -EFAULT;
207         } else if (packet_get(packet, "i", &ret) != 1) {
208                 ErrPrint("Packet is not valid\n");
209                 ret = -EINVAL;
210         }
211
212         if (item->result_cb)
213                 ret = item->result_cb(ret, pid, item->data);
214         else
215                 ret = 0;
216         free(item);
217         return ret;
218 }
219
220
221
222 static int disconnected_cb(int handle, void *data)
223 {
224         if (s_info.client_fd != handle) {
225                 /*!< This is not my favor */
226                 return 0;
227         }
228
229         s_info.client_fd = -EINVAL;
230         return 0;
231 }
232
233
234
235 EAPI int add_to_home_shortcut(const char *appid, const char *name, int type, const char *content, const char *icon, result_cb_t result_cb, void *data)
236 {
237         struct packet *packet;
238         struct result_cb_item *item;
239         int ret;
240
241         if (!s_info.initialized) {
242                 s_info.initialized = 1;
243                 com_core_add_event_callback(CONNECTOR_DISCONNECTED, disconnected_cb, NULL);
244         }
245
246         if (s_info.client_fd < 0) {
247                 static struct method service_table[] = {
248                         {
249                                 .cmd = NULL,
250                                 .handler = NULL,
251                         },
252                 };
253
254                 s_info.client_fd = com_core_packet_client_init(s_info.socket_file, 0, service_table);
255                 if (s_info.client_fd < 0) {
256                         ErrPrint("Failed to make connection\n");
257                         return s_info.client_fd;
258                 }
259         }
260
261         item = malloc(sizeof(*item));
262         if (!item) {
263                 ErrPrint("Heap: %s\n", strerror(errno));
264                 return -ENOMEM;
265         }
266
267         item->result_cb = result_cb;
268         item->data = data;
269
270         if (!appid)
271                 appid = "";
272
273         if (!name)
274                 name = "";
275
276         if (!content)
277                 content = "";
278
279         if (!icon)
280                 icon = "";
281
282         packet = packet_create("add_shortcut", "ssiss", appid, name, type, content, icon);
283         if (!packet) {
284                 ErrPrint("Failed to build a packet\n");
285                 free(item);
286                 return -EFAULT;
287         }
288
289         ret = com_core_packet_async_send(s_info.client_fd, packet, 0.0f, shortcut_send_cb, item);
290         if (ret < 0) {
291                 packet_destroy(packet);
292                 free(item);
293                 com_core_packet_client_fini(s_info.client_fd);
294                 s_info.client_fd = -1;
295         }
296
297         return ret;
298 }
299
300
301
302 EAPI int add_to_home_livebox(const char *appid, const char *name, int type, const char *content, const char *icon, double period, result_cb_t result_cb, void *data)
303 {
304         struct packet *packet;
305         struct result_cb_item *item;
306         int ret;
307
308         if (!s_info.initialized) {
309                 s_info.initialized = 1;
310                 com_core_add_event_callback(CONNECTOR_DISCONNECTED, disconnected_cb, NULL);
311         }
312
313         if (s_info.client_fd < 0) {
314                 static struct method service_table[] = {
315                         {
316                                 .cmd = NULL,
317                                 .handler = NULL,
318                         },
319                 };
320
321                 s_info.client_fd = com_core_packet_client_init(s_info.socket_file, 0, service_table);
322                 if (s_info.client_fd < 0)
323                         return s_info.client_fd;
324         }
325
326         item = malloc(sizeof(*item));
327         if (!item) {
328                 ErrPrint("Heap: %s\n", strerror(errno));
329                 return -ENOMEM;
330         }
331
332         item->result_cb = result_cb;
333         item->data = data;
334
335         packet = packet_create("add_livebox", "ssissd", appid, name, type, content, icon, period);
336         if (!packet) {
337                 ErrPrint("Failed to build a packet\n");
338                 free(item);
339                 return -EFAULT;
340         }
341
342         ret = com_core_packet_async_send(s_info.client_fd, packet, 0.0f, livebox_send_cb, item);
343         if (ret < 0) {
344                 packet_destroy(packet);
345                 free(item);
346                 com_core_packet_client_fini(s_info.client_fd);
347                 s_info.client_fd = -1;
348         }
349
350         return ret;
351 }
352
353
354 EAPI int shortcut_add_to_home_with_period(const char *appid, const char *name, int type, const char *content, const char *icon, double period, result_cb_t result_cb, void *data)
355 {
356         return add_to_home_livebox(appid, name, type, content, icon, period, result_cb, data);
357 }
358
359 EAPI int shortcut_add_to_home(const char *appid, const char *name, int type, const char *content, const char *icon, result_cb_t result_cb, void *data)
360 {
361         return add_to_home_shortcut(appid, name, type, content, icon, result_cb, data);
362 }
363
364 static inline int open_db(void)
365 {
366         int ret;
367
368         ret = db_util_open(s_info.dbfile, &s_info.handle, DB_UTIL_REGISTER_HOOK_METHOD);
369         if (ret != SQLITE_OK) {
370                 DbgPrint("Failed to open a %s\n", s_info.dbfile);
371                 return -EIO;
372         }
373
374         return 0;
375 }
376
377
378
379 /*!
380  * \note this function will returns allocated(heap) string
381  */
382 static inline char *get_i18n_name(const char *lang, int id)
383 {
384         sqlite3_stmt *stmt;
385         static const char *query = "SELECT name FROM shortcut_name WHERE id = ? AND lang = ? COLLATE NOCASE";
386         const unsigned char *name;
387         char *ret;
388         int status;
389
390         status = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL);
391         if (status != SQLITE_OK) {
392                 ErrPrint("Failed to prepare stmt: %s\n", sqlite3_errmsg(s_info.handle));
393                 return NULL;
394         }
395
396         status = sqlite3_bind_int(stmt, 1, id);
397         if (status != SQLITE_OK) {
398                 ErrPrint("Failed to bind id: %s\n", sqlite3_errmsg(s_info.handle));
399                 ret = NULL;
400                 goto out;
401         }
402
403         status = sqlite3_bind_text(stmt, 2, lang, -1, NULL);
404         if (status != SQLITE_OK) {
405                 ErrPrint("Failed to bind lang: %s\n", sqlite3_errmsg(s_info.handle));
406                 ret = NULL;
407                 goto out;
408         }
409
410         DbgPrint("id: %d, lang: %s\n", id, lang);
411         if (SQLITE_ROW != sqlite3_step(stmt)) {
412                 ErrPrint("Failed to do step: %s\n", sqlite3_errmsg(s_info.handle));
413                 ret = NULL;
414                 goto out;
415         }
416
417         name = sqlite3_column_text(stmt, 0);
418         ret = name ? strdup((const char *)name) : NULL;
419
420 out:
421         sqlite3_reset(stmt);
422         sqlite3_clear_bindings(stmt);
423         sqlite3_finalize(stmt);
424         return ret;
425 }
426
427
428
429 static inline int homescreen_get_i18n(const char *appid, const char *lang, char **name, char **desc)
430 {
431         sqlite3_stmt *stmt;
432         static const char *query = "SELECT name, desc FROM desc WHERE appid = ? AND lang = ?";
433         const unsigned char *_name;
434         const unsigned char *_desc;
435         int status;
436
437         status = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL);
438         if (status != SQLITE_OK) {
439                 ErrPrint("Failed to prepare stmt: %s\n", sqlite3_errmsg(s_info.handle));
440                 return -EIO;
441         }
442
443         status = sqlite3_bind_text(stmt, 1, appid, -1, NULL);
444         if (status != SQLITE_OK) {
445                 ErrPrint("Failed to bind appid: %s\n", sqlite3_errmsg(s_info.handle));
446                 status = -EIO;
447                 goto out;
448         }
449
450         status = sqlite3_bind_text(stmt, 2, lang, -1, NULL);
451         if (status != SQLITE_OK) {
452                 ErrPrint("Failed to bind lang: %s\n", sqlite3_errmsg(s_info.handle));
453                 status = -EIO;
454                 goto out;
455         }
456
457         if (SQLITE_ROW != sqlite3_step(stmt)) {
458                 ErrPrint("Failed to do step: %s\n", sqlite3_errmsg(s_info.handle));
459                 status = -EIO;
460                 goto out;
461         }
462
463         if (name) {
464                 _name = sqlite3_column_text(stmt, 0);
465                 *name = _name ? strdup((const char *)_name) : NULL;
466         }
467
468         if (desc) {
469                 _desc = sqlite3_column_text(stmt, 1);
470                 *desc = _desc ? strdup((const char *)_desc) : NULL;
471         }
472
473 out:
474         sqlite3_reset(stmt);
475         sqlite3_clear_bindings(stmt);
476         sqlite3_finalize(stmt);
477         return status;
478 }
479
480
481
482 /*!
483  * cb: SYNC callback
484  */
485 EAPI int homescreen_get_description(const char *appid, void (*cb)(const char *appid, const char *icon, const char *name, const char *desc, void *data), void *data)
486 {
487         sqlite3_stmt *stmt;
488         static const char *query = "SELECT icon, name, desc FROM homescreen WHERE appid = ?";
489         char *i18n_name;
490         char *i18n_desc;
491         const unsigned char *desc;
492         const unsigned char *name;
493         const unsigned char *icon;
494         int ret;
495
496         ret = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL);
497         if (ret != SQLITE_OK) {
498                 ErrPrint("Prepare failed: %s\n", sqlite3_errmsg(s_info.handle));
499                 ret = -EIO;
500                 goto out;
501         }
502
503         ret = sqlite3_bind_text(stmt, 1, appid, -1, NULL);
504         if (ret != SQLITE_OK) {
505                 ErrPrint("Prepare failed: %s\n", sqlite3_errmsg(s_info.handle));
506                 ret = -EIO;
507                 goto out;
508         }
509
510         if (SQLITE_ROW != sqlite3_step(stmt)) {
511                 ErrPrint("Step failed: %s\n", sqlite3_errmsg(s_info.handle));
512                 ret = -EIO;
513                 goto out;
514         }
515
516         icon = sqlite3_column_text(stmt, 0);
517         name = sqlite3_column_text(stmt, 1);
518         desc = sqlite3_column_text(stmt, 2);
519
520         /*!
521          * \todo
522          * Get the i18n name and desc
523          */
524         if (homescreen_get_i18n(appid, "en-us", &i18n_name, &i18n_desc) < 0) {
525                 i18n_name = NULL;
526                 i18n_desc = NULL;
527         }
528
529         cb(appid, (const char *)icon, i18n_name ? i18n_name : (const char *)name, i18n_desc ? i18n_desc : (const char *)desc, data);
530
531         free(i18n_name);
532         free(i18n_desc);
533
534 out:
535         sqlite3_reset(stmt);
536         sqlite3_clear_bindings(stmt);
537         sqlite3_finalize(stmt);
538         return ret;
539 }
540
541
542
543 EAPI char *homescreen_get_image(const char *appid, int idx)
544 {
545         static const char *query = "SELECT path FROM image WHERE appid = ? AND id = ?";
546         sqlite3_stmt *stmt;
547         int ret;
548         const unsigned char *path;
549         char *ret_path = NULL;
550
551         ret = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL);
552         if (ret != SQLITE_OK) {
553                 ErrPrint("Prepare failed: %s\n", sqlite3_errmsg(s_info.handle));
554                 goto out;
555         }
556
557         ret = sqlite3_bind_text(stmt, 1, appid, -1, NULL);
558         if (ret != SQLITE_OK) {
559                 ErrPrint("bind failed: %s\n", sqlite3_errmsg(s_info.handle));
560                 goto out;
561         }
562
563         ret = sqlite3_bind_int(stmt, 2, idx);
564         if (ret != SQLITE_OK) {
565                 ErrPrint("bind failed: %s\n", sqlite3_errmsg(s_info.handle));
566                 goto out;
567         }
568
569         if (SQLITE_ROW != sqlite3_step(stmt)) {
570                 ErrPrint("Step failed: %s\n", sqlite3_errmsg(s_info.handle));
571                 goto out;
572         }
573
574         path = sqlite3_column_text(stmt, 0);
575         if (!path) {
576                 ErrPrint("Get result: %s\n", sqlite3_errmsg(s_info.handle));
577                 goto out;
578         }
579
580         ret_path = strdup((const char *)path);
581         if (!ret_path)
582                 ErrPrint("Heap: %s\n", strerror(errno));
583
584 out:
585         sqlite3_reset(stmt);
586         sqlite3_clear_bindings(stmt);
587         sqlite3_finalize(stmt);
588         return ret_path;
589 }
590
591
592
593 EAPI int homescreen_get_image_count(const char *appid)
594 {
595         static const char *query = "SELECT COUNT(id) FROM image WHERE appid = ?";
596         sqlite3_stmt *stmt;
597         int ret;
598
599         ret = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL);
600         if (ret != SQLITE_OK) {
601                 ErrPrint("bind failed: %s\n", sqlite3_errmsg(s_info.handle));
602                 ret = -EIO;
603                 goto out;
604         }
605
606         ret = sqlite3_bind_text(stmt, 1, appid, -1, NULL);
607         if (ret != SQLITE_OK) {
608                 ErrPrint("bind failed: %s\n", sqlite3_errmsg(s_info.handle));
609                 ret = -EIO;
610                 goto out;
611         }
612
613         if (SQLITE_ROW != sqlite3_step(stmt)) {
614                 ErrPrint("step failed: %s\n", sqlite3_errmsg(s_info.handle));
615                 ret = -EIO;
616                 goto out;
617         }
618
619         ret = sqlite3_column_int(stmt, 0);
620
621 out:
622         sqlite3_reset(stmt);
623         sqlite3_clear_bindings(stmt);
624         sqlite3_finalize(stmt);
625         return ret;
626 }
627
628
629
630 static inline char *cur_locale(void)
631 {
632         char *language;
633         language = vconf_get_str(VCONFKEY_LANGSET);
634         if (language) {
635                 char *ptr;
636
637                 ptr = language;
638                 while (*ptr) {
639                         if (*ptr == '.') {
640                                 *ptr = '\0';
641                                 break;
642                         }
643
644                         if (*ptr == '_')
645                                 *ptr = '-';
646
647                         ptr++;
648                 }
649         } else {
650                 language = strdup("en-us");
651                 if (!language)
652                         ErrPrint("Heap: %s\n", strerror(errno));
653         }
654
655         return language;
656 }
657
658
659
660 /*!
661  * \note READ ONLY DB
662  */
663 EAPI int shortcut_get_list(const char *appid, int (*cb)(const char *appid, const char *icon, const char *name, const char *extra_key, const char *extra_data, void *data), void *data)
664 {
665         sqlite3_stmt *stmt;
666         const char *query;
667         const unsigned char *name;
668         char *i18n_name;
669         const unsigned char *extra_data;
670         const unsigned char *extra_key;
671         const unsigned char *icon;
672         int id;
673         int ret;
674         int cnt;
675         char *language;
676
677         if (!s_info.db_opened)
678                 s_info.db_opened = (open_db() == 0);
679
680         if (!s_info.db_opened) {
681                 ErrPrint("Failed to open a DB\n");
682                 return -EIO;
683         }
684
685         language = cur_locale();
686         if (!language) {
687                 ErrPrint("Locale is not valid\n");
688                 return -EINVAL;
689         }
690
691         if (appid) {
692                 query = "SELECT id, appid, name, extra_key, extra_data, icon FROM shortcut_service WHERE appid = ?";
693                 ret = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL);
694                 if (ret != SQLITE_OK) {
695                         ErrPrint("prepare: %s\n", sqlite3_errmsg(s_info.handle));
696                         free(language);
697                         return -EIO;
698                 }
699
700                 ret = sqlite3_bind_text(stmt, 1, appid, -1, NULL);
701                 if (ret != SQLITE_OK) {
702                         ErrPrint("bind text: %s\n", sqlite3_errmsg(s_info.handle));
703                         sqlite3_finalize(stmt);
704                         free(language);
705                         return -EIO;
706                 }
707         } else {
708                 query = "SELECT id, appid, name, extra_key, extra_data, icon FROM shortcut_service";
709                 ret = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL);
710                 if (ret != SQLITE_OK) {
711                         ErrPrint("prepare: %s\n", sqlite3_errmsg(s_info.handle));
712                         free(language);
713                         return -EIO;
714                 }
715         }
716
717         cnt = 0;
718         while (SQLITE_ROW == sqlite3_step(stmt)) {
719                 id = sqlite3_column_int(stmt, 0);
720
721                 appid = (const char *)sqlite3_column_text(stmt, 1);
722                 if (!appid) {
723                         LOGE("Failed to get package name\n");
724                         continue;
725                 }
726
727                 name = sqlite3_column_text(stmt, 2);
728                 if (!name) {
729                         LOGE("Failed to get name\n");
730                         continue;
731                 }
732
733                 extra_key = sqlite3_column_text(stmt, 3);
734                 if (!extra_key) {
735                         LOGE("Failed to get service\n");
736                         continue;
737                 }
738
739                 extra_data = sqlite3_column_text(stmt, 4);
740                 if (!extra_data) {
741                         LOGE("Failed to get service\n");
742                         continue;
743                 }
744
745                 icon = sqlite3_column_text(stmt, 5);
746                 if (!icon) {
747                         LOGE("Failed to get icon\n");
748                         continue;
749                 }
750
751                 /*!
752                  * \todo
753                  * Implement the "GET LOCALE" code
754                  */
755                 i18n_name = get_i18n_name(language, id);
756
757                 cnt++;
758                 if (cb(appid, (char *)icon, (i18n_name != NULL ? i18n_name : (char *)name), (char *)extra_key, (char *)extra_data, data) < 0) {
759                         free(i18n_name);
760                         break;
761                 }
762
763                 free(i18n_name);
764         }
765
766         sqlite3_reset(stmt);
767         sqlite3_clear_bindings(stmt);
768         sqlite3_finalize(stmt);
769         free(language);
770         return cnt;
771 }
772
773
774
775 /* End of a file */