diff options
Diffstat (limited to 'lib/src/main.c')
-rwxr-xr-x | lib/src/main.c | 887 |
1 files changed, 887 insertions, 0 deletions
diff --git a/lib/src/main.c b/lib/src/main.c new file mode 100755 index 0000000..23dbe83 --- /dev/null +++ b/lib/src/main.c @@ -0,0 +1,887 @@ +/* + * Copyright (c) 2000 - 2013 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <libgen.h> + +#include <dlog.h> +#include <glib.h> +#include <db-util.h> +#include <vconf.h> +#include <vconf-keys.h> + +#include <packet.h> +#include <com-core.h> +#include <com-core_packet.h> + +#include "shortcut.h" +#include "shortcut_product.h" +#include "shortcut_private.h" + +int errno; + +static struct info { + const char *dbfile; + sqlite3 *handle; + int server_fd; + int client_fd; + const char *socket_file; + struct { + int (*request_cb)(const char *appid, const char *name, int type, const char *content, const char *icon, pid_t pid, double period, int allow_duplicate, void *data); + void *data; + } server_cb; + int initialized; + int db_opened; + guint timer_id; +} s_info = { + .server_fd = -1, + .client_fd = -1, + .socket_file = "/tmp/.shortcut.service", + .dbfile = "/opt/dbspace/.shortcut_service.db", + .handle = NULL, + .initialized = 0, + .db_opened = 0, + .timer_id = 0, +}; + + +static inline int make_connection(void); + + +static struct packet *remove_shortcut_handler(pid_t pid, int handle, const struct packet *packet) +{ + const char *appid; + const char *name; + const char *content_info; + int ret; + int sender_pid; + + if (!packet) { + ErrPrint("Packet is NIL, maybe disconnected?\n"); + return NULL; + } + + if (packet_get(packet, "isss", &sender_pid, &appid, &name, &content_info) != 4) { + ErrPrint("Invalid apcket\n"); + return NULL; + } + + DbgPrint("appid[%s], name[%s], content_info[%s]\n", appid, name, content_info); + + if (s_info.server_cb.request_cb) { + ret = s_info.server_cb.request_cb(appid, name, SHORTCUT_REMOVE, content_info, NULL, sender_pid, -1.0f, 0, s_info.server_cb.data); + } else { + ret = SHORTCUT_ERROR_UNSUPPORTED; + } + + return packet_create_reply(packet, "i", ret); +} + + + +static struct packet *remove_dynamicbox_handler(pid_t pid, int handle, const struct packet *packet) +{ + const char *appid; + const char *name; + int ret; + int sender_pid; + + if (!packet) { + ErrPrint("PAcket is NIL, maybe disconnected?\n"); + return NULL; + } + + if (packet_get(packet, "iss", &sender_pid, &appid, &name) != 3) { + ErrPrint("Invalid packet\n"); + return NULL; + } + + DbgPrint("appid[%s], name[%s]\n", appid, name); + + if (s_info.server_cb.request_cb) { + ret = s_info.server_cb.request_cb(appid, name, DYNAMICBOX_REMOVE, NULL, NULL, sender_pid, -1.0f, 0, s_info.server_cb.data); + } else { + ret = SHORTCUT_ERROR_UNSUPPORTED; + } + + return packet_create_reply(packet, "i", ret); +} + + + +static struct packet *add_shortcut_handler(pid_t pid, int handle, const struct packet *packet) +{ + const char *appid; + const char *name; + int type; + const char *content; + const char *icon; + int allow_duplicate; + int ret; + int sender_pid; + + if (!packet) { + return NULL; + } + + if (packet_get(packet, "ississi", &sender_pid, &appid, &name, &type, &content, &icon, &allow_duplicate) != 7) { + ErrPrint("Invalid packet\n"); + return NULL; + } + + DbgPrint("appid[%s], name[%s], type[0x%x], content[%s], icon[%s] allow_duplicate[%d]\n", appid, name, type, content, icon, allow_duplicate); + + if (s_info.server_cb.request_cb) { + ret = s_info.server_cb.request_cb(appid, name, type, content, icon, sender_pid, -1.0f, allow_duplicate, s_info.server_cb.data); + } else { + ret = SHORTCUT_ERROR_UNSUPPORTED; + } + + return packet_create_reply(packet, "i", ret); +} + + + +static struct packet *add_dynamicbox_handler(pid_t pid, int handle, const struct packet *packet) +{ + const char *appid; + const char *name; + int type; + const char *content; + const char *icon; + double period; + int allow_duplicate; + int ret; + int sender_pid; + + if (!packet) { + return NULL; + } + + if (packet_get(packet, "ississdi", &sender_pid, &appid, &name, &type, &content, &icon, &period, &allow_duplicate) != 8) { + ErrPrint("Invalid packet\n"); + return NULL; + } + + DbgPrint("appid[%s], name[%s], type[0x%x], content[%s], icon[%s], period[%lf], allow_duplicate[%d]\n", appid, name, type, content, icon, period, allow_duplicate); + + if (s_info.server_cb.request_cb) { + ret = s_info.server_cb.request_cb(appid, name, type, content, icon, sender_pid, period, allow_duplicate, s_info.server_cb.data); + } else { + ret = 0; + } + + return packet_create_reply(packet, "i", ret); +} + + + +static void master_started_cb(keynode_t *node, void *user_data) +{ + int state = 0; + + if (vconf_get_bool(VCONFKEY_MASTER_STARTED, &state) < 0) { + ErrPrint("Unable to get \"%s\"\n", VCONFKEY_MASTER_STARTED); + } + + if (state == 1 && make_connection() == SHORTCUT_ERROR_NONE) { + (void)vconf_ignore_key_changed(VCONFKEY_MASTER_STARTED, master_started_cb); + } +} + + + +static gboolean timeout_cb(void *data) +{ + int ret; + + ret = vconf_notify_key_changed(VCONFKEY_MASTER_STARTED, master_started_cb, NULL); + if (ret < 0) { + ErrPrint("Failed to add vconf for service state [%d]\n", ret); + } else { + DbgPrint("vconf is registered\n"); + } + + master_started_cb(NULL, NULL); + + s_info.timer_id = 0; + return FALSE; +} + + + +static int disconnected_cb(int handle, void *data) +{ + if (s_info.client_fd == handle) { + s_info.client_fd = SHORTCUT_ERROR_INVALID_PARAMETER; + return 0; + } + + if (s_info.server_fd == handle) { + if (!s_info.timer_id) { + s_info.server_fd = SHORTCUT_ERROR_INVALID_PARAMETER; + s_info.timer_id = g_timeout_add(1000, timeout_cb, NULL); + if (!s_info.timer_id) { + ErrPrint("Unable to add timer\n"); + } + } + return 0; + } + + return 0; +} + + + +static inline int make_connection(void) +{ + int ret; + struct packet *packet; + static struct method service_table[] = { + { + .cmd = "add_shortcut", + .handler = add_shortcut_handler, + }, + { + .cmd = "add_dynamicbox", + .handler = add_dynamicbox_handler, + }, + { + .cmd = "rm_shortcut", + .handler = remove_shortcut_handler, + }, + { + .cmd = "rm_dynamicbox", + .handler = remove_dynamicbox_handler, + }, + { + .cmd = NULL, + .handler = NULL, + }, + }; + + if (s_info.initialized == 0) { + s_info.initialized = 1; + com_core_add_event_callback(CONNECTOR_DISCONNECTED, disconnected_cb, NULL); + } + + s_info.server_fd = com_core_packet_client_init(s_info.socket_file, 0, service_table); + if (s_info.server_fd < 0) { + ErrPrint("Failed to make a connection to the master\n"); + return SHORTCUT_ERROR_COMM; + } + + packet = packet_create_noack("service_register", ""); + if (!packet) { + ErrPrint("Failed to build a packet\n"); + return SHORTCUT_ERROR_FAULT; + } + + ret = com_core_packet_send_only(s_info.server_fd, packet); + DbgPrint("Service register sent: %d\n", ret); + packet_destroy(packet); + if (ret != 0) { + com_core_packet_client_fini(s_info.server_fd); + s_info.server_fd = -1; + ret = SHORTCUT_ERROR_COMM; + } else { + ret = SHORTCUT_ERROR_NONE; + } + + DbgPrint("Server FD: %d\n", s_info.server_fd); + return ret; +} + + + +EAPI int shortcut_set_request_cb(request_cb_t request_cb, void *data) +{ + if (request_cb == NULL) { + return SHORTCUT_ERROR_INVALID_PARAMETER; + } + + s_info.server_cb.request_cb = request_cb; + s_info.server_cb.data = data; + + if (s_info.server_fd < 0) { + int ret; + + ret = vconf_notify_key_changed(VCONFKEY_MASTER_STARTED, master_started_cb, NULL); + if (ret < 0) { + ErrPrint("Failed to add vconf for service state [%d]\n", ret); + return SHORTCUT_ERROR_COMM; + } else { + DbgPrint("vconf is registered\n"); + } + + master_started_cb(NULL, NULL); + } + + return SHORTCUT_ERROR_NONE; +} + + + +struct result_cb_item { + result_internal_cb_t result_cb; + void *data; +}; + + + +static int shortcut_send_cb(pid_t pid, int handle, const struct packet *packet, void *data) +{ + struct result_cb_item *item = data; + int ret; + + if (!packet) { + ErrPrint("Packet is not valid\n"); + ret = SHORTCUT_ERROR_FAULT; + } else if (packet_get(packet, "i", &ret) != 1) { + ErrPrint("Packet is not valid\n"); + ret = SHORTCUT_ERROR_INVALID_PARAMETER; + } + + if (item->result_cb) { + ret = item->result_cb(ret, pid, item->data); + } else { + ret = SHORTCUT_ERROR_NONE; + } + free(item); + return ret; +} + + + +EAPI int add_to_home_remove_shortcut(const char *appid, const char *name, const char *content_info, result_internal_cb_t result_cb, void *data) +{ + struct packet *packet; + struct result_cb_item *item; + int ret; + + if (!appid || !name) { + ErrPrint("Invalid argument\n"); + return SHORTCUT_ERROR_INVALID_PARAMETER; + } + + if (!s_info.initialized) { + s_info.initialized = 1; + com_core_add_event_callback(CONNECTOR_DISCONNECTED, disconnected_cb, NULL); + } + + if (s_info.client_fd < 0) { + static struct method service_table[] = { + { + .cmd = NULL, + .handler = NULL, + }, + }; + + s_info.client_fd = com_core_packet_client_init(s_info.socket_file, 0, service_table); + if (s_info.client_fd < 0) { + ErrPrint("Failed to make connection\n"); + return SHORTCUT_ERROR_COMM; + } + } + + item = malloc(sizeof(*item)); + if (!item) { + ErrPrint("Heap: %s\n", strerror(errno)); + return SHORTCUT_ERROR_OUT_OF_MEMORY; + } + + item->result_cb = result_cb; + item->data = data; + + packet = packet_create("rm_shortcut", "isss", getpid(), appid, name, content_info); + if (!packet) { + ErrPrint("Failed to build a packet\n"); + free(item); + return SHORTCUT_ERROR_FAULT; + } + + ret = com_core_packet_async_send(s_info.client_fd, packet, 0.0f, shortcut_send_cb, item); + if (ret < 0) { + packet_destroy(packet); + free(item); + com_core_packet_client_fini(s_info.client_fd); + s_info.client_fd = SHORTCUT_ERROR_INVALID_PARAMETER; + return SHORTCUT_ERROR_COMM; + } + + return SHORTCUT_ERROR_NONE; +} + + + +EAPI int add_to_home_remove_dynamicbox(const char *appid, const char *name, result_internal_cb_t result_cb, void *data) +{ + struct packet *packet; + struct result_cb_item *item; + int ret; + + if (!appid || !name) { + ErrPrint("Invalid argument\n"); + return SHORTCUT_ERROR_INVALID_PARAMETER; + } + + if (!s_info.initialized) { + s_info.initialized = 1; + com_core_add_event_callback(CONNECTOR_DISCONNECTED, disconnected_cb, NULL); + } + + if (s_info.client_fd < 0) { + static struct method service_table[] = { + { + .cmd = NULL, + .handler = NULL, + }, + }; + + + s_info.client_fd = com_core_packet_client_init(s_info.socket_file, 0, service_table); + if (s_info.client_fd < 0) { + ErrPrint("Failed to make connection\n"); + return SHORTCUT_ERROR_COMM; + } + } + + item = malloc(sizeof(*item)); + if (!item) { + ErrPrint("Heap: %s\n", strerror(errno)); + return SHORTCUT_ERROR_OUT_OF_MEMORY; + } + + item->result_cb = result_cb; + item->data = data; + + packet = packet_create("rm_dynamicbox", "iss", getpid(), appid, name); + if (!packet) { + ErrPrint("Failed to build a packet\n"); + free(item); + return SHORTCUT_ERROR_FAULT; + } + + ret = com_core_packet_async_send(s_info.client_fd, packet, 0.0f, shortcut_send_cb, item); + if (ret < 0) { + packet_destroy(packet); + free(item); + com_core_packet_client_fini(s_info.client_fd); + s_info.client_fd = SHORTCUT_ERROR_INVALID_PARAMETER; + return SHORTCUT_ERROR_COMM; + } + + return SHORTCUT_ERROR_NONE; +} + + + +EAPI int add_to_home_shortcut(const char *appid, const char *name, int type, const char *content, const char *icon, int allow_duplicate, result_internal_cb_t result_cb, void *data) +{ + struct packet *packet; + struct result_cb_item *item; + int ret; + + if (!appid || ADD_TO_HOME_IS_DYNAMICBOX(type)) { + ErrPrint("Invalid type used for adding a shortcut\n"); + return SHORTCUT_ERROR_INVALID_PARAMETER; + } + + if (!s_info.initialized) { + s_info.initialized = 1; + com_core_add_event_callback(CONNECTOR_DISCONNECTED, disconnected_cb, NULL); + } + + if (s_info.client_fd < 0) { + static struct method service_table[] = { + { + .cmd = NULL, + .handler = NULL, + }, + }; + + s_info.client_fd = com_core_packet_client_init(s_info.socket_file, 0, service_table); + if (s_info.client_fd < 0) { + ErrPrint("Failed to make connection\n"); + return SHORTCUT_ERROR_COMM; + } + } + + item = malloc(sizeof(*item)); + if (!item) { + ErrPrint("Heap: %s\n", strerror(errno)); + return SHORTCUT_ERROR_OUT_OF_MEMORY; + } + + item->result_cb = result_cb; + item->data = data; + + if (!name) { + name = ""; + } + + if (!content) { + content = ""; + } + + if (!icon) { + icon = ""; + } + + packet = packet_create("add_shortcut", "ississi", getpid(), appid, name, type, content, icon, allow_duplicate); + if (!packet) { + ErrPrint("Failed to build a packet\n"); + free(item); + return SHORTCUT_ERROR_FAULT; + } + + ret = com_core_packet_async_send(s_info.client_fd, packet, 0.0f, shortcut_send_cb, item); + if (ret < 0) { + packet_destroy(packet); + free(item); + com_core_packet_client_fini(s_info.client_fd); + s_info.client_fd = SHORTCUT_ERROR_INVALID_PARAMETER; + return SHORTCUT_ERROR_COMM; + } + + return SHORTCUT_ERROR_NONE; +} + +EAPI int shortcut_add_to_home(const char *name, shortcut_type type, const char *uri, const char *icon, int allow_duplicate, result_cb_t result_cb, void *data) +{ + return 0; +} + + +EAPI int add_to_home_dynamicbox(const char *appid, const char *name, int type, const char *content, const char *icon, double period, int allow_duplicate, result_internal_cb_t result_cb, void *data) +{ + struct packet *packet; + struct result_cb_item *item; + int ret; + + if (!appid || !ADD_TO_HOME_IS_DYNAMICBOX(type)) { + ErrPrint("Invalid type is used for adding a dynamicbox\n"); + return SHORTCUT_ERROR_INVALID_PARAMETER; + } + + if (!s_info.initialized) { + s_info.initialized = 1; + com_core_add_event_callback(CONNECTOR_DISCONNECTED, disconnected_cb, NULL); + } + + if (s_info.client_fd < 0) { + static struct method service_table[] = { + { + .cmd = NULL, + .handler = NULL, + }, + }; + + s_info.client_fd = com_core_packet_client_init(s_info.socket_file, 0, service_table); + if (s_info.client_fd < 0) { + return SHORTCUT_ERROR_COMM; + } + } + + item = malloc(sizeof(*item)); + if (!item) { + ErrPrint("Heap: %s\n", strerror(errno)); + return SHORTCUT_ERROR_OUT_OF_MEMORY; + } + + item->result_cb = result_cb; + item->data = data; + + packet = packet_create("add_dynamicbox", "ississdi", getpid(), appid, name, type, content, icon, period, allow_duplicate); + if (!packet) { + ErrPrint("Failed to build a packet\n"); + free(item); + return SHORTCUT_ERROR_FAULT; + } + + ret = com_core_packet_async_send(s_info.client_fd, packet, 0.0f, shortcut_send_cb, item); + if (ret < 0) { + packet_destroy(packet); + free(item); + com_core_packet_client_fini(s_info.client_fd); + s_info.client_fd = SHORTCUT_ERROR_INVALID_PARAMETER; + return SHORTCUT_ERROR_COMM; + } + + return SHORTCUT_ERROR_NONE; +} + + + +static inline int open_db(void) +{ + int ret; + + ret = db_util_open(s_info.dbfile, &s_info.handle, DB_UTIL_REGISTER_HOOK_METHOD); + if (ret != SQLITE_OK) { + DbgPrint("Failed to open a %s\n", s_info.dbfile); + return SHORTCUT_ERROR_IO_ERROR; + } + + return SHORTCUT_ERROR_NONE; +} + + + +/*! + * \note this function will returns allocated(heap) string + */ +static inline int get_i18n_name(const char *lang, int id, char **name, char **icon) +{ + sqlite3_stmt *stmt; + static const char *query = "SELECT name, icon FROM shortcut_name WHERE id = ? AND lang = ? COLLATE NOCASE"; + const unsigned char *_name; + const unsigned char *_icon; + int ret = 0; + int status; + + status = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL); + if (status != SQLITE_OK) { + ErrPrint("Failed to prepare stmt: %s\n", sqlite3_errmsg(s_info.handle)); + return -EFAULT; + } + + status = sqlite3_bind_int(stmt, 1, id); + if (status != SQLITE_OK) { + ErrPrint("Failed to bind id: %s\n", sqlite3_errmsg(s_info.handle)); + ret = -EFAULT; + goto out; + } + + status = sqlite3_bind_text(stmt, 2, lang, -1, SQLITE_TRANSIENT); + if (status != SQLITE_OK) { + ErrPrint("Failed to bind lang: %s\n", sqlite3_errmsg(s_info.handle)); + ret = -EFAULT; + goto out; + } + + DbgPrint("id: %d, lang: %s\n", id, lang); + if (SQLITE_ROW != sqlite3_step(stmt)) { + ErrPrint("Failed to do step: %s\n", sqlite3_errmsg(s_info.handle)); + ret = -ENOENT; + goto out; + } + + _name = sqlite3_column_text(stmt, 0); + if (name) { + if (_name && strlen((const char *)_name)) { + *name = strdup((const char *)_name); + if (!*name) { + ErrPrint("strdup: %s\n", strerror(errno)); + ret = -ENOMEM; + goto out; + } + } else { + *name = NULL; + } + } + + _icon = sqlite3_column_text(stmt, 1); + if (icon) { + if (_icon && strlen((const char *)_icon)) { + *icon = strdup((const char *)_icon); + if (!*icon) { + ErrPrint("strdup: %s\n", strerror(errno)); + ret = -ENOMEM; + if (name && *name) { + free(*name); + } + goto out; + } + } else { + *icon = NULL; + } + } + +out: + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + sqlite3_finalize(stmt); + return ret; +} + + + +static inline char *cur_locale(void) +{ + char *language; + language = vconf_get_str(VCONFKEY_LANGSET); + if (language) { + char *ptr; + + ptr = language; + while (*ptr) { + if (*ptr == '.') { + *ptr = '\0'; + break; + } + + if (*ptr == '_') { + *ptr = '-'; + } + + ptr++; + } + } else { + language = strdup("en-us"); + if (!language) { + ErrPrint("Heap: %s\n", strerror(errno)); + } + } + + return language; +} + + + +/*! + * \note READ ONLY DB + */ +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) +{ + sqlite3_stmt *stmt; + const char *query; + const unsigned char *name; + char *i18n_name = NULL; + char *i18n_icon = NULL; + const unsigned char *extra_data; + const unsigned char *extra_key; + const unsigned char *icon; + int id; + int ret; + int cnt; + char *language; + + if (cb == NULL) { + return SHORTCUT_ERROR_INVALID_PARAMETER; + } + + if (!s_info.db_opened) { + s_info.db_opened = (open_db() == 0); + } + + if (!s_info.db_opened) { + ErrPrint("Failed to open a DB\n"); + return SHORTCUT_ERROR_IO_ERROR; + } + + language = cur_locale(); + if (!language) { + ErrPrint("Locale is not valid\n"); + return SHORTCUT_ERROR_FAULT; + } + + if (appid) { + query = "SELECT id, appid, name, extra_key, extra_data, icon FROM shortcut_service WHERE appid = ?"; + ret = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ErrPrint("prepare: %s\n", sqlite3_errmsg(s_info.handle)); + free(language); + return SHORTCUT_ERROR_IO_ERROR; + } + + ret = sqlite3_bind_text(stmt, 1, appid, -1, SQLITE_TRANSIENT); + if (ret != SQLITE_OK) { + ErrPrint("bind text: %s\n", sqlite3_errmsg(s_info.handle)); + sqlite3_finalize(stmt); + free(language); + return SHORTCUT_ERROR_IO_ERROR; + } + } else { + query = "SELECT id, appid, name, extra_key, extra_data, icon FROM shortcut_service"; + ret = sqlite3_prepare_v2(s_info.handle, query, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ErrPrint("prepare: %s\n", sqlite3_errmsg(s_info.handle)); + free(language); + return SHORTCUT_ERROR_IO_ERROR; + } + } + + cnt = 0; + while (SQLITE_ROW == sqlite3_step(stmt)) { + id = sqlite3_column_int(stmt, 0); + + appid = (const char *)sqlite3_column_text(stmt, 1); + if (!appid) { + LOGE("Failed to get package name\n"); + continue; + } + + name = sqlite3_column_text(stmt, 2); + if (!name) { + LOGE("Failed to get name\n"); + continue; + } + + extra_key = sqlite3_column_text(stmt, 3); + if (!extra_key) { + LOGE("Failed to get service\n"); + continue; + } + + extra_data = sqlite3_column_text(stmt, 4); + if (!extra_data) { + LOGE("Failed to get service\n"); + continue; + } + + icon = sqlite3_column_text(stmt, 5); + if (!icon) { + LOGE("Failed to get icon\n"); + continue; + } + + /*! + * \todo + * Implement the "GET LOCALE" code + */ + if (get_i18n_name(language, id, &i18n_name, &i18n_icon) < 0) { + /* Okay, we can't manage this. just use the fallback string */ + } + + cnt++; + if (cb(appid, (i18n_icon != NULL ? i18n_icon : (char *)icon), (i18n_name != NULL ? i18n_name : (char *)name), (char *)extra_key, (char *)extra_data, data) < 0) { + free(i18n_name); + break; + } + + free(i18n_name); + i18n_name = NULL; + + free(i18n_icon); + i18n_icon = NULL; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + sqlite3_finalize(stmt); + free(language); + return cnt; +} + +/* End of a file */ |