Parse json cmd returns as success if nothing in the command json
[apps/native/tizen-things-daemon.git] / daemon / src / ttd-cmd-mgr.c
1 /*
2  * Copyright (c) 2018 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Flora License, Version 1.1 (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://floralicense.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 <glib.h>
18 #include "ttd-log.h"
19 #include "ttd-cmd-mgr.h"
20 #include "ttd-cmd.h"
21 #include "ttd-cmd-type.h"
22 #include "ttd-cmd-func.h"
23 #include "ttd-http.h"
24 #include "ttd-parse-cmd.h"
25 #include "ttd-build-json.h"
26 #include "common-util.h"
27
28 #define CMD_MGR_GET_INTERVAL_SEC (3600 * 3)
29 #define RESULT_WAIT_IN_SEC (10)
30 #define RESULT_WAIT_TIME (RESULT_WAIT_IN_SEC * 1000000)
31 #define RESULT_WAIT_TRY_MAX (10)
32
33 // #define CMD_FETCH_TEST
34
35 typedef struct _ttd_result_data_s {
36         char *cmd_id;
37         ttd_cmd_result_e result;
38         char *data;
39 } ttd_result_data_s;
40
41 struct __cmd_mgr_h {
42         GAsyncQueue *cmd_id_queue;
43         GHashTable *cmd_hash;
44         GAsyncQueue *result_queue;
45         const char *current_cmd_id;
46         GMutex mutex;
47         GThread *get_thread;
48         int get_thread_running;
49         int get_by_request;
50         GMutex get_mutex;
51         GCond get_cond;
52         GThread *launch_thread;
53 };
54
55 /* Just a pointer value to make unblocking async queue and exiting thread,
56  * it could be any address as long, as it isn't a valid queue item
57  */
58 static const gpointer quit_marker = (gpointer) &ttd_cmd_mgr_init;
59
60 /* Use singleton */
61 static struct __cmd_mgr_h *g_handle = NULL;
62
63 static void __cmd_id_item_free(gpointer data)
64 {
65         if (!data)
66                 return;
67
68         if (data == quit_marker)
69                 return;
70
71         g_free(data);
72 }
73
74 static void __result_item_free(gpointer data)
75 {
76         ttd_result_data_s *item = data;
77
78         if (!data)
79                 return;
80
81         if (data == quit_marker)
82                 return;
83
84         if (item->cmd_id)
85                 _D("free result item[%s]", item->cmd_id);
86         g_free(item->cmd_id);
87         g_free(item->data);
88
89         g_free(item);
90 }
91
92 int ttd_cmd_mgr_push_result(const char *id,
93         ttd_cmd_result_e result, const char *data)
94 {
95         ttd_result_data_s *result_item = NULL;
96         const char *curr_id = NULL;
97
98         retvm_if(!g_handle, -1, "cmd mgr is not initialized yet");
99         retvm_if(!id, -1, "cmd id is NULL");
100
101         g_mutex_lock(&g_handle->mutex);
102         curr_id = g_handle->current_cmd_id;
103         g_mutex_unlock(&g_handle->mutex);
104
105         retvm_if(!curr_id, -1, "current cmd id is NULL");
106
107         if (0 != g_strcmp0(id, curr_id)) {
108                 _E("invaild cmd id[%s] - current cmd id[%s]", id, curr_id);
109                 return -1;
110         }
111
112         result_item = g_try_new0(ttd_result_data_s, 1);
113
114         result_item->cmd_id = g_strdup(id);
115         result_item->result = result;
116         if (data)
117                 result_item->data = g_strdup(data);
118
119         g_async_queue_push(g_handle->result_queue, result_item);
120
121         return 0;
122 }
123
124 int ttd_cmd_mgr_get_cmd(void)
125 {
126         retvm_if(!g_handle, -1, "cmd mgr is not initialized yet");
127         _D("unblocking get_thread");
128         g_mutex_lock(&g_handle->get_mutex);
129         g_handle->get_by_request = 1;
130         g_cond_broadcast(&g_handle->get_cond);
131         g_mutex_unlock(&g_handle->get_mutex);
132         // wait here to get result of getting cmd or not ???
133
134         return 0;
135 }
136
137 static int _get_cloud_cmd(struct __cmd_mgr_h *handle, long *res_code)
138 {
139         int ret  = 0;
140         char *cmd = NULL;
141         long r_code = 0;
142         GList *cmd_list = NULL;
143         GList *l = NULL;
144
145         retv_if(!handle, -1);
146
147         ret = ttd_http_command_get(&cmd, &r_code);
148         if (ret) {
149                 _E("failed to get cmd [%ld]", r_code);
150                 if (res_code)
151                         *res_code = r_code;
152                 return -1;
153         }
154
155         if (res_code)
156                 *res_code = r_code;
157
158         retvm_if(!cmd, 0, "there is no new cmd now");
159
160         ret = ttd_parse_json_to_cmd(cmd, &cmd_list);
161         if (ret) {
162                 _E("failed to parse cmd");
163                 g_free(cmd);
164                 return -1;
165         }
166         g_free(cmd);
167         cmd = NULL;
168
169         if (!cmd_list) {
170                 _D("Thers is no command now");
171                 return 0;
172         }
173
174         for (l = cmd_list; l != NULL; l = l->next) {
175                 ttd_cmd_data *cmd_data = NULL;
176                 ttd_cmd_data *item = NULL;
177                 const char *cmd_id = NULL;
178
179                 cmd_data = (ttd_cmd_data *)l->data;
180                 continue_if(!cmd_data);
181
182                 cmd_id = ttd_cmd_get_id(cmd_data);
183
184                 g_mutex_lock(&handle->mutex);
185                 item = g_hash_table_lookup(handle->cmd_hash, cmd_id);
186                 if (!item) {
187                         _D("cmd[%s] is pushed in cmd queue", cmd_id);
188                         g_async_queue_push(handle->cmd_id_queue, g_strdup(cmd_id));
189                         g_hash_table_insert(handle->cmd_hash, g_strdup(cmd_id), cmd_data);
190                 } else {
191                         _D("cmd[%s] is already in cmd queue", cmd_id);
192                         ttd_cmd_free(cmd_data);
193                 }
194                 g_mutex_unlock(&handle->mutex);
195         }
196         g_list_free(cmd_list);
197
198         return 0;
199 }
200
201 static gpointer _get_thread(gpointer data)
202 {
203         struct __cmd_mgr_h *handle = data;
204
205         g_mutex_lock(&handle->get_mutex);
206         while (TRUE) {
207                 int ret  = 0;
208                 guint64 until = 0;
209                 unsigned int retry = 0;
210
211                 until = common_get_monotonic_coarse_time() +
212                         CMD_MGR_GET_INTERVAL_SEC * G_TIME_SPAN_SECOND;
213                 _D("thread blocked for 3 hours");
214                 g_cond_wait_until(&handle->get_cond, &handle->get_mutex, until);
215                 _D("thread unblocked");
216
217                 if (!handle->get_thread_running)
218                         break;
219
220                 if (handle->get_by_request)
221                         retry = 5;
222                 else
223                         retry = 1;
224
225                 while(retry) {
226                         long res_code = 0;
227
228                         ret = _get_cloud_cmd(handle, &res_code);
229                         if (ret)
230                                 _E("failed to get cmd - %ld", res_code);
231                         else
232                                 _D("res_code: %ld", res_code);
233
234                         if (res_code == TTD_HTTP_OK) // HTTP OK - success to get cmd
235                                 break;
236
237                         retry--;
238                         if (retry)
239                                 g_usleep(G_USEC_PER_SEC); // sleep in 1 second before retry
240                 };
241
242                 handle->get_by_request = 0;
243         }
244         g_mutex_unlock(&handle->get_mutex);
245         _D("thread terminated");
246         return NULL;
247 }
248
249 static gpointer _launch_thread(gpointer data)
250 {
251         struct __cmd_mgr_h *handle = data;
252
253         while (TRUE) {
254                 char *cmd_id = NULL;
255                 ttd_cmd_data *cmd_data = NULL;
256                 char *report = NULL;
257                 long r_code = 0;
258                 int ret  = 0;
259                 ttd_cmd_launch_func launch_func = NULL;
260                 ttd_cmd_type_e cmd_type = TTD_CMD_TYPE_UNKNOWN;
261                 int result_wait = 1;
262
263                 // get pop oldest cmd from cmd_id_queue
264                 _D("block launch thread");
265                 cmd_id = g_async_queue_pop(handle->cmd_id_queue);
266                 if (!cmd_id) {
267                         _D("unblock launch thread");
268                         _D("cmd queue is empty");
269                         goto DONE_N_WAIT;
270                 }
271                 _D("unblock launch thread");
272
273                 if (cmd_id == quit_marker)
274                         goto THREAD_EXIT;
275
276                 // get cmd data from cmd_hash
277                 g_mutex_lock(&handle->mutex);
278                 cmd_data = g_hash_table_lookup(handle->cmd_hash, cmd_id);
279                 g_mutex_unlock(&handle->mutex);
280                 if (!cmd_data) {
281                         _E("data for cmd[%s] is not exist", cmd_id);
282                         goto DONE_N_WAIT;
283                 }
284
285                 // report 'running' state of the cmd id to cloud
286                 cmd_type = ttd_cmd_get_type(cmd_data);
287                 report = ttd_build_json_create_report(cmd_id, cmd_type,
288                         TTD_CMD_STATE_RUNNING, 0, "state update", NULL);
289                 ret = ttd_http_report_post(report, &r_code);
290                 if (r_code != TTD_HTTP_OK) {
291                         _E("failed to post report [%ld]", r_code);
292                         g_free(report);
293                         goto DONE_N_WAIT;
294                 }
295                 g_free(report);
296                 report = NULL;
297                 ttd_cmd_set_state(cmd_data, TTD_CMD_STATE_RUNNING);
298
299                 // execute cmd
300                 launch_func = ttd_cmd_get_launch_func(cmd_type);
301                 if (!launch_func) {
302                         _E("cmd[%s] no proper launch function", cmd_id);
303                         report = ttd_build_json_create_report(cmd_id, cmd_type,
304                                 TTD_CMD_STATE_FAILED, 0, "no proper launch function", NULL);
305                         ttd_http_report_post(report, NULL);
306                         g_free(report);
307                         report = NULL;
308                         goto DONE_N_WAIT;
309                 }
310
311                 g_mutex_lock(&handle->mutex);
312                 g_handle->current_cmd_id = cmd_id;
313                 g_mutex_unlock(&handle->mutex);
314                 ret = launch_func(cmd_data);
315                 if (ret) {
316                         _E("cmd[%s] launch failed", cmd_id);
317                         report = ttd_build_json_create_report(cmd_id, cmd_type,
318                                 TTD_CMD_STATE_FAILED, 0, "command launch failed", NULL);
319                         ttd_http_report_post(report, NULL);
320                         g_free(report);
321                         report = NULL;
322                         goto DONE_N_WAIT;
323                 }
324
325                 while (result_wait) {
326                         ttd_result_data_s *result_item = NULL;
327                         static unsigned int count = 0;
328
329                         result_item = g_async_queue_timeout_pop(
330                                 g_handle->result_queue, RESULT_WAIT_TIME); /* 10 sec */
331
332                         if (!result_item) {
333                                 count++;
334
335                                 if (count <= RESULT_WAIT_TRY_MAX) /* 10 times */
336                                         continue;
337
338                                 /* Wait 100 sec to receive a result,
339                                  * IS IT(100 sec) "ENOUGH" to install pkgs or task monitoring ???
340                                  */
341                                 /* timeout to wait result, report fail */
342                                 report = ttd_build_json_create_report(cmd_id, cmd_type,
343                                         TTD_CMD_STATE_FAILED, 0, "timeout to wait result ", NULL);
344                                 ttd_http_report_post(report, NULL);
345                                 g_free(report);
346                                 report = NULL;
347
348                                 result_wait = 0;
349                                 break;
350                         }
351
352                         if (result_item == quit_marker)
353                                 goto THREAD_EXIT;
354
355                         switch (result_item->result) {
356                         case TTD_CMD_RESULT_RUNNING:
357                                 /* report running and wait more result */
358                                 report = ttd_build_json_create_report(cmd_id, cmd_type,
359                                         TTD_CMD_STATE_RUNNING, 0,
360                                         "report in progress", result_item->data);
361                                 ttd_http_report_post(report, NULL);
362                                 break;
363                         case TTD_CMD_RESULT_SUCCESS:
364                                 /* report done */
365                                 report = ttd_build_json_create_report(cmd_id, cmd_type,
366                                         TTD_CMD_STATE_DONE, 0,
367                                         "done", result_item->data);
368                                 ttd_http_report_post(report, NULL);
369                                 result_wait = 0;
370                                 break;
371                         case TTD_CMD_RESULT_FAIL:
372                                 /* report fail */
373                                 report = ttd_build_json_create_report(cmd_id, cmd_type,
374                                         TTD_CMD_STATE_FAILED, 0,
375                                         "failed to process command", result_item->data);
376                                 ttd_http_report_post(report, NULL);
377                                 result_wait = 0;
378                                 break;
379                         /* unhandled states */
380                         default:
381                                 break;
382                         }
383                         g_free(report);
384                         report = NULL;
385                         __result_item_free(result_item);
386                 }
387
388 DONE_N_WAIT:
389                 if (cmd_id) {
390                         g_mutex_lock(&handle->mutex);
391                         g_handle->current_cmd_id = NULL;
392                         g_hash_table_remove(handle->cmd_hash, cmd_id);
393                         g_mutex_unlock(&handle->mutex);
394                         g_free(cmd_id);
395                 }
396                 continue;
397
398 THREAD_EXIT:
399                 _D("get quit_marker");
400                 break;
401         }
402         _D("thread terminated");
403         return NULL;
404 }
405
406 static void __free_cmd_mgr_handle(void)
407 {
408         if (!g_handle)
409                 return;
410
411         if (g_handle->get_thread) {
412                 g_mutex_lock(&g_handle->get_mutex);
413                 g_handle->get_thread_running = 0;
414                 g_cond_broadcast(&g_handle->get_cond);
415                 g_mutex_unlock(&g_handle->get_mutex);
416
417                 g_thread_join(g_handle->get_thread);
418         }
419         g_mutex_clear(&g_handle->get_mutex);
420         g_cond_clear(&g_handle->get_cond);
421
422         if (g_handle->launch_thread) {
423                 g_async_queue_push(g_handle->cmd_id_queue, quit_marker);
424                 g_async_queue_push(g_handle->result_queue, quit_marker);
425
426                 g_thread_join(g_handle->launch_thread);
427         }
428
429         g_mutex_clear(&g_handle->mutex);
430
431         if (g_handle->result_queue)
432                 g_async_queue_unref(g_handle->result_queue);
433
434         if (g_handle->cmd_id_queue)
435                 g_async_queue_unref(g_handle->cmd_id_queue);
436
437         if (g_handle->cmd_hash) {
438                 g_hash_table_remove_all(g_handle->cmd_hash);
439                 g_hash_table_unref(g_handle->cmd_hash);
440         }
441
442         g_free(g_handle);
443         g_handle = NULL;
444 }
445
446 #ifdef CMD_FETCH_TEST
447 static gboolean __get_cmd_first_time(gpointer data)
448 {
449         ttd_cmd_mgr_get_cmd();
450
451         return G_SOURCE_REMOVE;
452 }
453 #endif /* CMD_FETCH_TEST */
454
455 int ttd_cmd_mgr_init(void)
456 {
457         GError *error = NULL;
458
459         retvm_if(g_handle, -1, "cmd mgr already initialized, finalized it first");
460
461         g_handle = g_try_malloc0(sizeof(struct __cmd_mgr_h));
462         retvm_if(!g_handle, -1, "failed to malloc");
463
464         g_handle->cmd_id_queue = g_async_queue_new_full(__cmd_id_item_free);
465         goto_if(!g_handle->cmd_id_queue, ERROR_N_EXIT);
466
467         g_handle->cmd_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
468                         g_free, (GDestroyNotify)ttd_cmd_free);
469         goto_if(!g_handle->cmd_hash, ERROR_N_EXIT);
470
471         g_handle->result_queue = g_async_queue_new_full(__result_item_free);
472         goto_if(!g_handle->result_queue, ERROR_N_EXIT);
473
474         g_mutex_init(&g_handle->mutex);
475
476         g_mutex_init(&g_handle->get_mutex);
477         g_cond_init(&g_handle->get_cond);
478         g_handle->get_thread_running = 1;
479
480         g_handle->get_thread =
481                 g_thread_try_new(NULL, (GThreadFunc)_get_thread, g_handle, &error);
482         if (!g_handle->get_thread) {
483                 _E("failed to create get thread - %s",
484                         error && error->message ? error->message : "Unknown Error");
485                 goto ERROR_N_EXIT;
486         }
487
488         g_handle->launch_thread =
489                 g_thread_try_new(NULL, (GThreadFunc)_launch_thread, g_handle, &error);
490         if (!g_handle->launch_thread) {
491                 _E("failed to create launch_thread - %s",
492                         error && error->message ? error->message : "Unknown Error");
493                 goto ERROR_N_EXIT;
494         }
495
496 #ifdef CMD_FETCH_TEST
497         g_idle_add(__get_cmd_first_time, NULL);
498 #endif /* CMD_FETCH_TEST */
499
500         return 0;
501
502 ERROR_N_EXIT:
503         if (error)
504                 g_error_free(error);
505         __free_cmd_mgr_handle();
506         return -1;
507 }
508
509 int ttd_cmd_mgr_fini(void)
510 {
511         __free_cmd_mgr_handle();
512         return 0;
513 }