GnmFunc: make this a GObject.
[gnumeric.git] / plugins / sample_datasource / sample_datasource.c
blob36e990a9137d478f29644e7edff46fdfa08c3040
1 /*
2 * sample_datasource.c: A prototype for handling external data sources
4 * Copyright (C) 2002 Jody Goldberg (jody@gnome.org)
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) version 3.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
22 #include <gnumeric-config.h>
23 #include <gnumeric.h>
25 #include <func.h>
26 #include <value.h>
27 #include <workbook.h>
28 #include <application.h>
29 #include <sheet.h>
30 #include <gutils.h>
31 #include <gnm-i18n.h>
32 #include <goffice/goffice.h>
33 #include <gnm-plugin.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <errno.h>
40 #include <string.h>
41 #include <fcntl.h>
42 #include <stdio.h>
43 #include <glib/gstdio.h>
45 #undef G_LOG_DOMAIN
46 #define G_LOG_DOMAIN "gnumeric:atl"
48 GNM_PLUGIN_MODULE_HEADER;
50 static gboolean debug;
51 static int atl_fd = -1;
52 static char * atl_filename = NULL;
53 static FILE *atl_file = NULL;
54 static guint atl_source = 0;
55 static GHashTable *watched_values = NULL;
56 static GHashTable *watchers = NULL;
58 typedef struct {
59 char *name;
60 gnm_float value;
61 gboolean valid;
62 GHashTable *deps;
63 } WatchedValue;
65 typedef struct {
66 GnmExprFunction const *node; /* Expression node that calls us */
67 GnmDependent *dep; /* GnmDependent containing that node */
69 WatchedValue *value;
70 } Watcher;
72 static guint
73 watcher_hash (Watcher const *w)
75 return (GPOINTER_TO_INT(w->node) << 16) + GPOINTER_TO_INT(w->dep);
77 static gint
78 watcher_equal (Watcher const *w1, Watcher const *w2)
80 return w1->node == w2->node && w1->dep == w2->dep;
83 static WatchedValue *
84 watched_value_fetch (char const *tag)
86 WatchedValue *val = g_hash_table_lookup (watched_values, tag);
87 if (val == NULL) {
88 val = g_new (WatchedValue, 1);
89 val->name = g_strdup (tag);
90 val->value = 0.;
91 val->valid = FALSE;
92 val->deps = g_hash_table_new (g_direct_hash, g_direct_equal);
93 g_hash_table_insert (watched_values, val->name, val);
95 return val;
98 /***************************************************************************/
100 static void
101 cb_watcher_queue_recalc (gpointer key, gpointer value, gpointer closure)
103 Watcher const *w = key;
104 dependent_queue_recalc (w->dep);
105 gnm_app_recalc ();
108 static gboolean
109 cb_atl_input (GIOChannel *gioc, GIOCondition cond, gpointer ignored)
111 char buf[128];
113 /* quick format ticker:value\n
114 * there is no notion of a field for now.
116 while (fgets (buf, sizeof (buf), atl_file) != NULL) {
117 char *sym = buf;
118 char *value_str = strchr (buf, ':');
120 if (value_str != NULL) {
121 gnm_float val;
122 char *end;
123 *value_str++ = '\0';
125 val = gnm_strto (value_str, &end);
126 if (sym != end && errno == 0) {
127 WatchedValue *wv = watched_value_fetch (sym);
128 wv->valid = TRUE;
129 wv->value = val;
131 g_hash_table_foreach (wv->deps,
132 cb_watcher_queue_recalc, NULL);
133 g_printerr ("'%s' <= %" GNM_FORMAT_f "\n", sym, val);
138 return TRUE;
141 static GnmValue *
142 atl_last (GnmFuncEvalInfo *ei, GnmValue const * const argv[])
144 WatchedValue *val = watched_value_fetch (value_peek_string (argv[0]));
146 Watcher key;
147 key.node = ei->func_call;
148 key.dep = ei->pos->dep;
150 g_return_val_if_fail (val != NULL,
151 value_new_error_NA (ei->pos));
153 /* If caller wants to be notified of updates */
154 if (key.node != NULL && key.dep != NULL) {
155 Watcher *w = g_hash_table_lookup (watchers, &key);
156 if (w == NULL) {
157 w = g_new (Watcher, 1);
158 key.value = val;
159 *w = key;
160 g_hash_table_insert (watchers, w, w);
161 g_hash_table_insert (w->value->deps, w, w);
162 } else if (w->value != val) {
163 g_hash_table_remove (w->value->deps, w);
164 w->value = val;
165 g_hash_table_insert (w->value->deps, w, w);
169 if (!val->valid)
170 return value_new_error_NA (ei->pos);
171 return value_new_float (val->value);
174 static int // GnmDependentFlags
175 atl_last_link (GnmFunc *func, GnmFuncEvalInfo *ei, gboolean qlink)
177 if (qlink) {
178 if (debug)
179 g_printerr ("link atl_last\n");
180 } else {
181 Watcher key, *w;
182 key.node = ei->func_call;
183 key.dep = ei->pos->dep;
185 w = g_hash_table_lookup (watchers, &key);
186 if (w != NULL) {
187 if (w->value != NULL)
188 g_hash_table_remove (w->value->deps, w);
189 g_free (w);
191 if (debug)
192 g_printerr ("unlink atl_last\n");
194 return DEPENDENT_NO_FLAG;
197 static GnmFuncHelp const help_atl_last[] = {
198 { GNM_FUNC_HELP_NAME, F_("ATL_LAST:sample real-time data source")},
199 { GNM_FUNC_HELP_ARG, F_("tag:tag to watch")},
200 { GNM_FUNC_HELP_DESCRIPTION, F_("ATL_LAST is a sample implementation of a real time data source. It takes a string tag and monitors the named pipe ~/atl for changes to the value of that tag.") },
201 { GNM_FUNC_HELP_NOTE, F_("This is not intended to be generally enabled and is OFF by default.") },
202 { GNM_FUNC_HELP_END }
205 GnmFuncDescriptor const ATL_functions[] = {
206 {"atl_last", "s", help_atl_last, atl_last, NULL,
207 GNM_FUNC_SIMPLE, GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE
210 {NULL}
213 G_MODULE_EXPORT void
214 go_plugin_init (GOPlugin *plugin, GOCmdContext *cc)
216 GIOChannel *channel = NULL;
217 char *filename;
218 GnmFunc *atl_last = gnm_func_lookup ("atl_last", NULL);
220 g_signal_connect (atl_last, "link-dep", G_CALLBACK (atl_last_link), NULL);
222 debug = gnm_debug_flag ("datasource");
224 if (debug)
225 g_printerr (">>>>>>>>>>>>>>>>>>>>>>>>>>>> LOAD ATL\n");
226 g_return_if_fail (atl_fd < 0);
228 filename = g_build_filename (g_get_home_dir (), "atl", NULL);
230 /* NOTE : better to use popen here, but this is fine for testing */
231 #ifdef HAVE_MKFIFO
232 #warning "If gstdio.h had g_mkfifo, that's what we should use here"
233 if (mkfifo (filename, S_IRUSR | S_IWUSR) == 0) {
234 atl_filename = filename;
235 atl_fd = g_open (atl_filename, O_RDWR|O_NONBLOCK, 0);
236 } else
237 #endif /* HAVE_MKFIFO */
238 g_free (filename);
240 if (atl_fd >= 0) {
241 atl_file = fdopen (atl_fd, "rb");
242 channel = g_io_channel_unix_new (atl_fd);
243 atl_source = g_io_add_watch (channel,
244 G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
245 cb_atl_input, NULL);
246 g_io_channel_unref (channel);
248 watched_values = g_hash_table_new (
249 (GHashFunc) g_str_hash,
250 (GEqualFunc) g_str_equal);
251 watchers = g_hash_table_new (
252 (GHashFunc) watcher_hash,
253 (GEqualFunc) watcher_equal);
256 /* TODO : init and cleanup should be given CommandContexts
257 * to make things tidier
259 G_MODULE_EXPORT void
260 go_plugin_shutdown (GOPlugin *plugin, GOCmdContext *cc)
262 if (debug)
263 g_printerr ("UNLOAD ATL >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
265 if (atl_source) {
266 g_source_remove (atl_source);
267 atl_source = 0;
270 if (atl_filename) {
271 g_unlink (atl_filename);
272 g_free (atl_filename);
273 atl_filename = NULL;
276 if (atl_fd >= 0) {
277 close (atl_fd);
278 atl_fd = -1;
281 if (atl_file != NULL) {
282 fclose (atl_file);
283 atl_file = NULL;
286 g_hash_table_destroy (watched_values);
287 watched_values = NULL;
288 g_hash_table_destroy (watchers);
289 watchers = NULL;