1 /*--------------------------------------------------------------------
2 *
3 * Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4 * See LICENSE.TXT file for copying and redistribution conditions.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; version 3 or 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 Lesser General Public License for more details.
14 *
15 * Contact info: www.generic-mapping-tools.org
16 *--------------------------------------------------------------------*/
17 /* Program: gmt_remote.c
18 * Purpose: routines involved in handling remote files and tiles
19 *
20 * Author: Paul Wessel
21 * Date: 15-Sept-2017
22 */
23
24 #include "gmt_dev.h"
25 #include "gmt_internals.h"
26 #include <curl/curl.h>
27 #ifdef WIN32
28 #include <sys/utime.h>
29 #endif
30
31 #define GMT_HASH_INDEX 0
32 #define GMT_INFO_INDEX 1
33
34 /* Copy a file from the GMT auto-download directory or the Internet. We recognize
35 * different types and names of files.
36 * 1. There are data sets of use to all GMT users, such as global relief:
37 * @earth_relief_<res>.grd Various global relief grids
38 * We may add more data later but this is our start.
39 * 2. Data sets only used to run an example or a test script
40 * and these are all called @*, i.e., a '@' is pre-pended to the name.
41 * They live in a cache subdirectory under the GMT_DATA_SERVER
42 * and will be placed in a cache directory in the users ~/.gmt directory.
43 * 3. Generic URLs starting with http:, https:, or ftp: These will be
44 * downloaded to the cache directory.
45 * If auto-download is enabled and a requested input file matches these
46 * names and not found by normal means then we download the file to the
47 * user-directory. All places that open files (GMT_Read_Data) will do
48 * this check by first calling gmt_download_file_if_not_found.
49 */
50
51 /* Need global variables for this black magic. Here is the problem:
52 * When a user accesses a large remote file and there is a power-outage
53 * or the user types Ctrl-C, the remote file is only partially copied
54 * over and is useless. In those cases we should make sure we delete
55 * the incomplete file before killing ourselves. Below is the implementation
56 * for Linux/macOS that handles these cases. The actual CURL calls
57 * are bracketed by gmtremote_turn_on_ctrl_C_check and gmtremote_turn_off_ctrl_C_check which
58 * temporarily activates or deactivates our signal action on Ctrl-C.
59 * P. Wessel, June 30, 2019.
60 */
61
62 #if !(defined(WIN32) || defined(NO_SIGHANDLER))
63 #define GMT_CATCH_CTRL_C
64 #include <signal.h>
65 struct sigaction cleanup_action, default_action;
66 char *file_to_delete_if_ctrl_C;
67 #endif
68
69 struct LOCFILE_FP {
70 char *file; /* Pointer to file name */
71 FILE *fp; /* Open file pointer */
72 };
73
gmtremote_delete_file_then_exit(int sig_no)74 GMT_LOCAL void gmtremote_delete_file_then_exit (int sig_no) {
75 /* If we catch a CTRL-C during CURL download we must assume file is corrupted and remove it before exiting */
76 gmt_M_unused (sig_no);
77 #ifdef GMT_CATCH_CTRL_C
78 #ifdef DEBUG
79 fprintf (stderr, "Emergency removal of file %s due to Ctrl-C action\n", file_to_delete_if_ctrl_C);
80 #endif
81 remove (file_to_delete_if_ctrl_C); /* Remove if we can, ignore any returns */
82 sigaction (SIGINT, &default_action, NULL); /* Reset the default action */
83 kill (0, SIGINT); /* Perform the final Ctrl-C action and die */
84 #endif
85 }
86
gmtremote_turn_on_ctrl_C_check(char * file)87 GMT_LOCAL void gmtremote_turn_on_ctrl_C_check (char *file) {
88 #ifdef GMT_CATCH_CTRL_C
89 file_to_delete_if_ctrl_C = file; /* File to delete if CTRL-C is caught */
90 gmt_M_memset (&cleanup_action, 1, struct sigaction); /* Initialize the structure to NULL */
91 cleanup_action.sa_handler = &gmtremote_delete_file_then_exit; /* Set function we should call if CTRL-C is caught */
92 sigaction(SIGINT, &cleanup_action, &default_action); /* Activate the alternative signal checking */
93 #else
94 gmt_M_unused (file);
95 #endif
96 }
97
gmtremote_turn_off_ctrl_C_check()98 GMT_LOCAL void gmtremote_turn_off_ctrl_C_check () {
99 #ifdef GMT_CATCH_CTRL_C
100 file_to_delete_if_ctrl_C = NULL; /* Remove trace of any file name */
101 sigaction (SIGINT, &default_action, NULL); /* Reset default signal action */
102 #endif
103 }
104
105 struct FtpFile { /* Needed for argument to libcurl */
106 const char *filename; /* Name of file to write */
107 FILE *fp; /* File pointer to said file */
108 };
109
gmtremote_throw_away(void * ptr,size_t size,size_t nmemb,void * data)110 GMT_LOCAL size_t gmtremote_throw_away (void *ptr, size_t size, size_t nmemb, void *data) {
111 gmt_M_unused (ptr);
112 gmt_M_unused (data);
113 /* We are not interested in the headers itself,
114 so we only return the file size we would have saved ... */
115 return (size_t)(size * nmemb);
116 }
117
gmtremote_fwrite_callback(void * buffer,size_t size,size_t nmemb,void * stream)118 GMT_LOCAL size_t gmtremote_fwrite_callback (void *buffer, size_t size, size_t nmemb, void *stream) {
119 struct FtpFile *out = (struct FtpFile *)stream;
120 if (out == NULL) return GMT_NOERROR; /* This cannot happen but Coverity fusses */
121 if (!out->fp) { /* Open file for writing */
122 out->fp = fopen (out->filename, "wb");
123 if (!out->fp)
124 return -1; /* failure, can't open file to write */
125 }
126 return fwrite (buffer, size, nmemb, out->fp);
127 }
128
gmtremote_compare_names(const void * item_1,const void * item_2)129 GMT_LOCAL int gmtremote_compare_names (const void *item_1, const void *item_2) {
130 /* Compare function used to sort the GMT_DATA_INFO array of structures into alphabetical order */
131 const char *name_1 = ((struct GMT_DATA_INFO *)item_1)->file;
132 const char *name_2 = ((struct GMT_DATA_INFO *)item_2)->file;
133
134 return (strcmp (name_1, name_2));
135 }
136
gmtremote_parse_version(char * line)137 GMT_LOCAL int gmtremote_parse_version (char *line) {
138 /* Parse a line like "# 6.1.0 or later GMT version required" and we will make no
139 * assumptions about how much space before the version. */
140 int k = 1, start, major, minor, release;
141 char text[GMT_LEN64] = {""};
142 if (line[0] != '#') return 1; /* Not a comment record! */
143 strncpy (text, line, GMT_LEN64-1);
144 while (isspace (text[k])) k++; /* Skip until we get to the version */
145 start = k;
146 while (isdigit(text[k]) || text[k] == '.') k++; /* Wind to end of version */
147 text[k] = '\0'; /* Chop off the rest */
148 if (sscanf (&text[start], "%d.%d.%d", &major, &minor, &release) != 3) return 1;
149 if (major > GMT_MAJOR_VERSION) return 2; /* Definitively too old */
150 if (major < GMT_MAJOR_VERSION) return 0; /* Should be fine */
151 if (minor > GMT_MINOR_VERSION) return 2; /* Definitively too old */
152 if (minor < GMT_MINOR_VERSION) return 0; /* Should be fine */
153 if (release > GMT_RELEASE_VERSION) return 2; /* Definitively too old */
154 return GMT_NOERROR;
155 }
156
gmtremote_remove_item(struct GMTAPI_CTRL * API,char * path,bool directory)157 GMT_LOCAL int gmtremote_remove_item (struct GMTAPI_CTRL *API, char *path, bool directory) {
158 int error = GMT_NOERROR;
159 if (directory) { /* Delete populated directories via an operating system remove call */
160 char del_cmd[PATH_MAX] = {""};
161 #ifdef _WIN32
162 char *t = gmt_strrep (path, "/", "\\"); /* DOS rmdir needs paths with back-slashes */
163 strcpy (del_cmd, "rmdir /s /q ");
164 strncat (del_cmd, t, PATH_MAX-1);
165 gmt_M_str_free (t);
166 #else
167 sprintf (del_cmd, "rm -rf %s", path);
168 #endif
169 if ((error = system (del_cmd))) {
170 GMT_Report (API, GMT_MSG_ERROR, "Failed to remove %s [error = %d]\n", path, error);
171 error = GMT_RUNTIME_ERROR;
172 }
173 }
174 else /* Just delete a single file */
175 gmt_remove_file (API->GMT, path);
176 return error;
177 }
178
gmtremote_data_load(struct GMTAPI_CTRL * API,int * n)179 GMT_LOCAL struct GMT_DATA_INFO *gmtremote_data_load (struct GMTAPI_CTRL *API, int *n) {
180 /* Read contents of the info file into an array of structs */
181 int k = 0, nr;
182 FILE *fp = NULL;
183 struct GMT_DATA_INFO *I = NULL;
184 char unit, line[GMT_LEN512] = {""}, file[PATH_MAX] = {""}, *c = NULL;
185 struct GMT_CTRL *GMT = API->GMT;
186
187 snprintf (file, PATH_MAX, "%s/server/%s", GMT->session.USERDIR, GMT_INFO_SERVER_FILE);
188
189 GMT_Report (API, GMT_MSG_DEBUG, "Load contents from %s\n", file);
190 *n = 0;
191 if ((fp = fopen (file, "r")) == NULL) {
192 GMT_Report (API, GMT_MSG_ERROR, "Unable to open file %s\n", file);
193 return NULL;
194 }
195 if (fgets (line, GMT_LEN256, fp) == NULL) { /* Try to get first record */
196 fclose (fp);
197 GMT_Report (API, GMT_MSG_ERROR, "Read error first record in file %s\n", file);
198 GMT_Report (API, GMT_MSG_ERROR, "Deleting %s so it can get regenerated - please try again\n", file);
199 gmt_remove_file (GMT, file);
200 return NULL;
201 }
202 *n = atoi (line); /* Number of non-commented records to follow */
203 if (*n <= 0 || *n > GMT_BIG_CHUNK) { /* Probably not a good value */
204 fclose (fp);
205 GMT_Report (API, GMT_MSG_ERROR, "Bad record counter in file %s\n", file);
206 GMT_Report (API, GMT_MSG_ERROR, "Deleting %s so it can get regenerated - please try again\n", file);
207 gmt_remove_file (GMT, file);
208 return NULL;
209 }
210 if (fgets (line, GMT_LEN256, fp) == NULL) { /* Try to get second record */
211 fclose (fp);
212 GMT_Report (API, GMT_MSG_ERROR, "Read error second record in file %s\n", file);
213 return NULL;
214 }
215 if ((k = gmtremote_parse_version (line))) {
216 fclose (fp);
217 if (k == 2)
218 GMT_Report (API, GMT_MSG_NOTICE, "Your GMT version too old to use the remote data mechanism - please upgrade to %s or later\n", line);
219 else
220 GMT_Report (API, GMT_MSG_ERROR, "Unable to parse \"%s\" to extract GMT version\n", line);
221 return NULL;
222 }
223 if ((I = gmt_M_memory (GMT, NULL, *n, struct GMT_DATA_INFO)) == NULL) {
224 fclose (fp);
225 GMT_Report (API, GMT_MSG_ERROR, "Unable to allocated %d GMT_DATA_INFO structures!\n", *n);
226 return NULL;
227 }
228
229 while (fgets (line, GMT_LEN512, fp) != NULL) {
230 if (line[0] == '#') continue; /* Skip any comments */
231 if ((nr = sscanf (line, "%s %s %s %c %lg %lg %s %lg %s %s %s %s %[^\n]", I[k].dir, I[k].file, I[k].inc, &I[k].reg, &I[k].scale, &I[k].offset, I[k].size, &I[k].tile_size, I[k].date, I[k].coverage, I[k].filler, I[k].CPT, I[k].remark)) != 13) {
232 GMT_Report (API, GMT_MSG_WARNING, "File %s should have 13 fields but only %d read for record %d - download error???\n", file, nr, k);
233 gmt_M_free (GMT, I);
234 fclose (fp);
235 return NULL;
236 }
237 /* Extract some useful bits to have in separate variables */
238 sscanf (I[k].inc, "%lg%c", &I[k].d_inc, &unit);
239 if (unit == 'm') I[k].d_inc *= GMT_MIN2DEG; /* E.g., 30m becomes 0.5 */
240 else if (unit == 's') I[k].d_inc *= GMT_SEC2DEG; /* E.g., 30s becomes 0.00833333333333 */
241 if ((c = strchr (I[k].file, '.'))) /* Get the file extension */
242 strcpy (I[k].ext, c);
243 if (I[k].tile_size > 0.0) { /* A tiled dataset */
244 size_t len = strlen (I[k].file);
245 strncpy (I[k].tag, I[k].file, len-1); /* Remote trailing slash */
246 }
247 k++;
248 }
249 fclose (fp);
250
251 if (k != *n) {
252 GMT_Report (API, GMT_MSG_WARNING, "File %s said it has %d records but only found %d - download error???\n", file, *n, k);
253 GMT_Report (API, GMT_MSG_WARNING, "File %s should be deleted. Please try again\n", file);
254 *n = 0; /* Flag that excrement hit the fan */
255 }
256 /* Soft alphabetically on file names */
257 qsort (I, *n, sizeof (struct GMT_DATA_INFO), gmtremote_compare_names);
258 for (k = 0; k < *n; k++) I[k].id = k; /* Give running number as ID in the sorted array */
259
260 if (GMT->current.io.new_data_list) { /* Take this opportunity to delete datasets that are past their expiration date */
261 time_t mod_time;
262 struct tm *UTC = NULL;
263 struct stat buf;
264 int year, month, day, kyear, kmonth, kday;
265 size_t L;
266
267 GMT->current.io.new_data_list = false; /* We only do this once after a gmt_data_server.txt update */
268 if (GMT->session.USERDIR == NULL) goto out_of_here; /* Cannot have server data if no user directory is set */
269 if (access (GMT->session.USERDIR, R_OK)) goto out_of_here; /* Set, but have not made a user directory yet, so cannot have any remote data yet either */
270
271 for (k = 0; k < *n; k++) { /* Check the release date of each data set that has been downloaded against the local file date */
272 if (sscanf (I[k].date, "%d-%d-%d", &kyear, &kmonth, &kday) != 3) continue; /* Maybe malformed datestring or on purpose to never check */
273 snprintf (file, PATH_MAX, "%s/%s%s", GMT->session.USERDIR, I[k].dir, I[k].file); /* Local path, may end in slash if a tile directory*/
274 if ((L = strlen (file) - 1) && file[L] == '/') file[L] = '\0'; /* Chop off trailing / that indicates directory of tiles */
275 if (access (file, R_OK)) continue; /* No such file or directory yet */
276 /* Here we have a local copy of this remote file or directory - we examine its creation date */
277 if (stat (file, &buf)) {
278 GMT_Report (API, GMT_MSG_WARNING, "Unable to get information about %s - skip\n", file);
279 continue;
280 }
281 /* Get its modification (creation) time */
282 #ifdef __APPLE__
283 mod_time = buf.st_mtimespec.tv_sec; /* Apple even has tv_nsec for nano-seconds... */
284 #else
285 mod_time = buf.st_mtime;
286 #endif
287 /* Extract the year, month, day integers */
288 UTC = gmtime (&mod_time);
289 year = UTC->tm_year + 1900; /* Yep, how stupid is that, Y2K lovers. I guess 2030 might overflow a 32-bit int... */
290 month = UTC->tm_mon + 1; /* Make it 1-12 since it is 0-11 */
291 day = UTC->tm_mday; /* Yep, lets start at 1 for days and 0 for months, makes sense */
292 if (kyear < year) continue; /* The origin year is older than our file so no need to check further */
293 if (kyear == year) { /* Our file and the server file is both from the same year */
294 if (kmonth < month) continue; /* The origin month is older than our copy so no need to check further */
295 if (kmonth == month) { /* Same year, same month, we are so close! */
296 if (kday < day) continue; /* The origin day is older than our copy so no need to check further */
297 }
298 }
299 /* If we get here we need to remove the outdated file or directory so we may download the latest on next try */
300 if (gmtremote_remove_item (API, file, I[k].tile_size > 0.0)) {
301 GMT_Report (GMT->parent, GMT_MSG_WARNING, "Unable to remove %s \n", file);
302 }
303 }
304 }
305
306 out_of_here:
307 return (I);
308 };
309
gmtremote_compare_key(const void * item_1,const void * item_2)310 GMT_LOCAL int gmtremote_compare_key (const void *item_1, const void *item_2) {
311 /* We are passing string item_1 without any leading @ */
312 const char *name_1 = (char *)item_1;
313 const char *name_2 = ((struct GMT_DATA_INFO *)item_2)->file;
314 size_t len = strlen (name_1);
315
316 return (strncmp (name_1, name_2, len));
317 }
318
gmtremote_wind_to_file(const char * file)319 int gmtremote_wind_to_file (const char *file) {
320 int k = strlen (file) - 2; /* This jumps past any trailing / for tiles */
321 while (k >= 0 && file[k] != '/') k--;
322 return (k+1);
323 }
324
gmt_remote_no_resolution_given(struct GMTAPI_CTRL * API,const char * rfile,int * registration)325 int gmt_remote_no_resolution_given (struct GMTAPI_CTRL *API, const char *rfile, int *registration) {
326 /* Return first entry to a list of different resolutions for the
327 * same data set. For instance, if file is "earth_relief" then we
328 * return the ID to the first one listed. */
329 char *c = NULL, *p = NULL, dir[GMT_LEN64] = {""}, file[GMT_LEN128] = {""};
330 int ID = GMT_NOTSET, reg = GMT_NOTSET;
331 size_t L;
332
333 if (rfile == NULL || rfile[0] == '\0') return GMT_NOTSET; /* No file name given */
334 if (rfile[0] != '@') return GMT_NOTSET; /* No remote file name given */
335 strcpy (file, &rfile[1]); /* Make a copy but skipping leading @ character */
336 if ((c = strchr (file, '+'))) c[0] = '\0'; /* Chop of modifiers such as in grdimage -I */
337 L = strlen (file);
338 if (!strncmp (&file[L-2], "_g", 2U)) { /* Want a gridline-registered version */
339 reg = GMT_GRID_NODE_REG;
340 file[L-2] = '\0';
341 }
342 else if (!strncmp (&file[L-2], "_p", 2U)) { /* Want a pixel-registered version */
343 reg = GMT_GRID_PIXEL_REG;
344 file[L-2] = '\0';
345 }
346 for (int k = 0; ID == GMT_NOTSET && k < API->n_remote_info; k++) {
347 strncpy (dir, API->remote_info[k].dir, strlen (API->remote_info[k].dir)-1); /* Make a copy without the trailing slash */
348 p = strrchr (dir, '/'); /* Start of final subdirectory */
349 p++; /* Skip past the slash */
350 if (!strcmp (p, file)) ID = k;
351 }
352 if (ID != GMT_NOTSET && registration)
353 *registration = reg; /* Pass back desired [or any] registration */
354
355 return (ID); /* Start of the family or -1 */
356 }
357
gmt_remote_resolutions(struct GMTAPI_CTRL * API,const char * rfile,unsigned int * n)358 struct GMT_RESOLUTION *gmt_remote_resolutions (struct GMTAPI_CTRL *API, const char *rfile, unsigned int *n) {
359 /* Return list of available resolutions and registrations for the specified data set family.
360 * For instance, if file is "earth_relief" then we return an array of structs
361 * with the resolution and registration from 01d (g&p) to 91s (g) .*/
362 char *c = NULL, *p = NULL, dir[GMT_LEN64] = {""}, file[GMT_LEN128] = {""};
363 static char *registration = "gp"; /* The two types of registrations */
364 int id = 0, reg = GMT_NOTSET;
365 size_t L, n_alloc = GMT_SMALL_CHUNK;
366 struct GMT_RESOLUTION *R = NULL;
367
368 if (rfile == NULL || rfile[0] == '\0') return NULL; /* No file name given */
369 if (rfile[0] != '@') return NULL; /* No remote file name given */
370 strcpy (file, &rfile[1]); /* Make a copy but skipping leading @ character */
371 if ((c = strchr (file, '+'))) c[0] = '\0'; /* Chop of modifiers such as in grdimage -I */
372 L = strlen (file);
373 if (!strncmp (&file[L-2], "_g", 2U)) { /* Want a gridline-registered version */
374 reg = GMT_GRID_NODE_REG;
375 file[L-2] = '\0';
376 }
377 else if (!strncmp (&file[L-2], "_p", 2U)) { /* Want a pixel-registered version */
378 reg = GMT_GRID_PIXEL_REG;
379 file[L-2] = '\0';
380 }
381 if ((R = gmt_M_memory (API->GMT, NULL, n_alloc, struct GMT_RESOLUTION)) == NULL)
382 return NULL; /* No memory */
383
384 for (int k = 0; k < API->n_remote_info; k++) {
385 strncpy (dir, API->remote_info[k].dir, strlen (API->remote_info[k].dir)-1); /* Make a copy without the trailing slash */
386 p = strrchr (dir, '/'); /* Start of final subdirectory */
387 p++; /* Skip past the slash */
388 if (!strcmp (p, file) && (reg == GMT_NOTSET || registration[reg] == API->remote_info[k].reg)) { /* Got one to keep */
389 R[id].resolution = urint (1.0 / API->remote_info[k].d_inc); /* Number of nodes per degree */
390 strncpy (R[id].inc, API->remote_info[k].inc, GMT_LEN8); /* Copy the formatted inc string */
391 R[id].reg = API->remote_info[k].reg; /* Copy the registration */
392 id++;
393 }
394 if (id == n_alloc) { /* Need more memory */
395 n_alloc += GMT_SMALL_CHUNK;
396 if ((R = gmt_M_memory (API->GMT, NULL, n_alloc, struct GMT_RESOLUTION)) == NULL)
397 return NULL; /* No memory */
398 }
399 }
400 if (id) { /* Did find some */
401 if ((R = gmt_M_memory (API->GMT, R, id, struct GMT_RESOLUTION)) == NULL)
402 return NULL; /* No memory */
403 *n = id;
404 }
405 else { /* No luck, probably filename typo */
406 gmt_M_free (API->GMT, R);
407 *n = 0;
408 }
409
410 return (R);
411 }
412
gmt_remote_dataset_id(struct GMTAPI_CTRL * API,const char * ifile)413 int gmt_remote_dataset_id (struct GMTAPI_CTRL *API, const char *ifile) {
414 /* Return the entry in the remote file table of file is found, else -1.
415 * Complications to consider before finding a match:
416 * Input file may or may not have leading @
417 * Input file may or may not have _g or _p for registration
418 * Input file may or many not have an extension
419 * Key file may be a tiled data set and thus ends with '/'
420 */
421 int pos = 0;
422 char file[PATH_MAX] = {""};
423 struct GMT_DATA_INFO *key = NULL;
424 if (ifile == NULL || ifile[0] == '\0') return GMT_NOTSET; /* No file name given */
425 if (ifile[0] == '@') pos = 1; /* Skip any leading remote flag */
426 /* Exclude leading directory for local saved versions of the file */
427 if (pos == 0) pos = gmtremote_wind_to_file (ifile); /* Skip any leading directories */
428 /* Must handle the use of srtm_relief vs earth_relief for the 01s and 03s data */
429 if (strncmp (&ifile[pos], "srtm_relief_0", 13U) == 0) /* Gave strm special name */
430 sprintf (file, "earth_%s", &ifile[pos+5]); /* Replace srtm with earth */
431 else /* Just copy as is from pos */
432 strcpy (file, &ifile[pos]);
433 key = bsearch (file, API->remote_info, API->n_remote_info, sizeof (struct GMT_DATA_INFO), gmtremote_compare_key);
434 if (key) { /* Make sure we actually got a real hit since file = "earth" will find a key starting with "earth****" */
435 char *ckey = strrchr (key->file, '.'); /* Find location of the start of the key file extension (or NULL if no extension) */
436 char *cfile = strrchr (file, '.'); /* Find location of the start of the input file extension (or NULL if no extension) */
437 size_t Lfile = (cfile) ? (size_t)(cfile - file) : strlen (file); /* Length of key file name without extension */
438 size_t Lkey = (ckey) ? (size_t)(ckey - key->file) : strlen (key->file); /* Length of key file name without extension */
439 if (ckey == NULL && Lkey > 1 && key->file[Lkey-1] == '/') Lkey--; /* Skip trailing dir flag */
440 if (Lkey > Lfile && Lkey > 2 && key->file[Lkey-2] == '_' && strchr ("gp", key->file[Lkey-1])) Lkey -= 2; /* Remove the length of _g or _p from Lkey */
441 if (Lfile != Lkey) /* Not an exact match (apart from trailing _p|g) */
442 key = NULL;
443 }
444 return ((key == NULL) ? GMT_NOTSET : key->id);
445 }
446
gmt_file_is_cache(struct GMTAPI_CTRL * API,const char * file)447 bool gmt_file_is_cache (struct GMTAPI_CTRL *API, const char *file) {
448 /* Returns true if a remote file is a cache file */
449 if (file == NULL || file[0] == '\0') return false; /* Nothing given */
450 if (gmt_M_file_is_memory (file)) return false; /* Memory files are not remote */
451 if (file[0] != '@') return false; /* Cannot be a remote file, let alone cache */
452 if (gmt_remote_dataset_id (API, file) != GMT_NOTSET) return false; /* Found a remote dataset, but not cache */
453 return true;
454 }
455
gmt_set_unspecified_remote_registration(struct GMTAPI_CTRL * API,char ** file_ptr)456 void gmt_set_unspecified_remote_registration (struct GMTAPI_CTRL *API, char **file_ptr) {
457 /* If a remote file is missing _g or _p we find which one we should use and revise the file accordingly.
458 * There are a few different scenarios where this can happen:
459 * 1. Users of GMT <= 6.0.0 are used to say earth_relief_01m. These will now get p.
460 * 2. Users who do not care about registration. If so, they get p if available. */
461 char newfile[GMT_LEN256] = {""}, reg[2] = {'p', 'g'}, *file = NULL, *infile = NULL, *ext = NULL, *c = NULL;
462 int k_data, k;
463 if (file_ptr == NULL || (file = *file_ptr) == NULL || file[0] == '\0') return;
464 if (gmt_M_file_is_memory (file)) return; /* Not a remote file for sure */
465 if (file[0] != '@') return;
466 infile = strdup (file);
467 if ((c = strchr (infile, '+'))) /* Got modifiers, probably from grdimage or similar, chop off for now */
468 c[0] = '\0';
469 /* Deal with any extension the user may have added */
470 ext = gmt_chop_ext (infile);
471 /* If the remote file is found then there is nothing to do */
472 if ((k_data = gmt_remote_dataset_id (API, infile)) == GMT_NOTSET) goto clean_up;
473 if (strstr (file, "_p") || strstr (file, "_g")) goto clean_up; /* Already have the registration codes */
474 for (k = 0; k < 2; k++) {
475 /* First see if this _<reg> version exists of this dataset */
476 sprintf (newfile, "%s_%c", infile, reg[k]);
477 if ((k_data = gmt_remote_dataset_id (API, newfile)) != GMT_NOTSET) {
478 /* Found, replace given file name with this */
479 if (c) { /* Restore the modifiers */
480 c[0] = '+';
481 if (gmt_found_modifier (API->GMT, c, "os"))
482 GMT_Report (API, GMT_MSG_WARNING, "Cannot append +s<scl> and/or +o<offset> to the remote global grid %s - ignored\n", newfile);
483 else
484 strcat (newfile, c);
485 }
486 gmt_M_str_free (*file_ptr);
487 *file_ptr = strdup (newfile);
488 goto clean_up;
489 }
490 }
491 clean_up:
492 gmt_M_str_free (infile);
493 }
494
gmt_remote_no_extension(struct GMTAPI_CTRL * API,const char * file)495 int gmt_remote_no_extension (struct GMTAPI_CTRL *API, const char *file) {
496 int k_data = gmt_remote_dataset_id (API, file);
497 if (k_data == GMT_NOTSET) return GMT_NOTSET;
498 if (API->remote_info[k_data].ext[0] == '\0') return GMT_NOTSET; /* Tiled grid */
499 if (strstr (file, API->remote_info[k_data].ext)) return GMT_NOTSET; /* Already has extension */
500 return k_data; /* Missing its extension */
501 }
502
gmtremote_display_attribution(struct GMTAPI_CTRL * API,int key,const char * file,int tile)503 GMT_LOCAL void gmtremote_display_attribution (struct GMTAPI_CTRL *API, int key, const char *file, int tile) {
504 /* Display a notice regarding the source of this data set */
505 char *c = NULL, name[GMT_LEN128] = {""};
506 if (key != GMT_NOTSET && !API->server_announced && !strchr (file, ':')) { /* Server file has no http:// here */
507 if ((c = strrchr (API->GMT->session.DATASERVER, '/'))) /* Found last slash in http:// */
508 strcpy (name, ++c);
509 else /* Just in case */
510 strncpy (name, gmt_dataserver_url (API), GMT_LEN128-1);
511 if ((c = strchr (name, '.'))) c[0] = '\0'; /* Chop off stuff after the initial name */
512 gmt_str_toupper (name);
513 GMT_Report (API, GMT_MSG_NOTICE, "Remote data courtesy of GMT data server %s [%s]\n\n", API->GMT->session.DATASERVER, gmt_dataserver_url (API));
514 API->server_announced = true;
515 }
516 if (key == GMT_NOTSET) { /* A Cache file */
517 if (strchr (file, ':')) /* Generic URL */
518 GMT_Report (API, GMT_MSG_INFORMATION, " -> Download URL file: %s\n", file);
519 else
520 GMT_Report (API, GMT_MSG_INFORMATION, " -> Download cache file: %s\n", file);
521 }
522 else { /* Remote data sets */
523 if (!API->remote_info[key].used) {
524 GMT_Report (API, GMT_MSG_NOTICE, "%s.\n", API->remote_info[key].remark);
525 API->remote_info[key].used = true;
526 }
527 if (tile) { /* Temporarily remote the trailing slash when printing the dataset name */
528 c = strrchr (API->remote_info[key].file, '/');
529 c[0] = '\0';
530 strncpy (name, &file[1], 7U); name[7] = '\0';
531 GMT_Report (API, GMT_MSG_NOTICE, " -> Download %lgx%lg degree grid tile (%s): %s\n",
532 API->remote_info[key].tile_size, API->remote_info[key].tile_size, API->remote_info[key].file, name);
533 c[0] = '/';
534 }
535 else
536 GMT_Report (API, GMT_MSG_NOTICE, " -> Download grid file [%s]: %s\n", API->remote_info[key].size, API->remote_info[key].file);
537 }
538 }
539
gmtremote_find_and_give_data_attribution(struct GMTAPI_CTRL * API,const char * file)540 GMT_LOCAL int gmtremote_find_and_give_data_attribution (struct GMTAPI_CTRL *API, const char *file) {
541 /* Print attribution when the @remotefile is downloaded for the first time */
542 char *c = NULL;
543 int match, tile = 0;
544
545 if (file == NULL || file[0] == '\0') return GMT_NOTSET; /* No file name given */
546 if ((c = strstr (file, ".grd")) || (c = strstr (file, ".tif"))) /* Ignore extension in comparison */
547 c[0] = '\0';
548 if ((match = gmt_remote_dataset_id (API, file)) == GMT_NOTSET) { /* Check if it is a tile */
549 if ((match = gmt_file_is_a_tile (API, file, GMT_LOCAL_DIR)) != GMT_NOTSET) /* Got a remote tile */
550 tile = 1;
551 }
552 gmtremote_display_attribution (API, match, file, tile);
553 if (c) c[0] = '.'; /* Restore extension */
554 return (match);
555 }
556
gmtremote_lockfile(struct GMT_CTRL * GMT,char * file)557 GMT_LOCAL char *gmtremote_lockfile (struct GMT_CTRL *GMT, char *file) {
558 /* Create a dummy file in temp with extension .download and use as a lock file */
559 char *c = strrchr (file, '/');
560 char Lfile[PATH_MAX] = {""};
561 if (c) /* Found the last slash, skip it */
562 c++;
563 else /* No path, just point to file */
564 c = file;
565 if (c[0] == '@') c++; /* Skip any leading @ sign */
566 sprintf (Lfile, "%s/%s.download", GMT->parent->tmp_dir, c);
567 return (strdup (Lfile));
568 }
569
gmtremote_skip_large_files(struct GMT_CTRL * GMT,char * URL,size_t limit)570 GMT_LOCAL size_t gmtremote_skip_large_files (struct GMT_CTRL *GMT, char * URL, size_t limit) {
571 /* Get the remote file's size and if too large we refuse to download */
572 CURL *curl = NULL;
573 CURLcode res;
574 double filesize = 0.0;
575 size_t action = 0;
576
577 if (limit == 0) return 0; /* Download regardless of size */
578 curl_global_init (CURL_GLOBAL_DEFAULT);
579
580 if ((curl = curl_easy_init ())) {
581 curl_easy_setopt (curl, CURLOPT_URL, URL);
582 /* Do not download the file */
583 curl_easy_setopt (curl, CURLOPT_NOBODY, 1L);
584 /* Tell libcurl to not verify the peer */
585 curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
586 /* Tell libcurl to fail on 4xx responses (e.g. 404) */
587 curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1L);
588 /* Tell libcurl to follow 30x redirects */
589 curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
590 /* No header output: TODO 14.1 http-style HEAD output for ftp */
591 curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, gmtremote_throw_away);
592 curl_easy_setopt (curl, CURLOPT_HEADER, 0L);
593 /* Complete connection within 10 seconds */
594 curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, GMT_CONNECT_TIME_OUT);
595
596 res = curl_easy_perform (curl);
597
598 if ((res = curl_easy_perform (curl)) == CURLE_OK) {
599 res = curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &filesize);
600 if ((res == CURLE_OK) && (filesize > 0.0)) { /* Got the size */
601 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Remote file %s: Size is %0.0f bytes\n", URL, filesize);
602 action = (filesize < (double)limit) ? 0 : (size_t)filesize;
603 }
604 }
605 else /* We failed */
606 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Remote file %s: Curl returned error %d\n", URL, res);
607
608 /* always cleanup */
609 curl_easy_cleanup (curl);
610 }
611
612 curl_global_cleanup ();
613
614 return action;
615 }
616
gmtremote_setup_curl(struct GMTAPI_CTRL * API,char * url,char * local_file,struct FtpFile * urlfile,unsigned int time_out)617 CURL * gmtremote_setup_curl (struct GMTAPI_CTRL *API, char *url, char *local_file, struct FtpFile *urlfile, unsigned int time_out) {
618 /* Single function that sets up an impending CURL operation */
619 CURL *Curl = NULL;
620 if ((Curl = curl_easy_init ()) == NULL) {
621 GMT_Report (API, GMT_MSG_ERROR, "Failed to initiate curl - cannot obtain %s\n", url);
622 return NULL;
623 }
624 if (curl_easy_setopt (Curl, CURLOPT_SSL_VERIFYPEER, 0L)) { /* Tell libcurl to not verify the peer */
625 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to not verify the peer\n");
626 return NULL;
627 }
628 if (curl_easy_setopt (Curl, CURLOPT_FOLLOWLOCATION, 1L)) { /* Tell libcurl to follow 30x redirects */
629 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to follow redirects\n");
630 return NULL;
631 }
632 if (curl_easy_setopt (Curl, CURLOPT_FAILONERROR, 1L)) { /* Tell libcurl to fail on 4xx responses (e.g. 404) */
633 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to fail for 4xx responses\n");
634 return NULL;
635 }
636 if (curl_easy_setopt (Curl, CURLOPT_URL, url)) { /* Set the URL to copy */
637 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to read from %s\n", url);
638 return NULL;
639 }
640 if (curl_easy_setopt (Curl, CURLOPT_CONNECTTIMEOUT, GMT_CONNECT_TIME_OUT)) { /* Set connection timeout to 10s [300] */
641 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to limit connection timeout to %lds\n", GMT_CONNECT_TIME_OUT);
642 return NULL;
643 }
644 if (time_out) { /* Set a timeout limit */
645 if (curl_easy_setopt (Curl, CURLOPT_TIMEOUT, time_out)) {
646 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to time out after %d seconds\n", time_out);
647 return NULL;
648 }
649 }
650 urlfile->filename = local_file; /* Set pointer to local filename */
651 /* Define our callback to get called when there's data to be written */
652 if (curl_easy_setopt (Curl, CURLOPT_WRITEFUNCTION, gmtremote_fwrite_callback)) {
653 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl output callback function\n");
654 return NULL;
655 }
656 /* Set a pointer to our struct that will be passed to the callback function */
657 if (curl_easy_setopt (Curl, CURLOPT_WRITEDATA, urlfile)) {
658 GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to write to %s\n", local_file);
659 return NULL;
660 }
661
662 return Curl; /* Happily return the Curl pointer */
663 }
664
gmtremote_lock_on(struct GMT_CTRL * GMT,char * file)665 struct LOCFILE_FP *gmtremote_lock_on (struct GMT_CTRL *GMT, char *file) {
666 /* Creates filename for lock and activates the lock */
667 struct LOCFILE_FP *P = gmt_M_memory (GMT, NULL, 1, struct LOCFILE_FP);
668 P->file = gmtremote_lockfile (GMT, file);
669 if ((P->fp = fopen (P->file, "w")) == NULL) {
670 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Failed to create lock file %s\n", P->file);
671 gmt_M_str_free (P->file);
672 gmt_M_free (GMT, P);
673 return NULL;
674 }
675 gmtlib_file_lock (GMT, fileno(P->fp)); /* Attempt exclusive lock */
676 return P;
677 }
678
gmtremote_lock_off(struct GMT_CTRL * GMT,struct LOCFILE_FP ** P)679 void gmtremote_lock_off (struct GMT_CTRL *GMT, struct LOCFILE_FP **P) {
680 /* Deactivates the lock on the file */
681 gmtlib_file_unlock (GMT, fileno((*P)->fp));
682 fclose ((*P)->fp);
683 gmt_remove_file (GMT, (*P)->file);
684 gmt_M_str_free ((*P)->file);
685 gmt_M_free (GMT, *P);
686 }
687
688 /* Deal with hash values of cache/data files */
689
gmtremote_get_url(struct GMT_CTRL * GMT,char * url,char * file,char * orig,unsigned int index,bool do_lock)690 GMT_LOCAL int gmtremote_get_url (struct GMT_CTRL *GMT, char *url, char *file, char *orig, unsigned int index, bool do_lock) {
691 bool turn_ctrl_C_off = false;
692 int curl_err = 0, error = GMT_NOERROR;
693 long time_spent;
694 CURL *Curl = NULL;
695 struct LOCFILE_FP *LF = NULL;
696 struct FtpFile urlfile = {NULL, NULL};
697 struct GMTAPI_CTRL *API = GMT->parent;
698 time_t begin, end;
699
700 if (GMT->current.setting.auto_download == GMT_NO_DOWNLOAD) { /* Not allowed to use remote copying */
701 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Remote download is currently deactivated\n");
702 return 1;
703 }
704 if (GMT->current.io.internet_error) return 1; /* Not able to use remote copying in this session */
705
706 /* Make a lock */
707 if (do_lock && (LF = gmtremote_lock_on (GMT, file)) == NULL)
708 return 1;
709
710 /* If file locking held us up as another process was downloading the same file,
711 * then that file should now be available. So we check again if it is before proceeding */
712
713 if (do_lock && !access (file, F_OK))
714 goto unlocking1; /* Yes it was, unlock and return no error */
715
716 /* Initialize the curl session */
717 if ((Curl = gmtremote_setup_curl (API, url, file, &urlfile, GMT_HASH_TIME_OUT)) == NULL)
718 goto unlocking1;
719
720 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Downloading file %s ...\n", url);
721 gmtremote_turn_on_ctrl_C_check (file); turn_ctrl_C_off = true;
722 begin = time (NULL);
723 if ((curl_err = curl_easy_perform (Curl))) { /* Failed, give error message */
724 end = time (NULL);
725 time_spent = (long)(end - begin);
726 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Unable to download file %s\n", url);
727 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Libcurl Error: %s\n", curl_easy_strerror (curl_err));
728 if (urlfile.fp != NULL) {
729 fclose (urlfile.fp);
730 urlfile.fp = NULL;
731 }
732 if (time_spent >= GMT_HASH_TIME_OUT) { /* Ten seconds is too long time - server down? */
733 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "GMT data server may be down - delay checking hash file for 24 hours\n");
734 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "You can turn remote file download off by setting GMT_DATA_UPDATE_INTERVAL to \"off\"\n");
735 if (orig && !access (orig, F_OK)) { /* Refresh modification time of original hash file */
736 #ifdef WIN32
737 _utime (orig, NULL);
738 #else
739 utimes (orig, NULL);
740 #endif
741 GMT->current.io.refreshed[index] = GMT->current.io.internet_error = true;
742 }
743 }
744 error = 1; goto unlocking1;
745 }
746 curl_easy_cleanup (Curl);
747 if (urlfile.fp) /* close the local file */
748 fclose (urlfile.fp);
749
750 unlocking1:
751
752 /* Remove lock file after successful download */
753 if (do_lock) gmtremote_lock_off (GMT, &LF);
754
755 if (turn_ctrl_C_off) gmtremote_turn_off_ctrl_C_check ();
756
757 return error;
758 }
759
gmtremote_hash_load(struct GMT_CTRL * GMT,char * file,int * n)760 GMT_LOCAL struct GMT_DATA_HASH *gmtremote_hash_load (struct GMT_CTRL *GMT, char *file, int *n) {
761 /* Read contents of the hash file into an array of structs */
762 int k;
763 FILE *fp = NULL;
764 struct GMT_DATA_HASH *L = NULL;
765 char line[GMT_LEN256] = {""};
766
767 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Load contents from %s\n", file);
768 *n = 0;
769 if ((fp = fopen (file, "r")) == NULL) return NULL;
770 if (fgets (line, GMT_LEN256, fp) == NULL) { /* Try to get first record */
771 fclose (fp);
772 return NULL;
773 }
774 *n = atoi (line); /* Number of records to follow */
775 if (*n <= 0 || *n > GMT_BIG_CHUNK) { /* Probably not a good value */
776 fclose (fp);
777 return NULL;
778 }
779 L = gmt_M_memory (GMT, NULL, *n, struct GMT_DATA_HASH);
780 for (k = 0; k < *n; k++) {
781 if (fgets (line, GMT_LEN256, fp) == NULL) break; /* Next record */
782 sscanf (line, "%s %s %" PRIuS, L[k].name, L[k].hash, &L[k].size);
783 }
784 fclose (fp);
785 if (k != *n) {
786 GMT_Report (GMT->parent, GMT_MSG_WARNING, "File %s said it has %d records but only found %d - download error???\n", file, *n, k);
787 GMT_Report (GMT->parent, GMT_MSG_WARNING, "File %s will be deleted. Please try again\n", file);
788 *n = 0; /* Flag that excrement hit the fan */
789 }
790 return (L);
791 };
792
gmtremote_refresh(struct GMTAPI_CTRL * API,unsigned int index)793 GMT_LOCAL int gmtremote_refresh (struct GMTAPI_CTRL *API, unsigned int index) {
794 /* This function is called every time we are about to access a @remotefile.
795 * It is called twice: Once for the hash table and once for the info table.
796 * First we check that we have the GMT_HASH_SERVER_FILE in the server directory.
797 * If we don't then we download it and return since no old file to compare to.
798 * If we do find the hash file then we get its creation time [st_mtime] as
799 * well as the current system time. If the file is < GMT->current.setting.refresh_time
800 * days old we are done.
801 * If the file is older we rename it to *.old and download the latest hash file.
802 * This is the same for both values of index (hash and info). For hash, we do more:
803 * Next, we load the contents of both files and do a double loop to find the
804 * entries for each old file in the new list, for all files. If the old file
805 * is no longer in the list then we delete the data file. If the hash code for
806 * a file has changed then we delete the local file so that the new versions
807 * will be downloaded from the server. Otherwise we do nothing.
808 * The result of this is that any file(s) that have changed will be removed
809 * so that they must be downloaded again to get the new versions.
810 */
811 struct stat buf;
812 time_t mod_time, right_now = time (NULL); /* Unix time right now */
813 char indexpath[PATH_MAX] = {""}, old_indexpath[PATH_MAX] = {""}, new_indexpath[PATH_MAX] = {""}, url[PATH_MAX] = {""};
814 const char *index_file = (index == GMT_HASH_INDEX) ? GMT_HASH_SERVER_FILE : GMT_INFO_SERVER_FILE;
815 struct LOCFILE_FP *LF = NULL;
816 struct GMT_CTRL *GMT = API->GMT; /* Short hand */
817
818 if (GMT->current.io.refreshed[index]) return GMT_NOERROR; /* Already been here */
819
820 snprintf (indexpath, PATH_MAX, "%s/server/%s", GMT->session.USERDIR, index_file);
821
822 if (access (indexpath, R_OK)) { /* Not found locally so need to download the first time */
823 char serverdir[PATH_MAX] = {""};
824 snprintf (serverdir, PATH_MAX, "%s/server", GMT->session.USERDIR);
825 if (access (serverdir, R_OK) && gmt_mkdir (serverdir)) {
826 GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT server directory : %s\n", serverdir);
827 return 1;
828 }
829 snprintf (url, PATH_MAX, "%s/%s", gmt_dataserver_url (API), index_file);
830 GMT_Report (API, GMT_MSG_DEBUG, "Download remote file %s for the first time\n", url);
831 if (gmtremote_get_url (GMT, url, indexpath, NULL, index, true)) {
832 GMT_Report (API, GMT_MSG_INFORMATION, "Failed to get remote file %s\n", url);
833 if (!access (indexpath, F_OK)) gmt_remove_file (GMT, indexpath); /* Remove index file just in case it got corrupted or zero size */
834 GMT->current.setting.auto_download = GMT_NO_DOWNLOAD; /* Temporarily turn off auto download in this session only */
835 GMT->current.io.internet_error = true; /* No point trying again */
836 return 1;
837 }
838 GMT->current.io.refreshed[index] = true; /* Done our job */
839 return GMT_NOERROR;
840 }
841 else
842 GMT_Report (API, GMT_MSG_DEBUG, "Local file %s found\n", indexpath);
843
844 GMT->current.io.refreshed[index] = true; /* Done our job */
845
846 /* Here we have the existing index file and its path is in indexpath. Check how old it is */
847
848 if (stat (indexpath, &buf)) {
849 GMT_Report (API, GMT_MSG_ERROR, "Unable to get information about %s - abort\n", indexpath);
850 return 1;
851 }
852 /* Get its modification (creation) time */
853 #ifdef __APPLE__
854 mod_time = buf.st_mtimespec.tv_sec; /* Apple even has tv_nsec for nano-seconds... */
855 #else
856 mod_time = buf.st_mtime;
857 #endif
858
859 if ((right_now - mod_time) > (GMT_DAY2SEC_I * GMT->current.setting.refresh_time)) { /* Older than selected number of days; Time to get a new index file */
860 GMT_Report (API, GMT_MSG_DEBUG, "File %s older than 24 hours, get latest from server.\n", indexpath);
861 strcpy (new_indexpath, indexpath); /* Duplicate path name */
862 strcat (new_indexpath, ".new"); /* Append .new to the copied path */
863 strcpy (old_indexpath, indexpath); /* Duplicate path name */
864 strcat (old_indexpath, ".old"); /* Append .old to the copied path */
865 snprintf (url, PATH_MAX, "%s/%s", gmt_dataserver_url (API), index_file); /* Set remote path to new index file */
866
867 /* Here we will try to download a file */
868
869 /* Make a lock on the file */
870 if ((LF = gmtremote_lock_on (GMT, (char *)new_indexpath)) == NULL)
871 return 1;
872
873 /* If file locking held us up as another process was downloading the same file,
874 * then that file should now be available. So we check again if it is before proceeding */
875
876 if (!access (new_indexpath, F_OK)) { /* Yes it was! Undo lock and return no error */
877 gmtremote_lock_off (GMT, &LF); /* Remove lock file after successful download (unless query) */
878 return GMT_NOERROR;
879 }
880
881 if (gmtremote_get_url (GMT, url, new_indexpath, indexpath, index, false)) { /* Get the new index file from server */
882 GMT_Report (API, GMT_MSG_DEBUG, "Failed to download %s - Internet troubles?\n", url);
883 if (!access (new_indexpath, F_OK)) gmt_remove_file (GMT, new_indexpath); /* Remove index file just in case it got corrupted or zero size */
884 gmtremote_lock_off (GMT, &LF);
885 return 1; /* Unable to update the file (no Internet?) - skip the tests */
886 }
887 if (!access (old_indexpath, F_OK))
888 remove (old_indexpath); /* Remove old index file if it exists */
889 GMT_Report (API, GMT_MSG_DEBUG, "Rename %s to %s\n", indexpath, old_indexpath);
890 if (gmt_rename_file (GMT, indexpath, old_indexpath, GMT_RENAME_FILE)) { /* Rename existing file to .old */
891 GMT_Report (API, GMT_MSG_ERROR, "Failed to rename %s to %s.\n", indexpath, old_indexpath);
892 gmtremote_lock_off (GMT, &LF);
893 return 1;
894 }
895 GMT_Report (API, GMT_MSG_DEBUG, "Rename %s to %s\n", new_indexpath, indexpath);
896 if (gmt_rename_file (GMT, new_indexpath, indexpath, GMT_RENAME_FILE)) { /* Rename newly copied file to existing file */
897 GMT_Report (API, GMT_MSG_ERROR, "Failed to rename %s to %s.\n", new_indexpath, indexpath);
898 gmtremote_lock_off (GMT, &LF);
899 return 1;
900 }
901
902 if (index == GMT_HASH_INDEX) { /* Special processing to upgrade or remove deprecated files */
903 bool found;
904 int nO, nN, n, o;
905 struct GMT_DATA_HASH *O = NULL, *N = NULL;
906
907 if ((N = gmtremote_hash_load (GMT, indexpath, &nN)) == 0) { /* Read in the new array of hash structs, will return 0 if mismatch of entries */
908 gmt_remove_file (GMT, indexpath); /* Remove corrupted index file */
909 gmtremote_lock_off (GMT, &LF);
910 return 1;
911 }
912
913 O = gmtremote_hash_load (GMT, old_indexpath, &nO); /* Read in the old array of hash structs */
914 for (o = 0; o < nO; o++) { /* Loop over items in old file */
915 if (gmt_getdatapath (GMT, O[o].name, url, R_OK) == NULL) continue; /* Don't have this file downloaded yet */
916 /* Here the file was found locally and the full path is in the url */
917 found = false; /* Not found this file in the new list yet */
918 for (n = 0; !found && n < nN; n++) { /* Loop over items in new file */
919 if (!strcmp (N[n].name, O[o].name)) { /* File is in the current hash table */
920 found = true; /* We will exit this loop regardless of what happens next below */
921 if (strcmp (N[n].hash, O[o].hash)) { /* New hash differs from entry in hash old file */
922 GMT_Report (API, GMT_MSG_DEBUG, "Server and cache versions of %s have different hash codes - must download new copy.\n", N[n].name);
923 gmt_remove_file (GMT, url); /* Need to re-download so be gone with it */
924 }
925 else { /* Do size check */
926 struct stat buf;
927 if (stat (url, &buf)) {
928 GMT_Report (API, GMT_MSG_WARNING, "Could not determine size of file %s.\n", url);
929 continue;
930 }
931 if (N[n].size != (size_t)buf.st_size) { /* Downloaded file size differ - need to re-download */
932 GMT_Report (API, GMT_MSG_DEBUG, "Server and cache versions of %s have different byte sizes (%" PRIuS " versus %" PRIuS ") - must download new copy.\n", N[n].name, N[n].size, (size_t)buf.st_size);
933 gmt_remove_file (GMT, url); /* Need to re-download so be gone with it */
934 }
935 else
936 GMT_Report (API, GMT_MSG_DEBUG, "Server and cache versions of %s are identical - no need to download new file.\n", N[n].name);
937 }
938 }
939 }
940 if (!found) { /* This file was present locally but is no longer part of files on the server and should be removed */
941 GMT_Report (API, GMT_MSG_DEBUG, "File %s no longer supported on server - deleting local copy.\n", O[o].name);
942 gmt_remove_file (GMT, url);
943 }
944 }
945 gmt_M_free (GMT, O); /* Free old hash table structures */
946 gmt_M_free (GMT, N); /* Free new hash table structures */
947 /* We now have an updated hash file */
948 if (!access (old_indexpath, F_OK))
949 remove (old_indexpath); /* Remove old index file if it exists */
950 }
951 else
952 GMT->current.io.new_data_list = true; /* Flag that we wish to delete datasets older than entries in this file */
953 /* Remove lock file after successful download */
954 gmtremote_lock_off (GMT, &LF);
955 }
956 else
957 GMT_Report (API, GMT_MSG_DEBUG, "File %s less than 24 hours old, refresh is premature.\n", indexpath);
958 return GMT_NOERROR;
959 }
960
gmt_refresh_server(struct GMTAPI_CTRL * API)961 void gmt_refresh_server (struct GMTAPI_CTRL *API) {
962 /* Called once in gmt_begin from GMT_Create_Session, The following actions take place:
963 *
964 * The data info table is refreshed if missing or older than 24 hours.
965 * The hash table is refreshed if missing or older than 24 hours.
966 * If a new hash table is obtained and there is a previous one, we determine
967 * if there are entries that have changed (e.g., newer, different files, different
968 * sizes, or gone altogether). In all these case we delete the file so that when the
969 * user requests it, it forces a download of the updated file.
970 */
971
972 if (gmtremote_refresh (API, GMT_INFO_INDEX)) /* Watch out for changes on the server info once a day */
973 GMT_Report (API, GMT_MSG_INFORMATION, "Unable to obtain remote information file %s\n", GMT_INFO_SERVER_FILE);
974 else if (API->remote_info == NULL) { /* Get server file attribution info if not yet loaded */
975 if ((API->remote_info = gmtremote_data_load (API, &API->n_remote_info)) == NULL) { /* Failed to load the info file */
976 GMT_Report (API, GMT_MSG_INFORMATION, "Unable to read server information file\n");
977 }
978 }
979
980 if (gmtremote_refresh (API, GMT_HASH_INDEX)) { /* Watch out for changes on the server hash once a day */
981 GMT_Report (API, GMT_MSG_INFORMATION, "Unable to obtain remote hash table %s\n", GMT_HASH_SERVER_FILE);
982 }
983 }
984
gmtremote_switch_to_srtm(char * file,char * res)985 GMT_LOCAL char * gmtremote_switch_to_srtm (char *file, char *res) {
986 /* There may be more than one remote Earth DEM product that needs to share the
987 * same 1x1 degree SRTM tiles. This function handles this overlap; add more cases if needed. */
988 char *c = NULL;
989 if ((c = strstr (file, ".earth_relief_01s_g")) || (c = strstr (file, ".earth_synbath_01s_g")))
990 *res = '1';
991 else if ((c = strstr (file, ".earth_relief_03s_g")) || (c = strstr (file, ".earth_synbath_03s_g")))
992 *res = '3';
993 return (c); /* Returns pointer to this "extension" or NULL */
994 }
995
gmtremote_get_jp2_tilename(char * file)996 GMT_LOCAL char * gmtremote_get_jp2_tilename (char *file) {
997 /* Must do special legacy checks for SRTMGL1|3 tag names for SRTM tiles.
998 * We also strip off the leading @ since we are building an URL for curl */
999 char res, *c = NULL, *new_file = NULL;
1000
1001 if ((c = gmtremote_switch_to_srtm (file, &res))) {
1002 /* Found one of the SRTM tile families, now replace the tag with SRTMGL1|3 */
1003 char remote_name[GMT_LEN64] = {""};
1004 c[0] = '\0'; /* Temporarily chop off tag and beyond */
1005 sprintf (remote_name, "%s.SRTMGL%c.%s", &file[1], res, GMT_TILE_EXTENSION_REMOTE);
1006 c[0] = '.'; /* Restore period */
1007 new_file = strdup (remote_name);
1008 }
1009 else
1010 new_file = gmt_strrep (&file[1], GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_REMOTE);
1011 return (new_file);
1012 }
1013
gmtremote_convert_jp2_to_nc(struct GMTAPI_CTRL * API,char * localfile)1014 GMT_LOCAL int gmtremote_convert_jp2_to_nc (struct GMTAPI_CTRL *API, char *localfile) {
1015 static char *args = " -fg -Vq --IO_NC4_DEFLATION_LEVEL=9 --GMT_HISTORY=readonly";
1016 char cmd[GMT_LEN512] = {""}, *ncfile = NULL;
1017 int k_data;
1018
1019 if (API->GMT->current.io.leave_as_jp2) return GMT_NOERROR; /* Conversion temporarily turned off by gmtget -N */
1020 if ((k_data = gmtlib_file_is_jpeg2000_tile (API, localfile)) == GMT_NOTSET) return GMT_NOERROR; /* Nothing to do */
1021
1022 /* Convert JP2 file to NC for local cache storage */
1023 ncfile = gmt_strrep (localfile, GMT_TILE_EXTENSION_REMOTE, GMT_TILE_EXTENSION_LOCAL);
1024 sprintf (cmd, "%s -G%s=ns", localfile, ncfile); /* We know we are writing a netCDF short int grid */
1025 if (!doubleAlmostEqual (API->remote_info[k_data].scale, 1.0) || !gmt_M_is_zero (API->remote_info[k_data].offset)) {
1026 /* Integer is not the original data unit and/or has an offset - must scale/shift jp2 integers to units first.
1027 * Because we are inverting the scaling and because grdconvert applies z' = z * scale + offset, we must
1028 * pre-scale and change the sign of the offset here to get the translation we want */
1029 char extra[GMT_LEN64] = {""};
1030 sprintf (extra, "+s%g+o%g", API->remote_info[k_data].scale, API->remote_info[k_data].offset);
1031 strcat (cmd, extra); /* This will embed the scale and offset in the netCDF file so we can use the full range */
1032 sprintf (extra, " -Z+s%g+o%g", API->remote_info[k_data].scale, -API->remote_info[k_data].offset / API->remote_info[k_data].scale);
1033 strcat (cmd, extra); /* This converts the integers we got back to Myr before we let netCDF do the offset/scaling above */
1034 }
1035 strcat (cmd, args); /* Append the common arguments */
1036 GMT_Report (API, GMT_MSG_INFORMATION, "Convert SRTM tile from JPEG2000 to netCDF grid [%s]\n", ncfile);
1037 GMT_Report (API, GMT_MSG_DEBUG, "Running: grdconvert %sn", cmd);
1038 if (GMT_Call_Module (API, "grdconvert", GMT_MODULE_CMD, cmd) != GMT_NOERROR) {
1039 GMT_Report (API, GMT_MSG_ERROR, "ERROR - Unable to convert SRTM file %s to compressed netCDF format\n", localfile);
1040 gmt_M_free (API->GMT, ncfile);
1041 return GMT_RUNTIME_ERROR;
1042 }
1043 gmt_M_str_free (ncfile);
1044 if (gmt_remove_file (API->GMT, localfile))
1045 GMT_Report (API, GMT_MSG_WARNING, "Could not even remove file %s\n", localfile);
1046 return GMT_NOERROR;
1047 }
1048
gmt_set_remote_and_local_filenames(struct GMT_CTRL * GMT,const char * file,char * local_path,char * remote_path,unsigned int mode)1049 int gmt_set_remote_and_local_filenames (struct GMT_CTRL *GMT, const char * file, char *local_path, char *remote_path, unsigned int mode) {
1050 /* Determines the remote and local files for any given file_name.
1051 * For remote files, the mode controls where they are written locally:
1052 * 0 : Place file where GMT wants it to be (e.g., server/earth/earth_relief, /cache etc depending on file type).
1053 * 1 : Place file in the cache directory
1054 * 2 : Place file in user directory
1055 * 3 : Place file in local (current) directory
1056 * If the file must be downloaded from a remote location then remote_path will be populated.
1057 * If the file exists in a local location then local_path will be populated and remote_path empty.
1058 * If both paths are set it means we want to download file and place it in local_path.
1059 * If a local file is not found we return an error code, else 0.
1060 */
1061
1062 int k_data = GMT_NOTSET, t_data = GMT_NOTSET;
1063 unsigned int pos;
1064 bool is_url = false, is_query = false, is_tile = false;
1065 char was, *c = NULL, *jp2_file = NULL, *clean_file = NULL;
1066 struct GMTAPI_CTRL *API = GMT->parent;
1067
1068 local_path[0] = remote_path[0] = '\0';
1069
1070 /* 0. Were we even given an argument? */
1071 if (!file || !file[0]) return GMT_ARG_IS_NULL; /* Got nutin' */
1072
1073 if (gmt_M_file_is_memory (file)) return GMT_NOERROR; /* Memory location always exists */
1074
1075 if (gmtlib_found_url_for_gdal ((char *)file)) { /* Special URLs for grids to be read via GDAL */
1076 snprintf (remote_path, PATH_MAX, "%s", file);
1077 pos = gmtlib_get_pos_of_filename (file); /* Start of file in URL (> 0) */
1078 snprintf (local_path, PATH_MAX, "%s", &file[pos]); /* Same. No directives when writing the file */
1079 return GMT_NOERROR;
1080 }
1081
1082 /* 1. First handle full paths as given */
1083 #ifdef WIN32
1084 if (file[0] == '/' || file[1] == ':')
1085 #else
1086 if (file[0] == '/')
1087 #endif
1088 {
1089 clean_file = gmt_get_filename (API, file, gmtlib_valid_filemodifiers (GMT)); /* Strip off any file modifier or netCDF directives */
1090 if (access (clean_file, F_OK)) {
1091 GMT_Report (API, GMT_MSG_ERROR, "File %s was not found\n", file);
1092 gmt_M_str_free (clean_file);
1093 return GMT_FILE_NOT_FOUND;
1094 }
1095 if (access (clean_file, R_OK)) {
1096 GMT_Report (API, GMT_MSG_ERROR, "File %s is not readable\n", file);
1097 gmt_M_str_free (clean_file);
1098 return GMT_BAD_PERMISSION;
1099 }
1100 gmt_M_str_free (clean_file);
1101 strncpy (local_path, file, PATH_MAX-1);
1102 remote_path[0] = '\0'; /* No need to get from elsewhere */
1103 GMT_Report (API, GMT_MSG_DEBUG, "Given full path to file %s\n", local_path);
1104 return GMT_NOERROR;
1105 }
1106
1107 if (file[0] == '@') { /* Either a cache file or a remote data set */
1108 if ((k_data = gmt_remote_dataset_id (API, file)) != GMT_NOTSET) {
1109 /* Got a valid remote server data filename and we know the local path to those */
1110 if (GMT->session.USERDIR == NULL) goto not_local; /* Cannot have server data if no user directory created yet */
1111 snprintf (local_path, PATH_MAX, "%s", GMT->session.USERDIR); /* This is the top-level directory for user data */
1112 if (access (local_path, R_OK)) goto not_local; /* Have not made a user directory yet, so cannot have the file yet either */
1113 strcat (local_path, GMT->parent->remote_info[k_data].dir); /* Append the subdir (/ or /server/earth/earth_relief/, etc) */
1114 strcat (local_path, GMT->parent->remote_info[k_data].file); /* Append filename */
1115 if (access (local_path, R_OK)) goto not_local; /* No such file yet */
1116 }
1117 else if ((t_data = gmt_file_is_a_tile (API, file, GMT_LOCAL_DIR)) != GMT_NOTSET) { /* Got a remote tile */
1118 /* Got a valid remote server tile filename and we know the local path to those */
1119 if (GMT->session.USERDIR == NULL) goto not_local; /* Cannot have server data if no user directory created yet */
1120 snprintf (local_path, PATH_MAX, "%s", GMT->session.USERDIR); /* This is the top-level directory for user data */
1121 if (access (local_path, R_OK)) goto not_local; /* Have not made a user directory yet, so cannot have the file yet either */
1122 strcat (local_path, GMT->parent->remote_info[t_data].dir); /* Append the subdir (/ or /server/earth/earth_relief/, etc) */
1123 strcat (local_path, GMT->parent->remote_info[t_data].file); /* Append the tiledir to get full path to dir for this type of tiles */
1124 strcat (local_path, &file[1]); /* Append filename */
1125 is_tile = true;
1126 if (access (local_path, R_OK)) { /* A local tile in netCDF format was not found. See if it exists as compressed JP2000 */
1127 char *local_jp2 = gmt_strrep (local_path, GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_REMOTE);
1128 if (access (local_jp2, R_OK))
1129 goto not_local; /* No such file yet */
1130 else { /* Yep, do the just-in-time conversion now */
1131 int error = gmtremote_convert_jp2_to_nc (API, local_jp2);
1132 gmt_M_str_free (local_jp2);
1133 if (error) return error; /* Something failed in the conversion */
1134 }
1135 }
1136 }
1137 else { /* Must be cache file */
1138 if (GMT->session.CACHEDIR == NULL) goto not_local; /* Cannot have cache data if no cache directory created yet */
1139 clean_file = gmt_get_filename (API, file, gmtlib_valid_filemodifiers (GMT)); /* Strip off any file modifier or netCDF directives */
1140 snprintf (local_path, PATH_MAX, "%s/%s", GMT->session.CACHEDIR, &clean_file[1]); /* This is where all cache files live */
1141 if ((c = strchr (local_path, '=')) || (c = strchr (local_path, '?'))) {
1142 was = c[0]; c[0] = '\0';
1143 }
1144 GMT->parent->cache = true;
1145 if (access (local_path, R_OK)) {
1146 if (c) c[0] = was;
1147 goto not_local; /* No such file yet */
1148 }
1149 if (c) c[0] = was;
1150 }
1151 GMT_Report (API, GMT_MSG_DEBUG, "Remote file %s exists locally as %s\n", clean_file, local_path);
1152 remote_path[0] = '\0'; /* No need to get from elsewhere */
1153 if (clean_file) gmt_M_str_free (clean_file);
1154 return GMT_NOERROR;
1155
1156 not_local: /* Get here if we failed to find a remote file already on disk */
1157 /* Set remote path */
1158 if (is_tile) { /* Tile not yet downloaded, but must switch to .jp2 format on the server (and deal with legacy SRTM tile tags) */
1159 jp2_file = gmtremote_get_jp2_tilename ((char *)file);
1160 snprintf (remote_path, PATH_MAX, "%s%s%s%s", gmt_dataserver_url (API), GMT->parent->remote_info[t_data].dir, GMT->parent->remote_info[t_data].file, jp2_file);
1161 }
1162 else if (k_data == GMT_NOTSET) { /* Cache file not yet downloaded */
1163 snprintf (remote_path, PATH_MAX, "%s/cache/%s", gmt_dataserver_url (API), &file[1]);
1164 if (mode == 0) mode = GMT_CACHE_DIR; /* Just so we default to the cache dir for cache files */
1165 }
1166 else /* Remote data set */
1167 snprintf (remote_path, PATH_MAX, "%s%s%s", gmt_dataserver_url (API), API->remote_info[k_data].dir, API->remote_info[k_data].file);
1168
1169 /* Set local path */
1170 switch (mode) {
1171 case GMT_CACHE_DIR:
1172 if (GMT->session.CACHEDIR == NULL) {
1173 GMT_Report (API, GMT_MSG_ERROR, "Cache directory storage requested for %s but your cache directory is undefined\n", file);
1174 return GMT_FILE_NOT_FOUND;
1175 }
1176 else if (access (GMT->session.CACHEDIR, R_OK) && gmt_mkdir (GMT->session.CACHEDIR))
1177 GMT_Report (API, GMT_MSG_ERROR, "Cache directory storage requested for %s but your cache directory could not be created\n", file);
1178 else
1179 snprintf (local_path, PATH_MAX, "%s/%s", GMT->session.CACHEDIR, &file[1]);
1180 break;
1181 case GMT_DATA_DIR:
1182 if (GMT->session.USERDIR == NULL || access (GMT->session.USERDIR, R_OK))
1183 GMT_Report (API, GMT_MSG_ERROR, "User directory storage requested for %s but your user directory is undefined or does not exist\n", file);
1184 else
1185 snprintf (local_path, PATH_MAX, "%s/%s", GMT->session.USERDIR, &file[1]);
1186 break;
1187 case GMT_LOCAL_DIR:
1188 snprintf (local_path, PATH_MAX, "%s", &file[1]);
1189 break;
1190 default: /* Place remote data files locally per the internal rules */
1191 if (GMT->session.USERDIR == NULL || access (GMT->session.USERDIR, R_OK))
1192 GMT_Report (API, GMT_MSG_ERROR, "User directory storage requested for %s but your user directory is undefined or does not exist\n", file);
1193 else { /* Have a user dir */
1194 snprintf (local_path, PATH_MAX, "%s/server", GMT->session.USERDIR);
1195 if (access (local_path, R_OK) && gmt_mkdir (local_path)) /* Have or just made a server subdirectory */
1196 GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT data directory : %s\n", local_path);
1197 if (is_tile) { /* One of the tiles */
1198 if (jp2_file) gmt_M_str_free (jp2_file);
1199 jp2_file = gmt_strrep (&file[1], GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_REMOTE);
1200 snprintf (local_path, PATH_MAX, "%s%s%s", GMT->session.USERDIR, GMT->parent->remote_info[t_data].dir, GMT->parent->remote_info[t_data].file);
1201 if (access (local_path, R_OK) && gmt_mkdir (local_path)) /* Have or just made a server/tile subdirectory */
1202 GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT data directory : %s\n", local_path);
1203 strcat (local_path, jp2_file);
1204 }
1205 else if (!strcmp (API->remote_info[k_data].dir, "/")) /* One of the symbolic links in server */
1206 snprintf (local_path, PATH_MAX, "%s/server/%s", GMT->session.USERDIR, API->remote_info[k_data].file);
1207 else {
1208 snprintf (local_path, PATH_MAX, "%s%s", GMT->session.USERDIR, API->remote_info[k_data].dir);
1209 if (access (local_path, R_OK) && gmt_mkdir (local_path)) /* Have or just made a subdirectory under server */
1210 GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT data directory : %s\n", local_path);
1211 strcat (local_path, API->remote_info[k_data].file);
1212 }
1213 }
1214 break;
1215 }
1216 if (jp2_file) gmt_M_str_free (jp2_file);
1217 if (clean_file) gmt_M_str_free (clean_file);
1218 GMT_Report (API, GMT_MSG_DEBUG, "Get remote file %s and write to %s\n", remote_path, local_path);
1219
1220 return GMT_NOERROR;
1221 }
1222
1223 /* URL files and queries and netcdf grids with directives all have characters like ? and = that
1224 * are not part of an actual file name (but is part of a query). Need to deal with those complexities
1225 * here an keep track of if we got a query or a file request. */
1226
1227 is_url = (gmt_M_file_is_url (file)); /* A remote file or query given via an URL */
1228 is_query = (gmt_M_file_is_query (file)); /* A remote file or query given via an URL */
1229
1230 if (strchr (file, '?') && (c = strstr (file, "=gd"))) { /* Must be a netCDF sliced file to be read via GDAL so chop off the =gd?layer/variable specifications */
1231 was = c[0]; c[0] = '\0';
1232 }
1233 else if ((c = strchr (file, '?')) && !strchr (file, '=')) { /* Must be a netCDF sliced URL file so chop off the layer/variable specifications */
1234 was = c[0]; c[0] = '\0';
1235 }
1236 else if (c == NULL && (c = strchr (file, '='))) { /* If no ? then = means grid attributes (e.g., =bf) */
1237 was = c[0]; c[0] = '\0';
1238 }
1239 else if (c) { /* else we have both ? and = which means file is an URL query */
1240 strncpy (remote_path, file, PATH_MAX-1); /* Pass whatever we were given, no check possible */
1241 was = c[0]; c[0] = '\0';
1242 }
1243
1244 if (is_url) { /* A remote file or query given via an URL never exists locally */
1245 pos = gmtlib_get_pos_of_filename (file); /* Start of file in URL (> 0) */
1246 if (is_query) /* We have truncated off all the ?specifications part */
1247 snprintf (local_path, PATH_MAX, "%s", &file[pos]);
1248 else { /* URL file, we have truncated off any netCDF directives */
1249 strncpy (remote_path, file, PATH_MAX-1);
1250 snprintf (local_path, PATH_MAX, "%s", &file[pos]); /* Same. No directives when writing the file */
1251 }
1252 if (c) c[0] = was;
1253 return GMT_NOERROR;
1254 }
1255
1256 /* Looking for local files given a relative path - must search directories we are allowed.
1257 * Note: Any netCDF files with directives have had those chopped off earlier, so file is a valid name */
1258
1259 clean_file = gmt_get_filename (API, file, gmtlib_valid_filemodifiers (GMT)); /* Strip off any file modifier or netCDF directives */
1260 if (gmt_getdatapath (GMT, clean_file, local_path, R_OK)) { /* Found it */
1261 /* Return full path */
1262 if (c &&!is_query) { /* We need to pass the ?var[]() or [id][+mods]stuff as part of the filename */
1263 c[0] = was;
1264 strcpy (local_path, file);
1265 }
1266 GMT_Report (API, GMT_MSG_DEBUG, "Replace file %s with path %s\n", file, local_path);
1267 gmt_M_str_free (clean_file);
1268 return GMT_NOERROR;
1269 }
1270
1271 /* No luck whatsoever */
1272
1273 gmt_M_str_free (clean_file);
1274
1275 return GMT_FILE_NOT_FOUND;
1276 }
1277
gmtlib_file_is_jpeg2000_tile(struct GMTAPI_CTRL * API,char * file)1278 int gmtlib_file_is_jpeg2000_tile (struct GMTAPI_CTRL *API, char *file) {
1279 /* Detect if a file matches the name <path>/[N|S]yy[E|W]xxx.tag.jp2 (e.g., N22W160.earth_relief_01m_p.jp2) */
1280 char *c, tmp[PATH_MAX] = {""};
1281 if (file == NULL || file[0] == '\0') return GMT_NOTSET; /* Bad argument */
1282 if ((c = strrchr (file, '/')) == NULL) /* Get place of the last slash */
1283 sprintf (tmp, "@%s", file); /* Now should have something like @N22W160.earth_relief_01m_p.jp2 */
1284 else
1285 sprintf (tmp, "@%s", &c[1]); /* Now should have something like @N22W160.earth_relief_01m_p.jp2 */
1286 return (gmt_file_is_a_tile (API, tmp, GMT_REMOTE_DIR));
1287 }
1288
gmt_download_file(struct GMT_CTRL * GMT,const char * name,char * url,char * localfile,bool be_fussy)1289 int gmt_download_file (struct GMT_CTRL *GMT, const char *name, char *url, char *localfile, bool be_fussy) {
1290 bool query = gmt_M_file_is_query (url), turn_ctrl_C_off = false;
1291 int curl_err, error = 0;
1292 size_t fsize;
1293 CURL *Curl = NULL;
1294 struct FtpFile urlfile = {NULL, NULL};
1295 struct LOCFILE_FP *LF = NULL;
1296 struct GMTAPI_CTRL *API = GMT->parent;
1297
1298 if (GMT->current.setting.auto_download == GMT_NO_DOWNLOAD) { /* Not allowed to use remote copying */
1299 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Remote download is currently deactivated\n");
1300 return 1;
1301 }
1302 if (GMT->current.io.internet_error) return 1; /* Not able to use remote copying in this session */
1303
1304 /* Check if the file is too big for our current limit */
1305
1306 if ((fsize = gmtremote_skip_large_files (GMT, url, GMT->current.setting.url_size_limit))) {
1307 char *S = strdup (gmt_memory_use (fsize, 3));
1308 GMT_Report (API, GMT_MSG_WARNING, "File %s skipped as size [%s] exceeds limit set by GMT_DATA_SERVER_LIMIT [%s]\n", name, S, gmt_memory_use (GMT->current.setting.url_size_limit, 0));
1309 gmt_M_str_free (S);
1310 return 1;
1311 }
1312
1313 /* Here we will try to download a file */
1314
1315 /* Only make a lock if not a query */
1316 if (!query && (LF = gmtremote_lock_on (GMT, (char *)name)) == NULL)
1317 return 1;
1318
1319 /* If file locking held us up as another process was downloading the same file,
1320 * then that file should now be available. So we check again if it is before proceeding */
1321
1322 if (!access (localfile, F_OK)) { /* Yes it was! Undo lock and return no error */
1323 if (!query) /* Remove lock file after successful download (unless query) */
1324 gmtremote_lock_off (GMT, &LF);
1325 return GMT_NOERROR;
1326 }
1327
1328 /* Initialize the curl session */
1329 if ((Curl = gmtremote_setup_curl (API, url, localfile, &urlfile, 0)) == NULL)
1330 goto unlocking2;
1331
1332 gmtremote_find_and_give_data_attribution (API, name);
1333
1334 GMT_Report (API, GMT_MSG_INFORMATION, "Downloading file %s ...\n", url);
1335 gmtremote_turn_on_ctrl_C_check (localfile);
1336 turn_ctrl_C_off = true;
1337 if ((curl_err = curl_easy_perform (Curl))) { /* Failed, give error message */
1338 if (be_fussy || !(curl_err == CURLE_REMOTE_FILE_NOT_FOUND || curl_err == CURLE_HTTP_RETURNED_ERROR)) { /* Unexpected failure - want to bitch about it */
1339 GMT_Report (API, GMT_MSG_ERROR, "Libcurl Error: %s\n", curl_easy_strerror (curl_err));
1340 GMT_Report (API, GMT_MSG_WARNING, "You can turn remote file download off by setting GMT_DATA_UPDATE_INTERVAL to \"off\"\n");
1341 if (urlfile.fp != NULL) {
1342 fclose (urlfile.fp);
1343 urlfile.fp = NULL;
1344 }
1345 if (!access (localfile, F_OK) && gmt_remove_file (GMT, localfile)) /* Failed to clean up as well */
1346 GMT_Report (API, GMT_MSG_WARNING, "Could not even remove file %s\n", localfile);
1347 }
1348 else if (curl_err == CURLE_COULDNT_CONNECT)
1349 GMT->current.io.internet_error = true; /* Prevent GMT from trying again in this session */
1350 }
1351 curl_easy_cleanup (Curl);
1352 if (urlfile.fp) /* close the local file */
1353 fclose (urlfile.fp);
1354
1355 unlocking2:
1356
1357 if (!query) /* Remove lock file after successful download (unless query) */
1358 gmtremote_lock_off (GMT, &LF);
1359
1360 if (turn_ctrl_C_off) gmtremote_turn_off_ctrl_C_check ();
1361
1362 if (error == 0) error = gmtremote_convert_jp2_to_nc (API, localfile);
1363
1364 return (error);
1365 }
1366
gmt_download_file_if_not_found(struct GMT_CTRL * GMT,const char * file,unsigned int mode)1367 unsigned int gmt_download_file_if_not_found (struct GMT_CTRL *GMT, const char *file, unsigned int mode) {
1368 /* Downloads a file if not found locally. Returns the position in file_name of the
1369 * start of the actual file (e.g., if given an URL). Values for mode:
1370 * 0 : Place file in the cache directory
1371 * 1 : Place file in user directory
1372 * 2 : Place file in local (current) directory
1373 * Add 4 if the file may not be found and we should not complain about this here.
1374 */
1375 unsigned int pos = 0;
1376 bool be_fussy;
1377 char remote_path[PATH_MAX] = {""}, local_path[PATH_MAX] = {""};
1378
1379 if (gmt_M_file_is_memory (file)) return GMT_NOERROR; /* Memory location always exists */
1380 if (gmtlib_found_url_for_gdal ((char *)file)) return GMT_NOERROR; /* /vis.../ files are read in GDAL */
1381
1382 be_fussy = ((mode & 4) == 0); if (!be_fussy) mode -= 4; /* Handle the optional 4 value */
1383
1384 if (file[0] == '@') /* Make sure we have a refreshed server this session */
1385 gmt_refresh_server (GMT->parent);
1386
1387 if (gmt_set_remote_and_local_filenames (GMT, file, local_path, remote_path, mode)) {
1388 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot find file %s\n", file);
1389 return GMT_NOERROR;
1390 }
1391
1392 if (remote_path[0]) { /* Remote file given but not yet stored locally */
1393 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Download %s to %s\n", remote_path, local_path);
1394 if (gmt_download_file (GMT, file, remote_path, local_path, be_fussy)) {
1395 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to obtain remote file %s\n", file);
1396 return GMT_NOERROR;
1397 }
1398 }
1399 if (gmt_M_file_is_url (file)) /* A remote file or query given via an URL */
1400 pos = gmtlib_get_pos_of_filename (file); /* Start of file in URL (> 0) */
1401 else if (strchr ("@=", file[0]))
1402 pos = 1;
1403
1404 return (pos);
1405 }
1406
1407 /* Support functions for tiled grids
1408 * gmtlib_file_is_tiled Determine if a request was given for a tiled dataset.
1409 * gmt_file_is_tiled_list: Determine if file is a listfile with SRTM tile info
1410 * gmtlib_get_tile_list: Convert -Rw/e/s/n into a file with a list of the tiles needed
1411 * gmtlib_assemble_tiles: Take the list, run grdblend to built the grid.
1412 *
1413 * The way the SRTM tiling works is that a user gives the virtual file name
1414 * @earth_relief_0[1|3]s or @srtm_relief_0[1|3]s. It is possible the user will append
1415 * _p for pixel registration but that is the only registration we have anyway.
1416 * If that is true (determined by gmtlib_remote_file_is_tiled) then we use the given
1417 * -Rw/e/s/n, the resolution given, and whether the ocean should be included (if the
1418 * srtm_relief_* name is given we only do land) and we create a list of all the
1419 * tiles that must be included to cover the region. If ocean is requested we add in
1420 * the filler grid as well. All these files are written in the form of remote
1421 * filenames (start with @). This blend file is written and given a name that
1422 * is unique from the mask =tiled_<ID>_[G|P].######, where<ID> is the dataset iD
1423 * and G|P reflects if -R was a grid or plot region.
1424 * This filename is then used to replace the virtual grid we gave. This happens
1425 * in gmt_init_module at the end of the function. Then, when GMT_Read_Data is given
1426 * the tiled list file it knows what to do: Call gmtlib_assemble_tiles which will set
1427 * up and run a grdblend job that returns the desired custom-built grid. Thus, when
1428 * grdblend starts accessing the files it finds that they are all remote files and
1429 * we will download a series of individual tiles (e.g., @N29W081.SRTMGL1.nc).
1430 */
1431
gmtremote_is_directory(struct GMTAPI_CTRL * API,int k)1432 GMT_LOCAL bool gmtremote_is_directory (struct GMTAPI_CTRL *API, int k) {
1433 /* If entry ends in / then it is a directory, else a file */
1434 size_t len = strlen (API->remote_info[k].file);
1435 if (len < 1) return false;
1436 return (API->remote_info[k].file[len-1] == '/');
1437 }
1438
gmtlib_remote_file_is_tiled(struct GMTAPI_CTRL * API,const char * file,unsigned int * mode)1439 int gmtlib_remote_file_is_tiled (struct GMTAPI_CTRL *API, const char *file, unsigned int *mode) {
1440 /* Determine if file is referring to a tiled remote data set. */
1441 int k_data;
1442 if (!file || file[0] != '@') return GMT_NOTSET; /* Not a remote file */
1443 if (mode) *mode = 0;
1444 if (strncmp (file, "@srtm_relief_0", 14U) == 0) { /* This virtual tile set does not exist. It means earth_relief_0xs_g but do not add filler */
1445 char tmpfile[GMT_LEN32] = {""};
1446 sprintf (tmpfile, "@earth_relief_0%cs_g", file[14]);
1447 if ((k_data = gmt_remote_dataset_id (API, tmpfile)) == GMT_NOTSET) return GMT_NOTSET; /* Not a recognized remote dataset */
1448 if (mode) *mode = GMT_SRTM_ONLY;
1449 return (k_data); /* Since we know earth_relief_01|d_g is a tiled directory */
1450 }
1451 if ((k_data = gmt_remote_dataset_id (API, file)) == GMT_NOTSET) return GMT_NOTSET; /* Not a recognized remote dataset */
1452 return (gmtremote_is_directory (API, k_data) ? k_data : GMT_NOTSET); /* -1 for a regular, remote file, valid index for a directory */
1453 }
1454
gmt_file_is_tiled_list(struct GMTAPI_CTRL * API,const char * file,int * ID,char * wetdry,char * region_type)1455 bool gmt_file_is_tiled_list (struct GMTAPI_CTRL *API, const char *file, int *ID, char *wetdry, char *region_type) {
1456 char *c = NULL, dummy2, dummy3, *wet, *region;
1457 int dummy1, *kval;
1458 kval = (ID) ? ID : &dummy1; /* Allow passing NULL if we don't care */
1459 wet = (wetdry) ? wetdry : &dummy2; /* Allow passing NULL if we don't care */
1460 region = (region_type) ? region_type : &dummy3; /* Allow passing NULL if we don't care */
1461 *kval = GMT_NOTSET;
1462 *wet = *region = 0;
1463 if (file == NULL) return false;
1464 if ((c = strstr (file, "=tiled_")) == NULL) return false; /* Not that kind of file */
1465 if (c[7] && sscanf (&c[7], "%d_%c%c", kval, region, wet) != 3) return false; /* Not finding the id, land/ocean, and grid/plot markers */
1466 if (strchr ("LOX", *wet) == NULL || strchr ("GP", *region) == NULL) return false; /* Invalid characters for two keys */
1467 if (*kval < 0 || (*kval) > API->n_remote_info) return false; /* Outside recognized range of remote file IDs */
1468 return true; /* We got one */
1469 }
1470
gmt_get_tile_id(struct GMTAPI_CTRL * API,char * file)1471 int gmt_get_tile_id (struct GMTAPI_CTRL *API, char *file) {
1472 int k_data;
1473 if (!gmt_file_is_tiled_list (API, file, &k_data, NULL, NULL)) return GMT_NOTSET;
1474 return (k_data);
1475 }
1476
gmt_file_is_a_tile(struct GMTAPI_CTRL * API,const char * infile,unsigned int where)1477 int gmt_file_is_a_tile (struct GMTAPI_CTRL *API, const char *infile, unsigned int where) {
1478 /* Recognizes remote files like @N42E126.SRTMGL1.nc|jp2
1479 * where == 0 means local file (nc extension) and where = 1 means on server (jp2 extension) */
1480 char *p = NULL, *file, tag[GMT_LEN64] = {""}, ext[GMT_LEN16] = {""};
1481 int k_data, n;
1482 size_t len = strlen (infile);
1483 gmt_M_unused (API);
1484 if (len < 12) return GMT_NOTSET; /* Filename too short to hold a full tile name */
1485 file = (char *)((infile[0] == '@') ? &infile[1] : infile); /* Now, file starts at N|S */
1486 if (strchr ("NS", file[0]) == 0) return GMT_NOTSET; /* Does not start with N|S */
1487 if (strchr ("EW", file[3]) == 0) return GMT_NOTSET; /* Does not contain E|W */
1488 if (!(isdigit (file[1]) && isdigit (file[2]))) return GMT_NOTSET; /* No yy latitude */
1489 if (!(isdigit (file[4]) && isdigit (file[5]) && isdigit (file[6]))) return GMT_NOTSET; /* No xxx longitude */
1490 if ((n = sscanf (file, "%*[^.].%[^.].%s", tag, ext)) != 2) return GMT_NOTSET; /* Could not extract tag and extension */
1491 if (where == GMT_REMOTE_DIR) { /* On the server the extension is jp2 */
1492 if (strncmp (ext, GMT_TILE_EXTENSION_REMOTE, GMT_TILE_EXTENSION_REMOTE_LEN)) return GMT_NOTSET; /* Incorrect extension */
1493 }
1494 else if (where == GMT_LOCAL_DIR) {
1495 if (strncmp (ext, GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_LOCAL_LEN)) return GMT_NOTSET; /* Incorrect extension */
1496 }
1497 else {
1498 GMT_Report (API, GMT_MSG_ERROR, "gmt_file_is_a_tile: Internal error - bad where assignment %d.\n", where);
1499 return GMT_NOTSET;
1500 }
1501 if ((p = strstr (file, ".SRTMGL"))) /* Convert to the new tag for legacy SRTM tiles tag of SRTMGL[1|3] so it reflects the name of the dataset */
1502 sprintf (tag, "earth_relief_0%cs_g", p[7]); /* 7th char in p is the 1|3 resolution character */
1503 k_data = gmt_remote_dataset_id (API, tag);
1504 return (k_data);
1505 }
1506
gmtremote_is_earth_dem(struct GMT_DATA_INFO * I)1507 GMT_LOCAL bool gmtremote_is_earth_dem (struct GMT_DATA_INFO *I) {
1508 /* Returns true if this data set is one of the earth_relief clones that must share SRTM tiles with @earth_relief.
1509 * Should we add more such DEMs then just add more cases like the synbath test */
1510 if (strstr (I->tag, "synbath") && (!strcmp (I->inc, "03s") || !strcmp (I->inc, "01s"))) return true;
1511 return false;
1512 }
1513
gmt_get_dataset_tiles(struct GMTAPI_CTRL * API,double wesn_in[],int k_data,unsigned int * n_tiles,bool * need_filler)1514 char ** gmt_get_dataset_tiles (struct GMTAPI_CTRL *API, double wesn_in[], int k_data, unsigned int *n_tiles, bool *need_filler) {
1515 /* Return the full list of tiles for this tiled dataset. If need_filler is not NULL we return
1516 * true if some of the tiles inside the wesn do not exist based on the coverage map. */
1517 bool partial_tile = false;
1518 char **list = NULL, YS, XS, file[GMT_LEN64] = {""}, tag[GMT_LEN64] = {""};
1519 int x, lon, clat, iw, ie, is, in, t_size;
1520 uint64_t node, row, col;
1521 unsigned int n_alloc = GMT_CHUNK, n = 0, n_missing = 0;
1522 double wesn[4];
1523 struct GMT_DATA_INFO *I = &API->remote_info[k_data]; /* Pointer to primary tiled dataset */
1524 struct GMT_GRID *Coverage = NULL;
1525
1526 if (gmt_M_is_zero (I->tile_size)) return NULL;
1527
1528 strncpy (tag, I->tag, GMT_LEN64); /* Initialize tag since it may change below */
1529 if (strcmp (I->coverage, "-")) { /* This primary tiled dataset has limited coverage as described by a named hit grid */
1530 char coverage_file[GMT_LEN64] = {""};
1531 sprintf (coverage_file, "@%s", I->coverage); /* Prepend the remote flag since we may need to download the file */
1532 if ((Coverage = GMT_Read_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, NULL, coverage_file, NULL)) == NULL) {
1533 GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to obtain coverage grid %s of available tiles.\n", I->coverage);
1534 API->error = GMT_RUNTIME_ERROR;
1535 return NULL;
1536 }
1537 if (gmtremote_is_earth_dem (I)) /* Dataset shares SRTM1|3 tiles with @earth_relief */
1538 sprintf (tag, "earth_relief_%s_%c", I->inc, I->reg);
1539 }
1540
1541 if ((list = gmt_M_memory (API->GMT, NULL, n_alloc, char *)) == NULL) {
1542 GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to allocate memory.\n");
1543 API->error = GMT_RUNTIME_ERROR;
1544 return NULL;
1545 }
1546
1547 /* Preprocess the selected wesn to make it work better for a -180/180 window */
1548 gmt_M_memcpy (wesn, wesn_in, 4U, double);
1549 if (gmt_M_360_range (wesn[XLO], wesn[XHI])) wesn[XLO] = -180, wesn[XHI] = +180.0;
1550 else if (wesn[XLO] < -180.0) wesn[XLO] += 360.0, wesn[XHI] += 360.0;
1551 else if (wesn[XLO] > +180.0) wesn[XLO] -= 360.0, wesn[XHI] -= 360.0;
1552 /* Get nearest whole multiple of tile size wesn boundary. This ASSUMES all global grids are -Rd */
1553 /* Also, the srtm_tiles.nc grid is gridline-registered and we check if the node corresponding to
1554 * the lon/lat of the SW corner of a tile is 1 or 0 */
1555 iw = (int)(-180 + floor ((wesn[XLO] + 180) / I->tile_size) * I->tile_size);
1556 ie = (int)(-180 + ceil ((wesn[XHI] + 180) / I->tile_size) * I->tile_size);
1557 is = (int)( -90 + floor ((wesn[YLO] + 90) / I->tile_size) * I->tile_size);
1558 in = (int)( -90 + ceil ((wesn[YHI] + 90) / I->tile_size) * I->tile_size);
1559 t_size = rint (I->tile_size);
1560
1561 for (clat = is; clat < in; clat += t_size) { /* Loop over the rows of tiles */
1562 if (Coverage && (clat < Coverage->header->wesn[YLO] || clat >= Coverage->header->wesn[YHI])) continue; /* Outside Coverage band */
1563 YS = (clat < 0) ? 'S' : 'N';
1564 for (x = iw; x < ie; x += t_size) { /* Loop over the columns of tiles */
1565 lon = (x < 0) ? x + 360 : x; /* Get longitude in 0-360 range */
1566 if (Coverage) { /* We will assume nothing about the west/east bounds of the coverage grid */
1567 int clon = lon - 360; /* Ensure we are far west */
1568 while (clon < Coverage->header->wesn[XLO]) clon += 360; /* Wind until past west */
1569 if (clon > Coverage->header->wesn[XHI]) continue; /* Outside Coverage band */
1570 row = gmt_M_grd_y_to_row (GMT, (double)clat, Coverage->header);
1571 col = gmt_M_grd_x_to_col (GMT, (double)clon, Coverage->header);
1572 node = gmt_M_ijp (Coverage->header, row, col);
1573 if (Coverage->data[node] == GMT_NO_TILE) { /* No such tile exists */
1574 n_missing++; /* Add up missing tiles */
1575 continue; /* Go to next tile */
1576 }
1577 else if (Coverage->data[node] == GMT_PARTIAL_TILE) /* Not missing, but still need ocean filler for partial tile */
1578 partial_tile = true; /* Note: We also get here with GMT < 6.3 so that @earth_relief_15s is always considered */
1579 }
1580 lon = (x >= 180) ? x - 360 : x; /* Need longitudes 0-179 for E and 1-180 for W */
1581 XS = (lon < 0) ? 'W' : 'E';
1582 /* Write remote tile name to list */
1583 if (n >= n_alloc) {
1584 n_alloc <<= 1;
1585 if ((list = gmt_M_memory (API->GMT, list, n_alloc, char *)) == NULL) {
1586 GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to reallocate memory.\n");
1587 API->error = GMT_RUNTIME_ERROR;
1588 return NULL;
1589 }
1590 }
1591 sprintf (file, "@%c%2.2d%c%3.3d.%s.%s", YS, abs(clat), XS, abs(lon), tag, GMT_TILE_EXTENSION_LOCAL);
1592 list[n++] = strdup (file);
1593 }
1594 }
1595 if (Coverage && GMT_Destroy_Data (API, &Coverage) != GMT_NOERROR) {
1596 GMT_Report (API, GMT_MSG_WARNING, "gmtlib_get_tile_list: Unable to destroy coverage grid.\n");
1597 }
1598
1599 if (need_filler) *need_filler = (partial_tile || n_missing > 0); /* Incomplete coverage of this data set within wesn */
1600
1601 *n_tiles = n;
1602 if (n == 0) { /* Nutin' */
1603 gmt_M_free (API->GMT, list);
1604 GMT_Report (API, GMT_MSG_WARNING, "gmt_get_dataset_tiles: No %s tiles available for your region.\n", I->tag);
1605 return NULL;
1606 }
1607
1608 if ((list = gmt_M_memory (API->GMT, list, n, char *)) == NULL) {
1609 GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to finalize memory.\n");
1610 API->error = GMT_RUNTIME_ERROR;
1611 return NULL;
1612 }
1613
1614 return (list);
1615 }
1616
gmtlib_get_tile_list(struct GMTAPI_CTRL * API,double wesn[],int k_data,bool plot_region,unsigned int srtm_flag)1617 char *gmtlib_get_tile_list (struct GMTAPI_CTRL *API, double wesn[], int k_data, bool plot_region, unsigned int srtm_flag) {
1618 /* Builds a list of the tiles to download for the chosen region, dataset and resolution.
1619 * Uses the optional tile information grid to know if a particular tile exists. */
1620 bool need_filler;
1621 char tile_list[PATH_MAX] = {""}, stem[GMT_LEN32] = {""}, *file = NULL, **tile = NULL, datatype[3] = {'L', 'O', 'X'}, regtype[2] = {'G', 'P'};
1622 int k_filler = GMT_NOTSET;
1623 unsigned int k, n_tiles = 0, ocean = (srtm_flag) ? 0 : 2;
1624 FILE *fp = NULL;
1625 struct GMT_DATA_INFO *Ip = &API->remote_info[k_data], *Is = NULL; /* Pointer to primary tiled dataset */
1626
1627 /* See if we want a background filler - this is most likely when using SRTM tiles and a 15s background ocean */
1628 if (strcmp (Ip->filler, "-") && srtm_flag == 0) { /* Want background filler, except special case when srtm_relief is the given dataset name (srtm_flag == 1) */
1629 if ((k_filler = gmt_remote_dataset_id (API, Ip->filler)) == GMT_NOTSET) {
1630 GMT_Report (API, GMT_MSG_ERROR, "gmtlib_get_tile_list: Internal error - Filler grid %s is not a recognized remote data set.\n", Ip->filler);
1631 return NULL;
1632 }
1633 Is = &API->remote_info[k_filler]; /* Pointer to secondary tiled dataset */
1634 ocean = (strcmp (Is->inc, "15s") == 0);
1635 }
1636
1637 /* Create temporary filename for list of tiles */
1638
1639 snprintf (stem, GMT_LEN32, "=tiled_%d_%c%c", k_data, regtype[plot_region], datatype[ocean]);
1640 if ((fp = gmt_create_tempfile (API, stem, NULL, tile_list)) == NULL) { /* Not good... */
1641 GMT_Report (API, GMT_MSG_ERROR, "gmtlib_get_tile_list: Unable to create list of tiles from template: %s.\n", tile_list);
1642 return NULL;
1643 }
1644 file = tile_list; /* Pointer to the buffer with the name */
1645
1646 /* Get the primary tiles and determine if the filler grid is needed */
1647 tile = gmt_get_dataset_tiles (API, wesn, k_data, &n_tiles, &need_filler);
1648
1649 /* Write primary tiles to list file */
1650 for (k = 0; k < n_tiles; k++)
1651 fprintf (fp, "%s\n", tile[k]);
1652
1653 gmt_free_list (API->GMT, tile, n_tiles); /* Free the primary tile list */
1654 if (k_filler != GMT_NOTSET) { /* Want the secondary tiles */
1655 if (need_filler && (tile = gmt_get_dataset_tiles (API, wesn, k_filler, &n_tiles, NULL))) {
1656 /* Write secondary tiles to list file */
1657 for (k = 0; k < n_tiles; k++)
1658 fprintf (fp, "%s\n", tile[k]);
1659 gmt_free_list (API->GMT, tile, n_tiles); /* Free the secondary tile list */
1660 }
1661 if (Ip->d_inc < Is->d_inc) {
1662 /* If selected dataset has smaller increment that the filler grid then we adjust -R to be a multiple of the larger spacing. */
1663 /* Enforce multiple of tile grid resolution in wesn so requested region is in phase with tiles and at least covers the given
1664 * region. The GMT_CONV8_LIMIT is there to ensure we won't round an almost exact x/dx away from the truth. */
1665 wesn[XLO] = floor ((wesn[XLO] / Is->d_inc) + GMT_CONV8_LIMIT) * Is->d_inc;
1666 wesn[XHI] = ceil ((wesn[XHI] / Is->d_inc) - GMT_CONV8_LIMIT) * Is->d_inc;
1667 wesn[YLO] = floor ((wesn[YLO] / Is->d_inc) + GMT_CONV8_LIMIT) * Is->d_inc;
1668 wesn[YHI] = ceil ((wesn[YHI] / Is->d_inc) - GMT_CONV8_LIMIT) * Is->d_inc;
1669 }
1670 }
1671 fclose (fp);
1672
1673 gmt_M_memcpy (API->tile_wesn, wesn, 4, double); /* Retain this knowledge in case it was obtained via map_setup for an oblique area */
1674
1675 return (strdup (file));
1676 }
1677
gmtlib_assemble_tiles(struct GMTAPI_CTRL * API,double * region,char * file)1678 struct GMT_GRID *gmtlib_assemble_tiles (struct GMTAPI_CTRL *API, double *region, char *file) {
1679 /* Get here if file is a =tiled_<id>_G|P.xxxxxx file. Need to do:
1680 * Set up a grdblend command and return the assembled grid
1681 */
1682 int k_data, v_level = API->verbose;
1683 struct GMT_GRID *G = NULL;
1684 double *wesn = (region) ? region : API->tile_wesn; /* Default to -R */
1685 char grid[GMT_VF_LEN] = {""}, cmd[GMT_LEN256] = {""}, code = 0;;
1686 struct GMT_GRID_HEADER_HIDDEN *HH = NULL;
1687
1688 (void) gmt_file_is_tiled_list (API, file, NULL, &code, NULL); /* Just get the code*/
1689
1690 if ((k_data = gmt_get_tile_id (API, file)) == GMT_NOTSET) {
1691 GMT_Report (API, GMT_MSG_ERROR, "Internal error: Non-recognized tiled ID embedded in file %s\n", file);
1692 return NULL;
1693 }
1694
1695 if (API->verbose == GMT_MSG_WARNING) API->verbose = GMT_MSG_ERROR; /* Drop from warnings to errors only when calling grdblend to avoid annoying messages about phase/shift from SRTM01|3 and 15s */
1696 GMT_Open_VirtualFile (API, GMT_IS_GRID, GMT_IS_SURFACE, GMT_OUT|GMT_IS_REFERENCE, NULL, grid);
1697 /* Pass -N0 so that missing tiles (oceans) yield z = 0 and not NaN, and -Co+n to override using negative earth_relief_15s values */
1698 snprintf (cmd, GMT_LEN256, "%s -R%.16g/%.16g/%.16g/%.16g -I%s -r%c -G%s -fg -Co+n", file, wesn[XLO], wesn[XHI], wesn[YLO], wesn[YHI], API->remote_info[k_data].inc, API->remote_info[k_data].reg, grid);
1699 if (code != 'X') strcat (cmd, " -N0 -Ve"); /* If ocean/land, set empty nodes to 0, else NaN. Also turn of warnings since mixing pixel and gridline grids, possibly */
1700 if (GMT_Call_Module (API, "grdblend", GMT_MODULE_CMD, cmd) != GMT_NOERROR) {
1701 API->verbose = v_level;
1702 GMT_Report (API, GMT_MSG_ERROR, "ERROR - Unable to produce blended grid from %s\n", file);
1703 return NULL;
1704 }
1705 if ((G = GMT_Read_VirtualFile (API, grid)) == NULL) { /* Load in the resampled grid */
1706 API->verbose = v_level;
1707 GMT_Report (API, GMT_MSG_ERROR, "ERROR - Unable to receive blended grid from grdblend\n");
1708 return NULL;
1709 }
1710
1711 API->verbose = v_level;
1712 HH = gmt_get_H_hidden (G->header);
1713 HH->orig_datatype = GMT_SHORT; /* Since we know this */
1714 return (G);
1715 }
1716
gmt_download_tiles(struct GMTAPI_CTRL * API,char * list,unsigned int mode)1717 int gmt_download_tiles (struct GMTAPI_CTRL *API, char *list, unsigned int mode) {
1718 /* Download all tiles not already here given by the list */
1719 uint64_t n, k;
1720 char **file = NULL;
1721
1722 if (!gmt_file_is_tiled_list (API, list, NULL, NULL, NULL)) return GMT_RUNTIME_ERROR;
1723 if ((n = gmt_read_list (API->GMT, list, &file)) == 0) return GMT_RUNTIME_ERROR;
1724 for (k = 0; k < n; k++) {
1725 gmt_download_file_if_not_found (API->GMT, file[k], mode);
1726 }
1727 gmt_free_list (API->GMT, file, n);
1728 return GMT_NOERROR;
1729 }
1730
gmt_dataserver_url(struct GMTAPI_CTRL * API)1731 char *gmt_dataserver_url (struct GMTAPI_CTRL *API) {
1732 /* Build the full URL to the currently selected data server */
1733 static char URL[GMT_LEN256] = {""}, *link = URL;
1734 if (strncmp (API->GMT->session.DATASERVER, "http", 4U)) { /* Not an URL so must assume it is the country/unit name, e.g., oceania */
1735 /* We make this part case insensitive since all official GMT servers are lower-case */
1736 char name[GMT_LEN64] = {""};
1737 strncpy (name, API->GMT->session.DATASERVER, GMT_LEN64-1);
1738 gmt_str_tolower (name);
1739 snprintf (URL, GMT_LEN256-1, "http://%s.generic-mapping-tools.org", name);
1740 }
1741 else /* Must use the URL as is */
1742 snprintf (URL, GMT_LEN256-1, "%s", API->GMT->session.DATASERVER);
1743 return (link);
1744 }
1745