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