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