1 /*
2  *  gretl -- Gnu Regression, Econometrics and Time-series Library
3  *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
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 3 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, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #define FULL_XML_HEADERS
21 
22 #include "libgretl.h"
23 #include "version.h"
24 #include "build.h"
25 #include "gretl_xml.h"
26 
27 /* Note: here's the canonical listing of gretl addons. */
28 
29 static const char *addon_names[] = {
30     "SVAR", "gig", "HIP", "ivpanel",
31     "dbnomics", "extra", "geoplot",
32     "regls", "logging", NULL
33 };
34 
35 static int n_addons = G_N_ELEMENTS(addon_names) - 1;
36 
37 /* Determine if @pkgname is the name of an addon:
38    @pkgname may be given with or without the ".gfn"
39    suffix.
40 */
41 
is_gretl_addon(const char * pkgname)42 int is_gretl_addon (const char *pkgname)
43 {
44     int i;
45 
46     if (has_suffix(pkgname, ".gfn")) {
47 	int n = strlen(pkgname) - 4;
48 
49 	for (i=0; i<n_addons; i++) {
50 	    if (!strncmp(pkgname, addon_names[i], n)) {
51 		return 1;
52 	    }
53 	}
54     } else {
55 	for (i=0; i<n_addons; i++) {
56 	    if (!strcmp(pkgname, addon_names[i])) {
57 		return 1;
58 	    }
59 	}
60     }
61 
62     return 0;
63 }
64 
65 /* Return a NULL-terminated array of addons names;
66    optionally, if @n is non-NULL, supply the number
67    of addons.
68 */
69 
get_addon_names(int * n)70 const char **get_addon_names (int *n)
71 {
72     if (n != NULL) {
73 	*n = n_addons;
74     }
75     return addon_names;
76 }
77 
78 /* Given a full path to an addon package, return its
79    version string. If @date is non-NULL, also return its
80    release date via this pointer. In both cases the
81    strings are allocated, and belong to the caller.
82 */
83 
get_addon_version(const char * fname,char ** date)84 char *get_addon_version (const char *fname, char **date)
85 {
86     char *version = NULL;
87     xmlDocPtr doc = NULL;
88     xmlNodePtr node, n1, n2;
89     int targ, got = 0;
90 
91     if (gretl_stat(fname, NULL) != 0) {
92 	/* not found */
93 	return NULL;
94     }
95 
96     gretl_xml_open_doc_root(fname, "gretl-functions", &doc, &node);
97     if (doc == NULL || node == NULL) {
98 	return NULL;
99     }
100 
101     targ = (date != NULL)? 2 : 1;
102     n1 = node->xmlChildrenNode;
103 
104     while (n1 != NULL && got < targ) {
105 	if (!xmlStrcmp(n1->name, (XUC) "gretl-function-package")) {
106 	    n2 = n1->xmlChildrenNode;
107 	    while (n2 != NULL && got < targ) {
108 		if (!xmlStrcmp(n2->name, (XUC) "version")) {
109 		    gretl_xml_node_get_trimmed_string(n2, doc, &version);
110 		    got++;
111 		} else if (date != NULL && !xmlStrcmp(n2->name, (XUC) "date")) {
112 		    gretl_xml_node_get_trimmed_string(n2, doc, date);
113 		    got++;
114 		}
115 		n2 = n2->next;
116 	    }
117 	}
118 	n1 = n1->next;
119     }
120 
121     if (doc != NULL) {
122 	xmlFreeDoc(doc);
123     }
124 
125     return version;
126 }
127 
get_user_path(char * targ,const char * pgkname,const char * gfnname)128 static char *get_user_path (char *targ, const char *pgkname,
129 			    const char *gfnname)
130 {
131 #ifdef OS_OSX
132     return gretl_build_path(targ, gretl_app_support_dir(),
133 			    "functions", pgkname, gfnname, NULL);
134 #else
135     return gretl_build_path(targ, gretl_dotdir(), "functions",
136 			    pgkname, gfnname, NULL);
137 #endif
138 }
139 
140 /* Build a plain text index of the installed addons, holding name,
141    version and full path, one addon per line. We allow for the fact
142    that a given addon might exist both in the "system" location and in
143    the user's personal filespace.  (This may happen if a user lacking
144    write-permission for the system location installs or updates an
145    addon.) In the case of such duplicates we determine which version
146    is newer and enter its details in the index file.
147 
148    This update routine is called automatically when (a) a user
149    installs an addon (FIXME: is this true for all ways an addon can be
150    installed?) or (b) gretl figures out that it has been updated (new
151    release or snapshot). It can also be called explicitly by the user,
152    via the command "pkg index addons".
153 
154    if @prn is non-NULL verbose output will be printed, otherwise the
155    function operates silently.
156 */
157 
update_addons_index(PRN * prn)158 int update_addons_index (PRN *prn)
159 {
160     gchar *idxname = gretl_make_dotpath("addons.idx");
161     char *pkgver1 = NULL;
162     char *pkgver2 = NULL;
163     char syspath[MAXLEN];
164     char usrpath[MAXLEN];
165     char gfnname[64];
166     int verbose = (prn != NULL);
167     double v1, v2;
168     FILE *fp;
169     int i;
170 
171     fp = gretl_fopen(idxname, "wb");
172     if (fp == NULL) {
173 	g_free(idxname);
174 	return E_FOPEN;
175     }
176 
177     for (i=0; i<n_addons; i++) {
178 	if (verbose) {
179 	    pprintf(prn, "check for %s\n", addon_names[i]);
180 	}
181 	/* construct the gfn name */
182 	sprintf(gfnname, "%s.gfn", addon_names[i]);
183 
184 	/* build the "system" path for the addon */
185 	gretl_build_path(syspath, gretl_home(), "functions",
186 			 addon_names[i], gfnname, NULL);
187 	/* and try to get its version */
188 	pkgver1 = get_addon_version(syspath, NULL);
189 	v1 = (pkgver1 != NULL)? dot_atof(pkgver1) : 0;
190 	if (verbose) {
191 	    pprintf(prn, " try '%s'\n", syspath);
192 	    if (v1 > 0) {
193 		pprintf(prn, "  found version %s\n", pkgver1);
194 	    } else {
195 		pputs(prn, "  not found\n");
196 	    }
197 	}
198 	/* build expected "userspace" path for the addon */
199 	get_user_path(usrpath, addon_names[i], gfnname);
200 	/* and try to get its version */
201 	pkgver2 = get_addon_version(usrpath, NULL);
202 	v2 = (pkgver2 != NULL)? dot_atof(pkgver2) : 0;
203 	if (verbose) {
204 	    pprintf(prn, " try '%s'\n", usrpath);
205 	    if (v2 > 0) {
206 		pprintf(prn, "  found version %s\n", pkgver2);
207 	    } else {
208 		pputs(prn, "  not found\n");
209 	    }
210 	}
211 
212 	if (v1 >= v2) {
213 	    /* system version is at least as new (or the only one) */
214 	    fprintf(fp, "%s %s %s\n", addon_names[i], pkgver1, syspath);
215 	} else if (v2 > 0) {
216 	    /* user version is newer (or the only one) */
217 	    fprintf(fp, "%s %s %s\n", addon_names[i], pkgver2, usrpath);
218 	}
219 	if (verbose && (v1 > 0 || v2 > 0)) {
220 	    pprintf(prn, " indexed version %s\n", v1 > v2 ?
221 		    pkgver1 : pkgver2);
222 	}
223 	free(pkgver1);
224 	free(pkgver2);
225     }
226 
227     fclose(fp);
228     g_free(idxname);
229 
230     return 0;
231 }
232 
233 /* Determine whether gretl has been updated since it was
234    last run. We do this by comparing the build_date string
235    in the program itself with that previously saved in the
236    gretl config file.
237 */
238 
gretl_is_updated(const char * prev_build)239 int gretl_is_updated (const char *prev_build)
240 {
241     int b_curr, b_prev;
242     int y, m, d;
243 
244     sscanf(BUILD_DATE, "%d-%d-%d", &y, &m, &d);
245     b_curr = 10000*y + 100*m + d;
246 
247     sscanf(prev_build, "%d-%d-%d", &y, &m, &d);
248     b_prev = 10000*y + 100*m + d;
249 
250     return b_curr > b_prev;
251 }
252 
253 /* Get the full path to the .gfn file for a given
254    addon. We first try for this via the simple plain
255    text index file addons.idx. If that's not found
256    we construct the index from scratch.
257 */
258 
gretl_addon_get_path(const char * addon)259 char *gretl_addon_get_path (const char *addon)
260 {
261     gchar *idxname = gretl_make_dotpath("addons.idx");
262     FILE *fp = gretl_fopen(idxname, "rb");
263     char *ret = NULL;
264 
265     if (fp == NULL) {
266 	update_addons_index(NULL);
267 	fp = gretl_fopen(idxname, "rb");
268     }
269 
270     if (fp != NULL) {
271 	char *s, line[512];
272 	int nsp, n = strlen(addon);
273 	int got = 0;
274 
275 	while (fgets(line, sizeof line, fp) && !got) {
276 	    if (!strncmp(addon, line, n)) {
277 		s = line;
278 		nsp = 0;
279 		while (*s && !got) {
280 		    if (*s == ' ') nsp++;
281 		    if (nsp == 2) {
282 			ret = gretl_strdup(s + 1);
283 			g_strchomp(ret);
284 			got = 1;
285 		    }
286 		    s++;
287 		}
288 	    }
289 	}
290 	fclose(fp);
291     } else {
292 	fprintf(stderr, "failed to read addons.idx\n");
293     }
294 
295     g_free(idxname);
296 
297     return ret;
298 }
299 
300 /* Retrieve the path to an addon's "examples" sub-dir.
301    Note that it's not required that every addon has
302    such (though maybe it should be?).
303 */
304 
get_addon_examples_dir(const char * addon)305 char *get_addon_examples_dir (const char *addon)
306 {
307     char epath[MAXLEN];
308     char *s, *path = gretl_addon_get_path(addon);
309     char *ret = NULL;
310 
311     if (path != NULL) {
312 	s = strrslash(path);
313 	if (s != NULL) {
314 	    *s = '\0';
315 	}
316 	gretl_build_path(epath, path, "examples", NULL);
317 	if (g_file_test(epath, G_FILE_TEST_IS_DIR)) {
318 	    ret = gretl_strdup(epath);
319 	}
320 	free(path);
321     }
322 
323     return ret;
324 }
325 
326 /* Retrieve the path to an addon's PDF documentation,
327    which is required of every addon. We accept the
328    @addon argument with or without the ".pdf" suffix.
329 */
330 
get_addon_pdf_path(const char * addon)331 char *get_addon_pdf_path (const char *addon)
332 {
333     char *s, *path = NULL;
334     char *ret = NULL;
335 
336     if (has_suffix(addon, ".pdf")) {
337 	/* strip off the suffix */
338 	gchar *tmp = g_strndup(addon, strlen(addon) - 4);
339 
340 	path = gretl_addon_get_path(tmp);
341 	g_free(tmp);
342     } else {
343 	path = gretl_addon_get_path(addon);
344     }
345 
346     if (path != NULL && has_suffix(path, ".gfn")) {
347 	/* should be the case */
348 	s = strrchr(path, '.');
349 	*s = '\0';
350 	strcpy(s, ".pdf");
351     }
352 
353     if (path != NULL && gretl_stat(path, NULL) == 0) {
354 	/* success */
355 	ret = path;
356 	path = NULL;
357     }
358 
359     free(path);
360 
361     return ret;
362 }
363