1 /*
2 Gpredict: Real-time satellite tracking and orbit prediction program
3
4 Copyright (C) 2001-2017 Alexandru Csete, OZ9AEC.
5 Copyright (C) 2009 Charles Suprin AA1VS.
6
7 Comments, questions and bugreports should be submitted via
8 http://sourceforge.net/projects/gpredict/
9 More details can be found at the project home page:
10
11 http://gpredict.oz9aec.net/
12
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, visit http://www.fsf.org/
25 */
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <glib/gstdio.h>
29 #include <gtk/gtk.h>
30 #include <string.h>
31
32 #ifdef HAVE_CONFIG_H
33 #include <build-config.h>
34 #endif
35 #ifdef WIN32
36 #include "win32-fetch.h"
37 #else
38 #include <curl/curl.h>
39 #endif
40
41 #include "compat.h"
42 #include "gpredict-utils.h"
43 #include "sat-cfg.h"
44 #include "sat-log.h"
45 #include "sgpsdp/sgp4sdp4.h"
46 #include "tle-update.h"
47
48
49 /* private function prototypes */
50 #ifndef WIN32
51 static size_t my_write_func(void *ptr, size_t size, size_t nmemb,
52 FILE * stream);
53 #endif
54 static gint read_fresh_tle(const gchar * dir, const gchar * fnam,
55 GHashTable * data);
56 static gboolean is_tle_file(const gchar * dir, const gchar * fnam);
57
58
59 static void update_tle_in_file(const gchar * ldname,
60 const gchar * fname,
61 GHashTable * data,
62 guint * sat_upd,
63 guint * sat_ski,
64 guint * sat_nod, guint * sat_tot);
65
66 static guint add_new_sats(GHashTable * data);
67 static gboolean is_computer_generated_name(gchar * satname);
68
69
70 /** Free a new_tle_t structure. */
free_new_tle(gpointer data)71 static void free_new_tle(gpointer data)
72 {
73 new_tle_t *tle;
74
75 tle = (new_tle_t *) data;
76
77 g_free(tle->satname);
78 g_free(tle->line1);
79 g_free(tle->line2);
80 g_free(tle->srcfile);
81 g_free(tle);
82 }
83
84
85 /**
86 * Update TLE files from local files.
87 *
88 * @param dir Directory where files are located.
89 * @param filter File filter, e.g. *.txt (not used at the moment!)
90 * @param silent TRUE if function should execute without graphical status indicator.
91 * @param label1 Activity label (can be NULL)
92 * @param label2 Statistics label (can be NULL)
93 * @param progress Pointer to progress indicator.
94 * @param init_prgs Initial value of progress indicator, e.g 0.5 if we are updating
95 * from network.
96 *
97 * This function is used to update the TLE data from local files.
98 */
tle_update_from_files(const gchar * dir,const gchar * filter,gboolean silent,GtkWidget * progress,GtkWidget * label1,GtkWidget * label2)99 void tle_update_from_files(const gchar * dir, const gchar * filter,
100 gboolean silent, GtkWidget * progress,
101 GtkWidget * label1, GtkWidget * label2)
102 {
103 static GMutex tle_file_in_progress;
104
105 GHashTable *data; /* hash table with fresh TLE data */
106 GDir *cache_dir; /* directory to scan fresh TLE */
107 GDir *loc_dir; /* directory for gpredict TLE files */
108 GError *err = NULL;
109 gchar *text;
110 gchar *ldname;
111 gchar *userconfdir;
112 const gchar *fnam;
113 guint num = 0;
114 guint updated, updated_tmp;
115 guint skipped, skipped_tmp;
116 guint nodata, nodata_tmp;
117 guint newsats = 0;
118 guint total, total_tmp;
119 gdouble fraction = 0.0;
120 gdouble start = 0.0;
121
122 (void)filter;
123
124 if (g_mutex_trylock(&tle_file_in_progress) == FALSE)
125 {
126 sat_log_log(SAT_LOG_LEVEL_ERROR,
127 _
128 ("%s: A TLE update process is already running. Aborting."),
129 __func__);
130 return;
131 }
132
133 /* create hash table */
134 data = g_hash_table_new_full(g_int_hash, g_int_equal, g_free,
135 free_new_tle);
136
137 /* open directory and read files one by one */
138 cache_dir = g_dir_open(dir, 0, &err);
139
140 if (err != NULL)
141 {
142 /* send an error message */
143 sat_log_log(SAT_LOG_LEVEL_ERROR,
144 _("%s: Error opening directory %s (%s)"),
145 __func__, dir, err->message);
146
147 /* insert error message into the status string, too */
148 if (!silent && (label1 != NULL))
149 {
150 text = g_strdup_printf(_("<b>ERROR</b> opening directory %s\n%s"),
151 dir, err->message);
152
153 gtk_label_set_markup(GTK_LABEL(label1), text);
154 g_free(text);
155 }
156
157 g_clear_error(&err);
158 err = NULL;
159 }
160 else
161 {
162 /* scan directory for tle files */
163 while ((fnam = g_dir_read_name(cache_dir)) != NULL)
164 {
165 /* check that we got a TLE file */
166 if (is_tle_file(dir, fnam))
167 {
168 /* status message */
169 if (!silent && (label1 != NULL))
170 {
171 text = g_strdup_printf(_("Reading data from %s"), fnam);
172 gtk_label_set_text(GTK_LABEL(label1), text);
173 g_free(text);
174
175 /* Force the drawing queue to be processed otherwise there will
176 not be any visual feedback, ie. frozen GUI
177 - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602
178 */
179 while (g_main_context_iteration(NULL, FALSE));
180
181 /* give user a chance to follow progress */
182 g_usleep(G_USEC_PER_SEC / 100);
183 }
184
185 /* now, do read the fresh data */
186 num = read_fresh_tle(dir, fnam, data);
187 }
188 else
189 {
190 num = 0;
191 }
192
193 if (num < 1)
194 {
195 sat_log_log(SAT_LOG_LEVEL_ERROR,
196 _("%s: No valid TLE data found in %s"),
197 __func__, fnam);
198 }
199 else
200 {
201 sat_log_log(SAT_LOG_LEVEL_INFO,
202 _("%s: Read %d sats from %s into memory"),
203 __func__, num, fnam);
204 }
205 }
206
207 /* close directory since we don't need it anymore */
208 g_dir_close(cache_dir);
209
210 /* now we load each .sat file and update if we have new data */
211 userconfdir = get_user_conf_dir();
212 ldname = g_strconcat(userconfdir, G_DIR_SEPARATOR_S, "satdata", NULL);
213 g_free(userconfdir);
214
215 /* open directory and read files one by one */
216 loc_dir = g_dir_open(ldname, 0, &err);
217
218 if (err != NULL)
219 {
220 /* send an error message */
221 sat_log_log(SAT_LOG_LEVEL_ERROR,
222 _("%s: Error opening directory %s (%s)"),
223 __func__, dir, err->message);
224
225 /* insert error message into the status string, too */
226 if (!silent && (label1 != NULL))
227 {
228 text =
229 g_strdup_printf(_("<b>ERROR</b> opening directory %s\n%s"),
230 dir, err->message);
231
232 gtk_label_set_markup(GTK_LABEL(label1), text);
233 g_free(text);
234 }
235
236 g_clear_error(&err);
237 err = NULL;
238 }
239 else
240 {
241 /* clear statistics */
242 updated = 0;
243 skipped = 0;
244 nodata = 0;
245 total = 0;
246
247 /* get initial value of progress indicator */
248 if (progress != NULL)
249 start =
250 gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(progress));
251
252 /* This is insane but I don't know how else to count the number of sats */
253 num = 0;
254 while ((fnam = g_dir_read_name(loc_dir)) != NULL)
255 {
256 /* only consider .sat files */
257 if (g_str_has_suffix(fnam, ".sat"))
258 {
259 num++;
260 }
261 }
262
263 g_dir_rewind(loc_dir);
264
265 /* update TLE files one by one */
266 while ((fnam = g_dir_read_name(loc_dir)) != NULL)
267 {
268 /* only consider .sat files */
269 if (g_str_has_suffix(fnam, ".sat"))
270 {
271 /* clear stat bufs */
272 updated_tmp = 0;
273 skipped_tmp = 0;
274 nodata_tmp = 0;
275 total_tmp = 0;
276
277 /* update TLE data in this file */
278 update_tle_in_file(ldname, fnam, data,
279 &updated_tmp,
280 &skipped_tmp, &nodata_tmp, &total_tmp);
281
282 /* update statistics */
283 updated += updated_tmp;
284 skipped += skipped_tmp;
285 nodata += nodata_tmp;
286 total = updated + skipped + nodata;
287
288 if (!silent)
289 {
290 if (label1 != NULL)
291 {
292 gtk_label_set_text(GTK_LABEL(label1),
293 _("Updating data..."));
294 }
295
296 if (label2 != NULL)
297 {
298 text =
299 g_strdup_printf(_
300 ("Satellites updated:\t %d\n"
301 "Satellites skipped:\t %d\n"
302 "Missing Satellites:\t %d\n"),
303 updated, skipped, nodata);
304 gtk_label_set_text(GTK_LABEL(label2), text);
305 g_free(text);
306 }
307
308 if (progress != NULL)
309 {
310 /* two different calculations for completeness depending on whether
311 we are adding new satellites or not. */
312 if (sat_cfg_get_bool(SAT_CFG_BOOL_TLE_ADD_NEW))
313 {
314 /* In this case we are possibly processing more than num satellites
315 How many more? We do not know yet. Worst case is g_hash_table_size more.
316
317 As we update skipped and updated we can reduce the denominator count
318 as those are in both pools (files and hash table). When we have processed
319 all the files, updated and skipped are completely correct and the progress
320 is correct. It may be correct sooner if the missed satellites are the
321 last files to process.
322
323 Until then, if we eliminate the ones that are updated and skipped from being
324 double counted, our progress will shown will always be less or equal to our
325 true progress since the denominator will be larger than is correct.
326
327 Advantages to this are that the progress bar does not stall close to
328 finished when there are a large number of new satellites.
329 */
330 fraction =
331 start + (1.0 -
332 start) * ((gdouble) total) /
333 ((gdouble) num + g_hash_table_size(data) -
334 updated - skipped);
335 }
336 else
337 {
338 /* here we only process satellites we have have files for so divide by num */
339 fraction =
340 start + (1.0 -
341 start) * ((gdouble) total) /
342 ((gdouble) num);
343 }
344 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR
345 (progress),
346 fraction);
347
348 }
349
350 /* update the gui only every so often to speed up the process */
351 /* 47 was selected empirically to balance the update looking smooth but not take too much time. */
352 /* it also tumbles all digits in the numbers so that there is no obvious pattern. */
353 /* on a developer machine this improved an update from 5 minutes to under 20 seconds. */
354 if (total % 47 == 0)
355 {
356 /* Force the drawing queue to be processed otherwise there will
357 not be any visual feedback, ie. frozen GUI
358 - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602
359 */
360 while (g_main_context_iteration(NULL, FALSE));
361
362 /* give user a chance to follow progress */
363 g_usleep(G_USEC_PER_SEC / 1000);
364 }
365 }
366 }
367 }
368
369 /* force gui update */
370 while (g_main_context_iteration(NULL, FALSE));
371
372 /* close directory handle */
373 g_dir_close(loc_dir);
374
375 /* see if we have any new sats that need to be added */
376 if (sat_cfg_get_bool(SAT_CFG_BOOL_TLE_ADD_NEW))
377 {
378 newsats = add_new_sats(data);
379
380 if (!silent && (label2 != NULL))
381 {
382 text = g_strdup_printf(_("Satellites updated:\t %d\n"
383 "Satellites skipped:\t %d\n"
384 "Missing Satellites:\t %d\n"
385 "New Satellites:\t\t %d"),
386 updated, skipped, nodata, newsats);
387 gtk_label_set_text(GTK_LABEL(label2), text);
388 g_free(text);
389 }
390
391 sat_log_log(SAT_LOG_LEVEL_INFO,
392 _("%s: Added %d new satellites to local database"),
393 __func__, newsats);
394 }
395
396 /* store time of update if we have updated something */
397 if ((updated > 0) || (newsats > 0))
398 {
399 GTimeVal tval;
400
401 g_get_current_time(&tval);
402 sat_cfg_set_int(SAT_CFG_INT_TLE_LAST_UPDATE, tval.tv_sec);
403 }
404 }
405
406 g_free(ldname);
407 sat_log_log(SAT_LOG_LEVEL_INFO,
408 _("%s: TLE elements updated."), __func__);
409 }
410
411 /* destroy hash tables */
412 g_hash_table_destroy(data);
413 g_mutex_unlock(&tle_file_in_progress);
414 }
415
416
417 /** Check if satellite is new, if so, add it to local database */
check_and_add_sat(gpointer key,gpointer value,gpointer user_data)418 static void check_and_add_sat(gpointer key, gpointer value, gpointer user_data)
419 {
420 new_tle_t *ntle = (new_tle_t *) value;
421 guint *num = user_data;
422 GKeyFile *satdata;
423 gchar *cfgfile;
424
425 (void)key;
426
427 /* check if sat is new */
428 if (!ntle->isnew)
429 return;
430
431 /* create config data */
432 satdata = g_key_file_new();
433
434 /* store data */
435 g_key_file_set_string(satdata, "Satellite", "VERSION", "1.1");
436 g_key_file_set_string(satdata, "Satellite", "NAME", ntle->satname);
437 g_key_file_set_string(satdata, "Satellite", "NICKNAME", ntle->satname);
438 g_key_file_set_string(satdata, "Satellite", "TLE1", ntle->line1);
439 g_key_file_set_string(satdata, "Satellite", "TLE2", ntle->line2);
440 g_key_file_set_integer(satdata, "Satellite", "STATUS", ntle->status);
441
442 /* create an I/O channel and store data */
443 cfgfile = sat_file_name_from_catnum(ntle->catnum);
444 if (!gpredict_save_key_file(satdata, cfgfile))
445 *num += 1;
446
447 /* clean up memory */
448 g_free(cfgfile);
449 g_key_file_free(satdata);
450 }
451
452 /** Add new satellites to local database */
add_new_sats(GHashTable * data)453 static guint add_new_sats(GHashTable * data)
454 {
455 guint num = 0;
456
457 g_hash_table_foreach(data, check_and_add_sat, &num);
458
459 return num;
460 }
461
462 /**
463 * Update TLE files from network.
464 *
465 * @param silent TRUE if function should execute without graphical status indicator.
466 * @param progress Pointer to a GtkProgressBar progress indicator (can be NULL)
467 * @param label1 GtkLabel for activity string.
468 * @param label2 GtkLabel for statistics string.
469 */
tle_update_from_network(gboolean silent,GtkWidget * progress,GtkWidget * label1,GtkWidget * label2)470 void tle_update_from_network(gboolean silent,
471 GtkWidget * progress,
472 GtkWidget * label1, GtkWidget * label2)
473 {
474 static GMutex tle_in_progress;
475
476 gchar *proxy = NULL;
477 gchar *files_tmp;
478 gchar **files;
479 guint numfiles, i;
480 gchar *curfile;
481 gchar *locfile;
482 gchar *userconfdir;
483 #ifdef WIN32
484 int res;
485 #else
486 CURL *curl;
487 CURLcode res;
488 #endif
489 gdouble fraction, start = 0;
490 FILE *outfile;
491 GDir *dir;
492 gchar *cache;
493 const gchar *fname;
494 gchar *text;
495 GError *err = NULL;
496 guint success = 0; /* no. of successfull downloads */
497
498 /* bail out if we are already in an update process */
499 if (g_mutex_trylock(&tle_in_progress) == FALSE)
500 {
501 sat_log_log(SAT_LOG_LEVEL_ERROR,
502 _("%s: TLE update is already running. Aborting."),
503 __func__);
504
505 return;
506 }
507
508 /* get server, proxy, and list of files */
509 proxy = sat_cfg_get_str(SAT_CFG_STR_TLE_PROXY);
510
511 /* avoid empty proxy string (bug in <2.2) */
512 if (proxy != NULL && strlen(proxy) == 0)
513 {
514 sat_cfg_reset_str(SAT_CFG_STR_TLE_PROXY);
515 g_free(proxy);
516 proxy = NULL;
517 }
518
519 files_tmp = sat_cfg_get_str(SAT_CFG_STR_TLE_URLS);
520 files = g_strsplit(files_tmp, ";", 0);
521 numfiles = g_strv_length(files);
522
523 if (numfiles < 1)
524 {
525 sat_log_log(SAT_LOG_LEVEL_ERROR,
526 _("%s: No files to fetch from network."), __func__);
527
528 /* set activity string, so user knows why nothing happens */
529 if (!silent && (label1 != NULL))
530 {
531 gtk_label_set_text(GTK_LABEL(label1),
532 _("No files to fetch from network"));
533 }
534 }
535 else
536 {
537 /* initialise progress bar */
538 if (!silent && (progress != NULL))
539 start = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(progress));
540
541 #ifndef WIN32
542 /* initialise curl */
543 curl = curl_easy_init();
544 if (proxy != NULL)
545 curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
546
547 curl_easy_setopt(curl, CURLOPT_USERAGENT, "gpredict/curl");
548 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
549 #endif
550
551 /* get files */
552 for (i = 0; i < numfiles; i++)
553 {
554 /* set URL */
555 curfile = g_strdup(files[i]);
556 #ifndef WIN32
557 curl_easy_setopt(curl, CURLOPT_URL, curfile);
558 #endif
559
560 /* set activity message */
561 if (!silent && (label1 != NULL))
562 {
563
564 text = g_strdup_printf(_("Fetching %s"), files[i]);
565 gtk_label_set_text(GTK_LABEL(label1), text);
566 g_free(text);
567
568 /* Force the drawing queue to be processed otherwise there will
569 not be any visual feedback, ie. frozen GUI
570 - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602
571 */
572 while (g_main_context_iteration(NULL, FALSE));
573 }
574
575 /* create local cache file ~/.config/Gpredict/satdata/cache/file-%d.tle */
576 userconfdir = get_user_conf_dir();
577 locfile = g_strdup_printf("%s%ssatdata%scache%sfile-%d.tle",
578 userconfdir, G_DIR_SEPARATOR_S,
579 G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S, i);
580 outfile = g_fopen(locfile, "wb");
581 if (outfile != NULL)
582 {
583 #ifdef WIN32
584 res = win32_fetch(curfile, outfile, proxy, "gpredict/win32");
585 if (res != 0)
586 {
587 sat_log_log(SAT_LOG_LEVEL_ERROR,
588 _("%s: Error fetching %s (%x)"),
589 __func__, curfile, res);
590 }
591 else
592 {
593 sat_log_log(SAT_LOG_LEVEL_INFO,
594 _("%s: Successfully fetched %s"),
595 __func__, curfile);
596 success++;
597 }
598 #else
599 curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
600 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_func);
601
602 /* get file */
603 res = curl_easy_perform(curl);
604
605 if (res != CURLE_OK)
606 {
607 sat_log_log(SAT_LOG_LEVEL_ERROR,
608 _("%s: Error fetching %s (%s)"),
609 __func__, curfile, curl_easy_strerror(res));
610 }
611 else
612 {
613 sat_log_log(SAT_LOG_LEVEL_INFO,
614 _("%s: Successfully fetched %s"),
615 __func__, curfile);
616 success++;
617 }
618 #endif
619 fclose(outfile);
620 }
621 else
622 {
623 sat_log_log(SAT_LOG_LEVEL_INFO,
624 _("%s: Failed to open %s preventing update"),
625 __func__, locfile);
626 }
627 /* update progress indicator */
628 if (!silent && (progress != NULL))
629 {
630
631 /* complete download corresponds to 50% */
632 fraction = start + (0.5 - start) * i / (1.0 * numfiles);
633 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress),
634 fraction);
635
636 /* Force the drawing queue to be processed otherwise there will
637 not be any visual feedback, ie. frozen GUI
638 - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602
639 */
640 while (g_main_context_iteration(NULL, FALSE));
641 }
642
643 g_free(userconfdir);
644 g_free(curfile);
645 g_free(locfile);
646 }
647
648 #ifndef WIN32
649 curl_easy_cleanup(curl);
650 #endif
651
652 /* continue update if we have fetched at least one file */
653 if (success > 0)
654 {
655 sat_log_log(SAT_LOG_LEVEL_INFO,
656 _("%s: Fetched %d files from network; updating..."),
657 __func__, success);
658 /* call update_from_files */
659 cache = sat_file_name("cache");
660 tle_update_from_files(cache, NULL, silent, progress, label1,
661 label2);
662 g_free(cache);
663 }
664 else
665 {
666 sat_log_log(SAT_LOG_LEVEL_ERROR,
667 _
668 ("%s: Could not fetch TLE files from network. Aborting."),
669 __func__);
670 }
671
672 }
673
674 /* clear cache and memory */
675 g_strfreev(files);
676 g_free(files_tmp);
677 if (proxy != NULL)
678 g_free(proxy);
679
680 /* open cache */
681 cache = sat_file_name("cache");
682 dir = g_dir_open(cache, 0, &err);
683
684 if (err != NULL)
685 {
686 /* send an error message */
687 sat_log_log(SAT_LOG_LEVEL_ERROR,
688 _("%s: Error opening %s (%s)"),
689 __func__, dir, err->message);
690 g_clear_error(&err);
691 }
692 else
693 {
694 /* delete files in cache one by one */
695 while ((fname = g_dir_read_name(dir)) != NULL)
696 {
697
698 locfile = g_strconcat(cache, G_DIR_SEPARATOR_S, fname, NULL);
699 if (g_remove(locfile))
700 sat_log_log(SAT_LOG_LEVEL_ERROR,
701 _("%s: Failed to remove %s"), __func__, locfile);
702 g_free(locfile);
703 }
704 /* close cache */
705 g_dir_close(dir);
706 }
707
708 g_free(cache);
709 g_mutex_unlock(&tle_in_progress);
710 }
711
712 #ifndef WIN32
713 /**
714 * Write TLE data block to file.
715 *
716 * @param ptr Pointer to the data block to be written.
717 * @param size Size of data block.
718 * @param nmemb Size multiplier?
719 * @param stream Pointer to the file handle.
720 * @return The number of bytes actually written.
721 *
722 * This function writes the received data to the file pointed to by stream.
723 * It is used as write callback by to curl exec function.
724 */
my_write_func(void * ptr,size_t size,size_t nmemb,FILE * stream)725 static size_t my_write_func(void *ptr, size_t size, size_t nmemb,
726 FILE * stream)
727 {
728 /*** FIXME: TBC whether this works in wintendo */
729 return fwrite(ptr, size, nmemb, stream);
730 }
731 #endif
732
733 /**
734 * Check whether file is TLE file.
735 * @param dir The directory.
736 * @param fnam The file name.
737 *
738 * This function checks whether the file with path dir/fnam is a potential
739 * TLE file. Checks performed:
740 * - It is a real file
741 * - suffix is .txt or .tle
742 */
is_tle_file(const gchar * dir,const gchar * fnam)743 static gboolean is_tle_file(const gchar * dir, const gchar * fnam)
744 {
745 gchar *path;
746 gchar *fname_lower;
747 gboolean fileIsOk = FALSE;
748
749 path = g_strconcat(dir, G_DIR_SEPARATOR_S, fnam, NULL);
750 fname_lower = g_ascii_strdown(fnam, -1);
751
752 if (g_file_test(path, G_FILE_TEST_IS_REGULAR) &&
753 (g_str_has_suffix(fname_lower, ".tle") ||
754 g_str_has_suffix(fname_lower, ".txt")))
755 {
756 fileIsOk = TRUE;
757 }
758 g_free(fname_lower);
759 g_free(path);
760
761 return fileIsOk;
762 }
763
764 /**
765 * Read fresh TLE data into hash table.
766 *
767 * @param dir The directory to read from.
768 * @param fnam The name of the file to read from.
769 * @param fresh_data Hash table where the data should be stored.
770 * @return The number of satellites successfully read.
771 *
772 * This function will read fresh TLE data from local files into memory.
773 * If there is a saetllite category (.cat file) with the same name as the
774 * input file it will also update the satellites in that category.
775 */
read_fresh_tle(const gchar * dir,const gchar * fnam,GHashTable * data)776 static gint read_fresh_tle(const gchar * dir, const gchar * fnam,
777 GHashTable * data)
778 {
779 new_tle_t *ntle;
780 tle_t tle;
781 gchar *path;
782 gchar tle_str[3][80];
783 gchar tle_working[3][80];
784 gchar linetmp[80];
785 guint linesneeded = 3;
786 gchar catstr[6];
787 gchar idstr[7] = "\0\0\0\0\0\0\0", idyearstr[3];
788 gchar *b;
789 FILE *fp;
790 gint retcode = 0;
791 guint catnr, i, idyear;
792 guint *key = NULL;
793
794 /* category sync related */
795 gchar *catname, *catpath, *buff, **buffv;
796 FILE *catfile;
797 gchar category[80];
798 gboolean catsync = FALSE; /* whether .cat file should be synced. NB: not effective since 1.4 */
799
800 /*
801 Normal cases to check
802 1. 3 line tle file as in amatuer.txt from celestrak
803 2. 2 line tle file as in .... from celestrak
804
805 corner cases to check
806 1. 3 line tle with something at the end. (nasa.all from amsat)
807 2. 2 line tle with only one in the file
808 3. 2 line tle file reading the last one.
809 */
810
811 path = g_strconcat(dir, G_DIR_SEPARATOR_S, fnam, NULL);
812 fp = g_fopen(path, "r");
813 if (fp != NULL)
814 {
815 /* Prepare .cat file for sync while we read data */
816 buffv = g_strsplit(fnam, ".", 0);
817 catname = g_strconcat(buffv[0], ".cat", NULL);
818 g_strfreev(buffv);
819 catpath = sat_file_name(catname);
820 g_free(catname);
821
822 /* read category name for catfile */
823 catfile = g_fopen(catpath, "r");
824 if (catfile != NULL)
825 {
826 b = fgets(category, 80, catfile);
827 if (b == NULL)
828 {
829 sat_log_log(SAT_LOG_LEVEL_ERROR,
830 _("%s:%s: There is no category in %s"),
831 __FILE__, __func__, catpath);
832 }
833 fclose(catfile);
834 catsync = TRUE;
835 }
836 else
837 {
838 /* There is no category with this name (could be update from custom file) */
839 sat_log_log(SAT_LOG_LEVEL_INFO,
840 _("%s:%s: There is no category called %s"),
841 __FILE__, __func__, fnam);
842 }
843
844 /* reopen a new catfile and write category name */
845 if (catsync)
846 {
847 catfile = g_fopen(catpath, "w");
848 if (catfile != NULL)
849 {
850 fputs(category, catfile);
851 }
852 else
853 {
854 catsync = FALSE;
855 sat_log_log(SAT_LOG_LEVEL_ERROR,
856 _
857 ("%s:%s: Could not reopen .cat file while reading TLE from %s"),
858 __FILE__, __func__, fnam);
859 }
860
861 /* .cat file now contains the category name;
862 satellite catnums will be added during update in the while loop */
863 }
864
865 /* set b to non-null as a flag */
866 b = path;
867
868 /* read lines from tle file */
869 while (fgets(linetmp, 80, fp))
870 {
871 /*read in the number of lines needed to potentially get to a new tle */
872 switch (linesneeded)
873 {
874 case 3:
875 strncpy(tle_working[0], linetmp, 80);
876 tle_working[0][79] = 0; // make coverity happy
877 /* b is being used a flag here
878 if b==NULL then we only have one line read in
879 and there is no way we have a full tle as there
880 is only one line in the buffer.
881 A TLE must be two or three lines.
882 */
883 b = fgets(tle_working[1], 80, fp);
884 if (b == NULL)
885 {
886 /* make sure there is no junk in tle_working[1] */
887 tle_working[1][0] = '\0';
888 break;
889 }
890 /* make sure there is no junk in tle_working[2] */
891 if (fgets(tle_working[2], 80, fp) == NULL)
892 {
893 tle_working[2][0] = '\0';
894 }
895
896 break;
897 case 2:
898 strncpy(tle_working[0], tle_working[2], 80);
899 strncpy(tle_working[1], linetmp, 80);
900 /* make sure there is no junk in tle_working[2] */
901 if (fgets(tle_working[2], 80, fp) == NULL)
902 {
903 tle_working[2][0] = '\0';
904 }
905 break;
906 case 1:
907 strncpy(tle_working[0], tle_working[1], 80);
908 strncpy(tle_working[1], tle_working[2], 80);
909 strncpy(tle_working[2], linetmp, 80);
910 tle_working[2][79] = 0; // make coverity happy
911 break;
912 default:
913 sat_log_log(SAT_LOG_LEVEL_ERROR,
914 _
915 ("%s:%s: Something wrote linesneeded to an illegal value %d"),
916 __FILE__, __func__, linesneeded);
917 break;
918 }
919 /* b can only be null if there is only one line in the buffer */
920 /* a tle must be two or three */
921 if (b == NULL)
922 {
923 break;
924 }
925 /* remove leading and trailing whitespace to be more forgiving */
926 g_strstrip(tle_working[0]);
927 g_strstrip(tle_working[1]);
928 g_strstrip(tle_working[2]);
929
930 /* there are three possibilities at this point */
931 /* first is that line 0 is a name and normal text for three line element and that lines 1 and 2
932 are the corresponding tle */
933 /* second is that line 0 and line 1 are a tle for a bare tle */
934 /* third is that neither of these is true and we are consuming either text at the top of the
935 file or a text file that happens to be in the update directory
936 */
937 if ((tle_working[1][0] == '1') &&
938 (tle_working[2][0] == '2') &&
939 Checksum_Good(tle_working[1]) && Checksum_Good(tle_working[2]))
940 {
941 sat_log_log(SAT_LOG_LEVEL_DEBUG,
942 _("%s:%s: Processing a three line TLE"),
943 __FILE__, __func__);
944
945 /* it appears that the first line may be a name followed by a tle */
946 strncpy(tle_str[0], tle_working[0], 80);
947 tle_str[0][79] = 0; // make coverity happy
948 strncpy(tle_str[1], tle_working[1], 80);
949 strncpy(tle_str[2], tle_working[2], 80);
950 /* we consumed three lines so we need three lines */
951 linesneeded = 3;
952
953 }
954 else if ((tle_working[0][0] == '1') &&
955 (tle_working[1][0] == '2') &&
956 Checksum_Good(tle_working[0]) &&
957 Checksum_Good(tle_working[1]))
958 {
959 sat_log_log(SAT_LOG_LEVEL_DEBUG,
960 _("%s:%s: Processing a bare two line TLE"),
961 __FILE__, __func__);
962
963 /* first line appears to belong to the start of bare TLE */
964 /* put in a dummy name of form yyyy-nnaa base on international id */
965 /* this special form will be overwritten if a three line tle ever has another name */
966
967 strncpy(idstr, &tle_working[0][11], 6);
968 g_strstrip(idstr);
969 strncpy(idyearstr, &tle_working[0][9], 2);
970 idstr[6] = '\0';
971 idyearstr[2] = '\0';
972 idyear = g_ascii_strtod(idyearstr, NULL);
973
974 /* there is a two digit year field that started around sputnik */
975 if (idyear >= 57)
976 idyear += 1900;
977 else
978 idyear += 2000;
979
980 snprintf(tle_str[0], 79, "%d-%s", idyear, idstr);
981 strncpy(tle_str[1], tle_working[0], 80);
982 strncpy(tle_str[2], tle_working[1], 80);
983
984 /* we consumed two lines so we need two lines */
985 linesneeded = 2;
986 }
987 else
988 {
989 /* we appear to have junk
990 read another line in and do nothing else */
991 linesneeded = 1;
992 /* skip back to beginning of loop */
993 continue;
994 }
995
996
997 tle_str[1][69] = '\0';
998 tle_str[2][69] = '\0';
999
1000 /* copy catnum and convert to integer */
1001 for (i = 2; i < 7; i++)
1002 {
1003 catstr[i - 2] = tle_str[1][i];
1004 }
1005 catstr[5] = '\0';
1006 catnr = (guint) g_ascii_strtod(catstr, NULL);
1007
1008
1009 if (Get_Next_Tle_Set(tle_str, &tle) != 1)
1010 {
1011 /* TLE data not good */
1012 sat_log_log(SAT_LOG_LEVEL_ERROR,
1013 _("%s:%s: Invalid data for %d"),
1014 __FILE__, __func__, catnr);
1015 }
1016 else
1017 {
1018 if (catsync)
1019 {
1020 /* store catalog number in catfile */
1021 buff = g_strdup_printf("%d\n", catnr);
1022 fputs(buff, catfile);
1023 g_free(buff);
1024 }
1025
1026 /* add data to hash table */
1027 key = g_try_new0(guint, 1);
1028 *key = catnr;
1029
1030 ntle = g_hash_table_lookup(data, key);
1031
1032 /* check if satellite already in hash table */
1033 if (ntle == NULL)
1034 {
1035
1036 /* create new_tle structure */
1037 ntle = g_try_new(new_tle_t, 1);
1038 ntle->catnum = catnr;
1039 ntle->epoch = tle.epoch;
1040 ntle->status = tle.status;
1041 ntle->satname = g_strdup(tle.sat_name);
1042 ntle->line1 = g_strdup(tle_str[1]);
1043 ntle->line2 = g_strdup(tle_str[2]);
1044 ntle->srcfile = g_strdup(fnam);
1045 ntle->isnew = TRUE; /* flag will be reset when using data */
1046
1047 g_hash_table_insert(data, key, ntle);
1048 retcode++;
1049 }
1050 else
1051 {
1052 /* satellite is already in hash */
1053 /* apply various merge routines */
1054
1055 /* time merge */
1056 if (ntle->epoch == tle.epoch)
1057 {
1058 /* if satellite epoch has the same time, merge status as appropriate */
1059 if (ntle->status != tle.status)
1060 {
1061 /* log if there is something funny about the data coming in */
1062 sat_log_log(SAT_LOG_LEVEL_WARN,
1063 _
1064 ("%s:%s: Two different statuses for %d (%s) at the same time."),
1065 __FILE__, __func__, ntle->catnum,
1066 ntle->satname);
1067 if (tle.status != OP_STAT_UNKNOWN)
1068 ntle->status = tle.status;
1069 }
1070 }
1071 else if (ntle->epoch < tle.epoch)
1072 {
1073 /* if the satellite in the hash is older than
1074 the one just loaded, copy the values over. */
1075
1076 ntle->catnum = catnr;
1077 ntle->epoch = tle.epoch;
1078 ntle->status = tle.status;
1079 g_free(ntle->line1);
1080 ntle->line1 = g_strdup(tle_str[1]);
1081 g_free(ntle->line2);
1082 ntle->line2 = g_strdup(tle_str[2]);
1083 g_free(ntle->srcfile);
1084 ntle->srcfile = g_strdup(fnam);
1085 ntle->isnew = TRUE; /* flag will be reset when using data */
1086 }
1087
1088 /* merge based on name */
1089 if (is_computer_generated_name(ntle->satname) &&
1090 !is_computer_generated_name(tle_str[0]))
1091 {
1092 g_free(ntle->satname);
1093 ntle->satname = g_strdup(tle.sat_name);
1094 }
1095
1096 /* free the key since we do not commit it to the cache */
1097 g_free(key);
1098 }
1099 }
1100
1101 }
1102
1103 if (catsync)
1104 {
1105 /* close category file */
1106 fclose(catfile);
1107 }
1108
1109 g_free(catpath);
1110
1111 /* close input TLE file */
1112 fclose(fp);
1113 }
1114
1115 else
1116 {
1117 sat_log_log(SAT_LOG_LEVEL_ERROR,
1118 _("%s:%s: Failed to open %s"), __FILE__, __func__, path);
1119 }
1120 g_free(path);
1121
1122 return retcode;
1123 }
1124
1125 /**
1126 * Update TLE data in a file.
1127 *
1128 * @param ldname Directory name for gpredict tle files.
1129 * @param fname The name of the TLE file.
1130 * @param data The hash table containing the fresh data.
1131 * @param sat_upd OUT: number of sats updated.
1132 * @param sat_ski OUT: number of sats skipped.
1133 * @param sat_nod OUT: number of sats for which no data found
1134 * @param sat_tot OUT: total number of sats
1135 *
1136 * For each satellite in the TLE file ldname/fnam, this function
1137 * checks whether there is any newer data available in the hash table.
1138 * If yes, the function writes the fresh data to temp_file, if no, the
1139 * old data is copied to temp_file.
1140 * When all sats have been copied ldname/fnam is deleted and temp_file
1141 * is renamed to ldname/fnam.
1142 */
update_tle_in_file(const gchar * ldname,const gchar * fname,GHashTable * data,guint * sat_upd,guint * sat_ski,guint * sat_nod,guint * sat_tot)1143 static void update_tle_in_file(const gchar * ldname,
1144 const gchar * fname,
1145 GHashTable * data,
1146 guint * sat_upd,
1147 guint * sat_ski,
1148 guint * sat_nod, guint * sat_tot)
1149 {
1150 gchar *path;
1151 guint updated = 0; /* number of updated sats */
1152 guint nodata = 0; /* no sats for which no fresh data available */
1153 guint skipped = 0; /* no. sats where fresh data is older */
1154 guint total = 0; /* total no. of sats in gpredict tle file */
1155 gchar **catstr;
1156 guint catnr;
1157 guint *key = NULL;
1158 tle_t tle;
1159 new_tle_t *ntle;
1160 op_stat_t status;
1161 GError *error = NULL;
1162 GKeyFile *satdata;
1163 gchar *tlestr1, *tlestr2, *rawtle, *satname, *satnickname;
1164 gboolean updateddata;
1165
1166 /* get catalog number for this satellite */
1167 catstr = g_strsplit(fname, ".sat", 0);
1168 catnr = (guint) g_ascii_strtod(catstr[0], NULL);
1169
1170 /* see if we have new data for this satellite */
1171 key = g_try_new0(guint, 1);
1172 *key = catnr;
1173 ntle = (new_tle_t *) g_hash_table_lookup(data, key);
1174 g_free(key);
1175
1176 if (ntle == NULL)
1177 {
1178 /* no new data found for this sat => obsolete */
1179 nodata++;
1180
1181 /* check if obsolete sats should be deleted */
1182 /**** FIXME: This is dangereous, so we omit it */
1183 sat_log_log(SAT_LOG_LEVEL_INFO,
1184 _
1185 ("%s: No new TLE data found for %d. Satellite might be obsolete."),
1186 __func__, catnr);
1187 }
1188 else
1189 {
1190 /* open input file (file containing old tle) */
1191 path = g_strconcat(ldname, G_DIR_SEPARATOR_S, fname, NULL);
1192 satdata = g_key_file_new();
1193 if (!g_key_file_load_from_file
1194 (satdata, path, G_KEY_FILE_KEEP_COMMENTS, &error))
1195 {
1196 sat_log_log(SAT_LOG_LEVEL_ERROR,
1197 _("%s: Error loading %s (%s)"),
1198 __func__, path, error->message);
1199 g_clear_error(&error);
1200
1201 skipped++;
1202 }
1203 else
1204 {
1205 /* This satellite is not new */
1206 ntle->isnew = FALSE;
1207
1208 /* get TLE data */
1209 tlestr1 =
1210 g_key_file_get_string(satdata, "Satellite", "TLE1", NULL);
1211 if (error != NULL)
1212 {
1213 sat_log_log(SAT_LOG_LEVEL_ERROR,
1214 _("%s: Error reading TLE line 2 from %s (%s)"),
1215 __func__, path, error->message);
1216 g_clear_error(&error);
1217 }
1218 tlestr2 =
1219 g_key_file_get_string(satdata, "Satellite", "TLE2", NULL);
1220 if (error != NULL)
1221 {
1222 sat_log_log(SAT_LOG_LEVEL_ERROR,
1223 _("%s: Error reading TLE line 2 from %s (%s)"),
1224 __func__, path, error->message);
1225 g_clear_error(&error);
1226 }
1227
1228 /* get name data */
1229 satname =
1230 g_key_file_get_string(satdata, "Satellite", "NAME", NULL);
1231 satnickname =
1232 g_key_file_get_string(satdata, "Satellite", "NICKNAME", NULL);
1233
1234 /* get status data */
1235 if (g_key_file_has_key(satdata, "Satellite", "STATUS", NULL))
1236 {
1237 status =
1238 g_key_file_get_integer(satdata, "Satellite", "STATUS",
1239 NULL);
1240 }
1241 else
1242 {
1243 status = OP_STAT_UNKNOWN;
1244 }
1245
1246 rawtle = g_strconcat(tlestr1, tlestr2, NULL);
1247
1248 if (!Good_Elements(rawtle))
1249 {
1250 sat_log_log(SAT_LOG_LEVEL_WARN,
1251 _("%s: Current TLE data for %d appears to be bad"),
1252 __func__, catnr);
1253 /* set epoch to zero so it gets overwritten */
1254 tle.epoch = 0;
1255 }
1256 else
1257 {
1258 Convert_Satellite_Data(rawtle, &tle);
1259 }
1260 g_free(tlestr1);
1261 g_free(tlestr2);
1262 g_free(rawtle);
1263
1264 /* Initialize flag for update */
1265 updateddata = FALSE;
1266
1267 if (ntle->satname != NULL)
1268 {
1269 /* when a satellite first appears in the elements it is sometimes refered to by the
1270 international designator which is awkward after it is given a name */
1271 if (!is_computer_generated_name(ntle->satname))
1272 {
1273 if (is_computer_generated_name(satname))
1274 {
1275 sat_log_log(SAT_LOG_LEVEL_INFO,
1276 _("%s: Data for %d updated for name."),
1277 __func__, catnr);
1278 g_key_file_set_string(satdata, "Satellite", "NAME",
1279 ntle->satname);
1280 updateddata = TRUE;
1281 }
1282
1283 /* FIXME what to do about nickname Possibilities: */
1284 /* clobber with name */
1285 /* clobber if nickname and name were same before */
1286 /* clobber if international designator */
1287 if (is_computer_generated_name(satnickname))
1288 {
1289 sat_log_log(SAT_LOG_LEVEL_INFO,
1290 _
1291 ("%s: Data for %d updated for nickname."),
1292 __func__, catnr);
1293 g_key_file_set_string(satdata, "Satellite", "NICKNAME",
1294 ntle->satname);
1295 updateddata = TRUE;
1296 }
1297 }
1298 }
1299
1300 g_free(satname);
1301 g_free(satnickname);
1302
1303 if (tle.epoch < ntle->epoch)
1304 {
1305 /* new data is newer than what we already have */
1306 /* store new data */
1307 sat_log_log(SAT_LOG_LEVEL_INFO,
1308 _("%s: Data for %d updated for tle."),
1309 __func__, catnr);
1310 g_key_file_set_string(satdata, "Satellite", "TLE1",
1311 ntle->line1);
1312 g_key_file_set_string(satdata, "Satellite", "TLE2",
1313 ntle->line2);
1314 g_key_file_set_integer(satdata, "Satellite", "STATUS",
1315 ntle->status);
1316 updateddata = TRUE;
1317
1318 }
1319 else if (tle.epoch == ntle->epoch)
1320 {
1321 if ((status != ntle->status) &&
1322 (ntle->status != OP_STAT_UNKNOWN))
1323 {
1324 sat_log_log(SAT_LOG_LEVEL_INFO,
1325 _
1326 ("%s: Data for %d updated for operational status."),
1327 __func__, catnr);
1328 g_key_file_set_integer(satdata, "Satellite", "STATUS",
1329 ntle->status);
1330 updateddata = TRUE;
1331 }
1332 }
1333
1334 if (updateddata == TRUE)
1335 {
1336 if (gpredict_save_key_file(satdata, path))
1337 skipped++;
1338 else
1339 updated++;
1340 }
1341 else
1342 {
1343 skipped++;
1344 }
1345 }
1346
1347 g_key_file_free(satdata);
1348 g_free(path);
1349 }
1350 g_strfreev(catstr);
1351
1352 /* update out parameters */
1353 *sat_upd = updated;
1354 *sat_ski = skipped;
1355 *sat_nod = nodata;
1356 *sat_tot = total;
1357 }
1358
1359
1360 const gchar *freq_to_str[TLE_AUTO_UPDATE_NUM] = {
1361 N_("Never"),
1362 N_("Monthly"),
1363 N_("Weekly"),
1364 N_("Daily")
1365 };
1366
tle_update_freq_to_str(tle_auto_upd_freq_t freq)1367 const gchar *tle_update_freq_to_str(tle_auto_upd_freq_t freq)
1368 {
1369 if ((freq <= TLE_AUTO_UPDATE_NEVER) || (freq >= TLE_AUTO_UPDATE_NUM))
1370 freq = TLE_AUTO_UPDATE_NEVER;
1371
1372 return _(freq_to_str[freq]);
1373 }
1374
1375 /**
1376 * Determine if name is generic.
1377 *
1378 * @param satname The satellite name that might be old.
1379 *
1380 * This function determines if the satellite name is generic. Examples
1381 * of this are the names YYYY-NNNAAA international ID names used by
1382 * Celestrak. Also space-track.org will give items names of OBJECT A as
1383 * well until the name is advertised.
1384 */
is_computer_generated_name(gchar * satname)1385 static gboolean is_computer_generated_name(gchar * satname)
1386 {
1387 /* celestrak generic satellite name */
1388 if (g_regex_match_simple("\\d{4,}-\\d{3,}", satname, 0, 0))
1389 {
1390 return (TRUE);
1391 }
1392 /* space-track generic satellite name */
1393 if (g_regex_match_simple("OBJECT", satname, 0, 0))
1394 {
1395 return (TRUE);
1396 }
1397 return (FALSE);
1398 }
1399