/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* Gpredict: Real-time satellite tracking and orbit prediction program Copyright (C) 2001-2007 Alexandru Csete, OZ9AEC. Authors: Alexandru Csete Comments, questions and bugreports should be submitted via http://sourceforge.net/projects/groundstation/ More details can be found at the project home page: http://groundstation.sourceforge.net/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, visit http://www.fsf.org/ */ #include #include #include #include #ifdef HAVE_CONFIG_H # include #endif #if HAVE_LIBCURL # include #endif #include "sgpsdp/sgp4sdp4.h" #include "sat-log.h" #include "sat-cfg.h" #include "tle-lookup.h" #include "tle-update.h" /* Flag indicating whether TLE update is in progress. This should avoid multiple attempts to update TLE, e.g. user starts update from menubar while automatic update is in progress */ static gboolean tle_in_progress = FALSE; /* private function prototypes */ #if 0 static void copy_tle (tle_t *source, tle_t *target); static gint compare_epoch (tle_t *tle1, tle_t *tle2); static tle_t *gchar_to_tle (tle_type_t type, const gchar *lines); static gchar *tle_to_gchar (tle_type_t type, tle_t *tle); #endif static size_t my_write_func (void *ptr, size_t size, size_t nmemb, FILE *stream); static gint read_fresh_tle (const gchar *dir, const gchar *fnam, GHashTable *data); static void update_tle_in_file (const gchar *ldname, const gchar *fname, GHashTable *data, guint *sat_upd, guint *sat_ski, guint *sat_nod, guint *sat_tot); /** \bief Free a new_tle_t structure. */ static void free_new_tle (gpointer data) { new_tle_t *tle; tle = (new_tle_t *) data; g_free (tle->satname); g_free (tle->line1); g_free (tle->line2); g_free (tle); } /** \brief Update TLE files from local files. * \param dir Directory where files are located. * \param filter File filter, e.g. *.txt (not used at the moment!) * \param silent TRUE if function should execute without graphical status indicator. * \param label1 Activity label (can be NULL) * \param label2 Statistics label (can be NULL) * \param progress Pointer to progress indicator. * \param init_prgs Initial value of progress indicator, e.g 0.5 if we are updating * from network. * * This function is used to update the TLE data from local files. * * Functional description: TBD * */ void tle_update_from_files (const gchar *dir, const gchar *filter, gboolean silent, GtkWidget *progress, GtkWidget *label1, GtkWidget *label2) { GHashTable *data; /* hash table with fresh TLE data */ GDir *cache_dir; /* directory to scan fresh TLE */ GDir *loc_dir; /* directory for gpredict TLE files */ GError *err = NULL; gchar *text; gchar *ldname; const gchar *fnam; guint num = 0; guint updated,updated_tmp; guint skipped,skipped_tmp; guint nodata,nodata_tmp; guint total,total_tmp; gdouble fraction = 0.0; gdouble start = 0.0; /* create hash table */ data = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, free_new_tle); /* open directory and read files one by one */ cache_dir = g_dir_open (dir, 0, &err); if (err != NULL) { /* send an error message */ sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Error opening directory %s (%s)"), __FUNCTION__, dir, err->message); /* insert error message into the status string, too */ if (!silent && (label1 != NULL)) { text = g_strdup_printf (_("ERROR opening directory %s\n%s"), dir, err->message); gtk_label_set_markup (GTK_LABEL (label1), text); g_free (text); } g_clear_error (&err); err = NULL; } else { /* read fresh tle files into memory */ while ((fnam = g_dir_read_name (cache_dir)) != NULL) { /* status message */ if (!silent && (label1 != NULL)) { text = g_strdup_printf (_("Reading data from %s"), fnam); gtk_label_set_text (GTK_LABEL (label1), text); g_free (text); /* Force the drawing queue to be processed otherwise there will not be any visual feedback, ie. frozen GUI - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602 */ while (g_main_context_iteration (NULL, FALSE)); /* give user a chance to follow progress */ g_usleep (G_USEC_PER_SEC / 10); } /* now, do read the fresh data */ num = read_fresh_tle (dir, fnam, data); if (num < 1) { sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: No valid TLE data found in %s"), __FUNCTION__, fnam); } else { sat_log_log (SAT_LOG_LEVEL_MSG, _("%s: Read %d sats from %s into memory"), __FUNCTION__, num, fnam); } } /* close directory since we don't need it anymore */ g_dir_close (cache_dir); /* store number of available sats */ num = tle_lookup_count (); /* now it is time to read gpredict-tle into memory, too */ ldname = g_strconcat (g_get_home_dir (), G_DIR_SEPARATOR_S, ".gpredict2", G_DIR_SEPARATOR_S, "tle", NULL); /* open directory and read files one by one */ loc_dir = g_dir_open (ldname, 0, &err); if (err != NULL) { /* send an error message */ sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Error opening directory %s (%s)"), __FUNCTION__, dir, err->message); /* insert error message into the status string, too */ if (!silent && (label1 != NULL)) { text = g_strdup_printf (_("ERROR opening directory %s\n%s"), dir, err->message); gtk_label_set_markup (GTK_LABEL (label1), text); g_free (text); } g_clear_error (&err); err = NULL; } else { /* clear statistics */ updated = 0; skipped = 0; nodata = 0; total = 0; /* get initial value of progress indicator */ if (progress != NULL) start = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (progress)); /* update TLE files one by one */ while ((fnam = g_dir_read_name (loc_dir)) != NULL) { /* skip 'cache' directory */ if (g_ascii_strcasecmp (fnam, "cache")) { /* clear stat bufs */ updated_tmp = 0; skipped_tmp = 0; nodata_tmp = 0; total_tmp = 0; /* update TLE data in this file */ update_tle_in_file (ldname, fnam, data, &updated_tmp, &skipped_tmp, &nodata_tmp, &total_tmp); /* update statistics */ updated += updated_tmp; skipped += skipped_tmp; nodata += nodata_tmp; total += total_tmp; if (!silent) { if (label1 != NULL) { gtk_label_set_text (GTK_LABEL (label1), _("Updating data...")); } if (label2 != NULL) { text = g_strdup_printf (_("Satellites updated:\t %d\n"\ "Satellites skipped:\t %d\n"\ "Missing Satellites:\t %d"), updated, skipped, nodata); gtk_label_set_text (GTK_LABEL (label2), text); g_free (text); } if (progress != NULL) { /* calculate and saturate fraction (number of sats in TLE files is greater than the number of sats in link table) */ fraction = start + (1.0-start) * ((gdouble) total) / ((gdouble) num); if (fraction >= 0.95) fraction = 0.98; gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), fraction); } /* Force the drawing queue to be processed otherwise there will not be any visual feedback, ie. frozen GUI - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602 */ while (g_main_context_iteration (NULL, FALSE)); /* give user a chance to follow progress */ g_usleep (G_USEC_PER_SEC / 10); } } } /* close directory handle */ g_dir_close (loc_dir); /* store time of update if we have updated something */ if (updated > 0) { GTimeVal tval; g_get_current_time (&tval); sat_cfg_set_int (SAT_CFG_INT_TLE_LAST_UPDATE, tval.tv_sec); } } g_free (ldname); sat_log_log (SAT_LOG_LEVEL_MSG, _("%s: TLE elements updated."), __FUNCTION__); } /* destroy hash tables */ g_hash_table_destroy (data); } /** \brief Update TLE files from network. * \param silent TRUE if function should execute without graphical status indicator. * \param progress Pointer to a GtkProgressBar progress indicator (can be NULL) * \param label1 GtkLabel for activity string. * \param label2 GtkLabel for statistics string. */ void tle_update_from_network (gboolean silent, GtkWidget *progress, GtkWidget *label1, GtkWidget *label2) { #if HAVE_LIBCURL gchar *server; gchar *proxy = NULL; gchar *files_tmp; gchar **files; guint numfiles,i; gchar *curfile; gchar *locfile; CURL *curl; CURLcode res; gboolean error = FALSE; gdouble fraction,start=0; FILE *outfile; GDir *dir; gchar *cache; const gchar *fname; gchar *text; GError *err = NULL; guint success = 0; /* no. of successfull downloads */ /* bail out if we are already in an update process */ if (tle_in_progress) return; tle_in_progress = TRUE; /* get server, proxy, and list of files */ server = sat_cfg_get_str (SAT_CFG_STR_TLE_SERVER); proxy = sat_cfg_get_str (SAT_CFG_STR_TLE_PROXY); files_tmp = sat_cfg_get_str (SAT_CFG_STR_TLE_FILES); files = g_strsplit (files_tmp, ";", 0); numfiles = g_strv_length (files); if (numfiles < 1) { sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: No files to fetch from network."), __FUNCTION__); /* set activity string, so user knows why nothing happens */ if (!silent && (label1 != NULL)) { gtk_label_set_text (GTK_LABEL (label1), _("No files to fetch from network")); } } else { /* initialise progress bar */ if (!silent && (progress != NULL)) start = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (progress)); /* initialise curl */ curl = curl_easy_init(); if (proxy != NULL) curl_easy_setopt (curl, CURLOPT_PROXY, proxy); curl_easy_setopt (curl, CURLOPT_USERAGENT, "gpredict/curl"); /* get files */ for (i = 0; i < numfiles; i++) { /* set URL */ curfile = g_strconcat (server, files[i], NULL); curl_easy_setopt (curl, CURLOPT_URL, curfile); /* set activity message */ if (!silent && (label1 != NULL)) { text = g_strdup_printf (_("Fetching %s"), files[i]); gtk_label_set_text (GTK_LABEL (label1), text); g_free (text); /* Force the drawing queue to be processed otherwise there will not be any visual feedback, ie. frozen GUI - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602 */ while (g_main_context_iteration (NULL, FALSE)); } /* create local file */ locfile = g_strconcat (g_get_home_dir (), G_DIR_SEPARATOR_S, ".gpredict2", G_DIR_SEPARATOR_S, "tle", G_DIR_SEPARATOR_S, "cache", G_DIR_SEPARATOR_S, files[i], NULL); outfile = g_fopen (locfile, "wb"); curl_easy_setopt (curl, CURLOPT_WRITEDATA, outfile); curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, my_write_func); /* get file */ res = curl_easy_perform (curl); if (res != CURLE_OK) { sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Error fetching %s (%s)"), __FUNCTION__, curfile, curl_easy_strerror (res)); error = TRUE; } else { sat_log_log (SAT_LOG_LEVEL_MSG, _("%s: Successfully fetched %s"), __FUNCTION__, curfile); success++; } /* update progress indicator */ if (!silent && (progress != NULL)) { /* complete download corresponds to 50% */ fraction = start + (0.5-start) * i / (2.0 * numfiles); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), fraction); /* Force the drawing queue to be processed otherwise there will not be any visual feedback, ie. frozen GUI - see Gtk+ FAQ http://www.gtk.org/faq/#AEN602 */ while (g_main_context_iteration (NULL, FALSE)); } g_free (curfile); g_free (locfile); fclose (outfile); } curl_easy_cleanup (curl); /* continue update if we have fetched at least one file */ if (success > 0) { /* call update_from_files */ cache = g_strconcat (g_get_home_dir (), G_DIR_SEPARATOR_S, ".gpredict2", G_DIR_SEPARATOR_S, "tle", G_DIR_SEPARATOR_S, "cache", NULL); tle_update_from_files (cache, NULL, silent, progress, label1, label2); g_free (cache); } } /* clear cache and memory */ g_free (server); g_strfreev (files); g_free (files_tmp); if (proxy != NULL) g_free (proxy); /* open cache */ cache = g_strconcat (g_get_home_dir (), G_DIR_SEPARATOR_S, ".gpredict2", G_DIR_SEPARATOR_S, "tle", G_DIR_SEPARATOR_S, "cache", NULL); dir = g_dir_open (cache, 0, &err); if (err != NULL) { /* send an error message */ sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Error opening %s (%s)"), __FUNCTION__, dir, err->message); g_clear_error (&err); } else { /* delete files in cache one by one */ while ((fname = g_dir_read_name (dir)) != NULL) { locfile = g_strconcat (cache, G_DIR_SEPARATOR_S, fname, NULL); g_remove (locfile); g_free (locfile); } /* close cache */ g_dir_close (dir); } g_free (cache); /* clear busy flag */ tle_in_progress = FALSE; #else sat_log_log (SAT_LOG_LEVEL_BUG, _("%s: This version of gpredict has been compiled without network support.\n" "To update TLE data, download NASA 2-line files manually, then run Update from files."), __FUNCTION__); #endif } /** \brief Write TLE data block to file. * \param ptr Pointer to the data block to be written. * \param size Size of data block. * \param nmemb Size multiplier? * \param stream Pointer to the file handle. * \return The number of bytes actually written. * * This function writes the received data to the file pointed to by stream. * It is used as write callback by to curl exec function. */ static size_t my_write_func (void *ptr, size_t size, size_t nmemb, FILE *stream) { /*** FIXME: TBC whether this works in wintendo */ return fwrite (ptr, size, nmemb, stream); } /** \brief Read fresh TLE data into hash table. * \param dir The directory to read from. * \param fnam The name of the file to read from. * \param fresh_data Hash table where the data should be stored. * \return The number of satellites successfully read. */ static gint read_fresh_tle (const gchar *dir, const gchar *fnam, GHashTable *data) { new_tle_t *ntle; tle_t tle; gchar *path; gchar tle_str[3][80]; gchar catstr[6]; FILE *fp; gint retcode = 0; guint catnr,i; guint *key = NULL; path = g_strconcat (dir, G_DIR_SEPARATOR_S, fnam, NULL); fp = g_fopen (path, "r"); if (fp != NULL) { /* read 3 lines at a time */ while (fgets (tle_str[0], 80, fp)) { /* read second and third lines */ fgets (tle_str[1], 80, fp); fgets (tle_str[2], 80, fp); /* copy catnum and convert to integer */ for (i = 2; i < 7; i++) { catstr[i-2] = tle_str[1][i]; } catstr[5] = '\0'; catnr = (guint) g_ascii_strtod (catstr, NULL); if (Get_Next_Tle_Set (tle_str, &tle) != 1) { /* TLE data not good */ sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s:%s: Invalid data for %d"), __FILE__, __FUNCTION__, catnr); } else { /* DATA OK, phew... */ /* sat_log_log (SAT_LOG_LEVEL_DEBUG, */ /* _("%s: Good data for %d"), */ /* __FUNCTION__, */ /* catnr); */ /* add data to hash table */ key = g_try_new0 (guint, 1); *key = catnr; if (g_hash_table_lookup (data, key) == NULL) { /* create new_tle structure */ ntle = g_try_new (new_tle_t, 1); ntle->epoch = tle.epoch; ntle->satname = g_strdup (tle_str[0]); ntle->line1 = g_strdup (tle_str[1]); ntle->line2 = g_strdup (tle_str[2]); g_hash_table_insert (data, key, ntle); retcode++; } else { g_free (key); } } } /* close input TLE file */ fclose (fp); } else { sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s:%s: Failed to open %s"), __FILE__, __FUNCTION__, path); } g_free (path); return retcode; } /** \brief Update TLE data in a file. * \param ldname Directory name for gpredict tle files. * \param fname The name of the TLE file. * \param data The hash table containing the fresh data. * \param sat_upd OUT: number of sats updated. * \param sat_ski OUT: number of sats skipped. * \param sat_nod OUT: number of sats for which no data found * \param sat_tot OUT: total number of sats * * For each satellite in the TLE file ldname/fnam, this function * checks whether there is any newer data available in the hash table. * If yes, the function writes the fresh data to temp_file, if no, the * old data is copied to temp_file. * When all sats have been copied ldname/fnam is deleted and temp_file * is renamed to ldname/fnam. */ static void update_tle_in_file (const gchar *ldname, const gchar *fname, GHashTable *data, guint *sat_upd, guint *sat_ski, guint *sat_nod, guint *sat_tot) { gchar *path; gchar *tmp_path; guint updated = 0; /* number of updated sats */ guint nodata = 0; /* no sats for which no fresh data available */ guint skipped = 0; /* no. sats where fresh data is older */ guint total = 0; /* total no. of sats in gpredict tle file */ gchar tle_str[3][80]; gchar catstr[6]; FILE *fp; FILE *tmp; guint catnr,i; guint *key = NULL; tle_t tle; new_tle_t *ntle; gint retcode = 0; /* open input file (file containing old tle) */ path = g_strconcat (ldname, G_DIR_SEPARATOR_S, fname, NULL); fp = g_fopen (path, "r"); if (fp != NULL) { /* open output temp file */ tmp_path = g_strconcat (ldname, G_DIR_SEPARATOR_S, "TLEUPDATE.tmp", NULL); tmp = g_fopen (tmp_path, "w"); if (tmp != NULL) { /* read 3 lines at a time */ while (fgets (tle_str[0], 80, fp)) { /* read second and third lines */ fgets (tle_str[1], 80, fp); fgets (tle_str[2], 80, fp); /* stats */ total++; /* copy catnum and convert to integer */ for (i = 2; i < 7; i++) { catstr[i-2] = tle_str[1][i]; } catstr[5] = '\0'; catnr = (guint) g_ascii_strtod (catstr, NULL); if (Get_Next_Tle_Set (tle_str, &tle) != 1) { /* TLE data not good */ sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s:%s: Original data for %d seems to be bad"), __FILE__, __FUNCTION__, catnr); } /* get data from hash table */ key = g_try_new0 (guint, 1); *key = catnr; ntle = (new_tle_t *) g_hash_table_lookup (data, key); g_free (key); if (ntle == NULL) { /* no new data found for this sat */ nodata++; /* write current data back to tle file */ fputs (tle_str[0], tmp); fputs (tle_str[1], tmp); fputs (tle_str[2], tmp); } else { /* new data is available for this sat */ if (tle.epoch < ntle->epoch) { /* fresh data is newer than original => update */ updated++; /* write new data to file */ fputs (ntle->satname, tmp); fputs (ntle->line1, tmp); fputs (ntle->line2, tmp); } else { /* fresh data is older than original => don't update */ skipped++; /* write current data back to tle file */ fputs (tle_str[0], tmp); fputs (tle_str[1], tmp); fputs (tle_str[2], tmp); } } } fclose (tmp); fclose (fp); /* if there is at least one updated sat */ if (updated > 0) { /* remove old tle file fp */ retcode = g_remove (path); if (retcode != 0) { /* could not remove file */ sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Could not remove %s (file open?)"), __FUNCTION__, path); } /* rename temp file tmp to old tle file */ retcode = g_rename (tmp_path, path); if (retcode != 0) { /* could not rename file */ sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Could not rename %s to %s"), __FUNCTION__, tmp_path, path); } } else { /* we have to remove the temp file */ g_remove (tmp_path); } } else { sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Failed to open temp file %s"), __FUNCTION__, tmp_path); /* close input TLE file */ fclose (fp); } /* close input TLE file */ //fclose (fp); /* free path to tmp file */ g_free (tmp_path); /* print stats */ sat_log_log (SAT_LOG_LEVEL_MSG, _("%s: Update statistics for %s (U/O/N/T): %d/%d/%d/%d"), __FUNCTION__, fname, updated, skipped, nodata, total); } else { sat_log_log (SAT_LOG_LEVEL_ERROR, _("%s: Failed to open %s"), __FUNCTION__, path); } g_free (path); /* update out parameters */ *sat_upd = updated; *sat_ski = skipped; *sat_nod = nodata; *sat_tot = total; } #if 0 /** \brief Copy a TLE structure. * \param source The TLE structure to copy from. * \param target The TLE structure to copy to. * */ static void copy_tle (tle_t *source, tle_t *target) { target->epoch = source->epoch; target->epoch_year = source->epoch_year; target->epoch_day = source->epoch_day; target->epoch_fod = source->epoch_fod; target->xndt2o = source->xndt2o; target->xndd6o = source->xndd6o; target->bstar = source->bstar; target->xincl = source->xincl; target->xnodeo = source->xnodeo; target->eo = source->eo; target->omegao = source->omegao; target->xmo = source->xmo; target->xno = source->xno; /*target->catnr = source->catnr;*/ target->elset = source->elset; target->revnum = source->revnum; target->status = source->status; /* target->xincl1 = source->xincl1; target->xnodeo1 = source->xnodeo1; target->omegao1 = source->omegao1; */ } /** \brief Compare EPOCH of two element sets * \param tle1 The first element set. * \param tle2 The second element set. * \return 0 if the epochs are equal, -1 if tle1->epoch < tle2->epoch, and * +1 if tle1->epoch > tle2->epoch. */ static gint compare_epoch (tle_t *tle1, tle_t *tle2) { gint retval = 0; if (tle1->epoch > tle2->epoch) retval = 1; else if (tle1->epoch < tle2->epoch) retval = -1; else retval = 0; return retval; } static tle_t * gchar_to_tle (tle_type_t type, const gchar *lines) { return NULL; } static gchar * tle_to_gchar (tle_type_t type, tle_t *tle) { return NULL; } #endif const gchar *freq_to_str[TLE_AUTO_UPDATE_NUM] = { N_("Never"), N_("Monthly"), N_("Weekly"), N_("Daily") }; const gchar * tle_update_freq_to_str (tle_auto_upd_freq_t freq) { if ((freq < TLE_AUTO_UPDATE_NEVER) || (freq >= TLE_AUTO_UPDATE_NUM)) { freq = TLE_AUTO_UPDATE_NEVER; } return freq_to_str[freq]; }