1 /*
2  *  Tvheadend - Network Scanner
3  *
4  *  Copyright (C) 2014 Adam Sutton
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "input.h"
21 
22 /******************************************************************************
23  * Timer
24  *****************************************************************************/
25 
26 /* Notify */
27 static void
mpegts_network_scan_notify(mpegts_mux_t * mm)28 mpegts_network_scan_notify ( mpegts_mux_t *mm )
29 {
30   idnode_notify_changed(&mm->mm_id);
31   idnode_notify_changed(&mm->mm_network->mn_id);
32 }
33 
34 static int
mm_cmp(mpegts_mux_t * a,mpegts_mux_t * b)35 mm_cmp ( mpegts_mux_t *a, mpegts_mux_t *b )
36 {
37   int r = b->mm_scan_weight - a->mm_scan_weight;
38   if (r == 0) {
39     int64_t l = b->mm_start_monoclock - a->mm_start_monoclock;
40     if (l == 0)
41       return mpegts_mux_compare(a, b);
42     r = l > 0 ? 1 : -1;
43   }
44   return r;
45 }
46 
47 void
mpegts_network_scan_timer_cb(void * p)48 mpegts_network_scan_timer_cb ( void *p )
49 {
50   mpegts_network_t *mn = p;
51   mpegts_mux_t *mm, *nxt = NULL;
52   int r;
53 
54   /* Process Q */
55   for (mm = TAILQ_FIRST(&mn->mn_scan_pend); mm != NULL; mm = nxt) {
56     nxt = TAILQ_NEXT(mm, mm_scan_link);
57     assert(mm->mm_scan_state == MM_SCAN_STATE_PEND || mm->mm_scan_state == MM_SCAN_STATE_ACTIVE);
58 
59     /* Don't try to subscribe already tuned muxes */
60     if (mm->mm_active) continue;
61 
62     /* Attempt to tune */
63     r = mpegts_mux_subscribe(mm, NULL, "scan", mm->mm_scan_weight,
64                              mm->mm_scan_flags |
65                              SUBSCRIPTION_ONESHOT |
66                              SUBSCRIPTION_TABLES);
67 
68     /* Started */
69     if (!r) {
70       assert(mm->mm_scan_state == MM_SCAN_STATE_ACTIVE);
71       continue;
72     }
73     assert(mm->mm_scan_state == MM_SCAN_STATE_PEND);
74 
75     /* No free tuners - stop */
76     if (r == SM_CODE_NO_FREE_ADAPTER || r == SM_CODE_NO_ADAPTERS)
77       break;
78 
79     /* No valid tuners (subtly different, might be able to tuner a later
80      * mux)
81      */
82     if (r == SM_CODE_NO_VALID_ADAPTER && mm->mm_is_enabled(mm))
83       continue;
84 
85     /* Failed */
86     TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
87     if (mm->mm_scan_result != MM_SCAN_FAIL) {
88       mm->mm_scan_result = MM_SCAN_FAIL;
89       idnode_changed(&mm->mm_id);
90     }
91     mm->mm_scan_state  = MM_SCAN_STATE_IDLE;
92     mm->mm_scan_weight = 0;
93     mpegts_network_scan_notify(mm);
94   }
95 
96   /* Re-arm timer. Really this is just a safety measure as we'd normally
97    * expect the timer to be forcefully triggered on finish of a mux scan
98    */
99   mtimer_arm_rel(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, sec2mono(120));
100 }
101 
102 /******************************************************************************
103  * Mux transition
104  *****************************************************************************/
105 
106 /* Finished */
107 static inline void
mpegts_network_scan_mux_done0(mpegts_mux_t * mm,mpegts_mux_scan_result_t result,int weight)108 mpegts_network_scan_mux_done0
109   ( mpegts_mux_t *mm, mpegts_mux_scan_result_t result, int weight )
110 {
111   mpegts_network_t *mn = mm->mm_network;
112   mpegts_mux_scan_state_t state = mm->mm_scan_state;
113 
114   /* prevent double del: */
115   /*   mpegts_mux_stop -> mpegts_network_scan_mux_cancel */
116   mm->mm_scan_state = MM_SCAN_STATE_IDLE;
117   mpegts_mux_unsubscribe_by_name(mm, "scan");
118   mm->mm_scan_state = state;
119 
120   if (state == MM_SCAN_STATE_PEND) {
121     if (weight || mn->mn_idlescan) {
122       if (!weight)
123         mm->mm_scan_weight = SUBSCRIPTION_PRIO_SCAN_IDLE;
124       TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
125       TAILQ_INSERT_SORTED_R(&mn->mn_scan_pend, mpegts_mux_queue,
126                             mm, mm_scan_link, mm_cmp);
127       mtimer_arm_rel(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, sec2mono(10));
128       weight = 0;
129     } else {
130       mpegts_network_scan_queue_del(mm);
131     }
132   } else {
133     if (!weight && mn->mn_idlescan)
134       weight = SUBSCRIPTION_PRIO_SCAN_IDLE;
135     mpegts_network_scan_queue_del(mm);
136   }
137 
138   if (result != MM_SCAN_NONE && mm->mm_scan_result != result) {
139     mm->mm_scan_result = result;
140     idnode_changed(&mm->mm_id);
141   }
142 
143   /* Re-enable? */
144   if (weight > 0)
145     mpegts_network_scan_queue_add(mm, weight, mm->mm_scan_flags, 10);
146 }
147 
148 /* Failed - couldn't start */
149 void
mpegts_network_scan_mux_fail(mpegts_mux_t * mm)150 mpegts_network_scan_mux_fail    ( mpegts_mux_t *mm )
151 {
152   mpegts_network_scan_mux_done0(mm, MM_SCAN_FAIL, 0);
153 }
154 
155 /* Completed succesfully */
156 void
mpegts_network_scan_mux_done(mpegts_mux_t * mm)157 mpegts_network_scan_mux_done    ( mpegts_mux_t *mm )
158 {
159   mm->mm_scan_flags = 0;
160   mpegts_network_scan_mux_done0(mm, MM_SCAN_OK, 0);
161 }
162 
163 /* Partially completed (not all tables were read) */
164 void
mpegts_network_scan_mux_partial(mpegts_mux_t * mm)165 mpegts_network_scan_mux_partial ( mpegts_mux_t *mm )
166 {
167   mpegts_network_scan_mux_done0(mm, MM_SCAN_PARTIAL, 0);
168 }
169 
170 /* Interrupted (re-add) */
171 void
mpegts_network_scan_mux_cancel(mpegts_mux_t * mm,int reinsert)172 mpegts_network_scan_mux_cancel  ( mpegts_mux_t *mm, int reinsert )
173 {
174   if (mm->mm_scan_state != MM_SCAN_STATE_ACTIVE)
175     return;
176 
177   if (!reinsert)
178     mm->mm_scan_flags = 0;
179 
180   mpegts_network_scan_mux_done0(mm, MM_SCAN_NONE,
181                                 reinsert ? mm->mm_scan_weight : 0);
182 }
183 
184 /* Mux has been started */
185 void
mpegts_network_scan_mux_active(mpegts_mux_t * mm)186 mpegts_network_scan_mux_active ( mpegts_mux_t *mm )
187 {
188   mpegts_network_t *mn = mm->mm_network;
189   if (mm->mm_scan_state != MM_SCAN_STATE_PEND)
190     return;
191   mm->mm_scan_state = MM_SCAN_STATE_ACTIVE;
192   mm->mm_scan_init  = 0;
193   TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
194   TAILQ_INSERT_TAIL(&mn->mn_scan_active, mm, mm_scan_link);
195 }
196 
197 /******************************************************************************
198  * Mux queue handling
199  *****************************************************************************/
200 
201 void
mpegts_network_scan_queue_del(mpegts_mux_t * mm)202 mpegts_network_scan_queue_del ( mpegts_mux_t *mm )
203 {
204   mpegts_network_t *mn = mm->mm_network;
205   char buf[384];
206   if (mm->mm_scan_state == MM_SCAN_STATE_ACTIVE) {
207     TAILQ_REMOVE(&mn->mn_scan_active, mm, mm_scan_link);
208   } else if (mm->mm_scan_state == MM_SCAN_STATE_PEND) {
209     TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
210   }
211   mpegts_mux_nice_name(mm, buf, sizeof(buf));
212   tvhdebug(LS_MPEGTS, "removing mux %s from scan queue", buf);
213   mm->mm_scan_state  = MM_SCAN_STATE_IDLE;
214   mm->mm_scan_weight = 0;
215   mtimer_disarm(&mm->mm_scan_timeout);
216   mtimer_arm_rel(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, 0);
217   mpegts_network_scan_notify(mm);
218 }
219 
220 void
mpegts_network_scan_queue_add(mpegts_mux_t * mm,int weight,int flags,int delay)221 mpegts_network_scan_queue_add
222   ( mpegts_mux_t *mm, int weight, int flags, int delay )
223 {
224   int reload = 0;
225   char buf[384];
226   mpegts_network_t *mn = mm->mm_network;
227 
228   if (!mm->mm_is_enabled(mm)) return;
229 
230   if (weight <= 0) return;
231 
232   if (weight > mm->mm_scan_weight) {
233     mm->mm_scan_weight = weight;
234     reload             = 1;
235   }
236 
237   /* Already active */
238   if (mm->mm_scan_state == MM_SCAN_STATE_ACTIVE)
239     return;
240 
241   /* Remove entry (or ignore) */
242   if (mm->mm_scan_state == MM_SCAN_STATE_PEND) {
243     if (!reload)
244       return;
245     TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
246   }
247 
248   mpegts_mux_nice_name(mm, buf, sizeof(buf));
249   tvhdebug(LS_MPEGTS, "adding mux %s to scan queue weight %d flags %04X",
250                       buf, weight, flags);
251 
252   /* Add new entry */
253   mm->mm_scan_state  = MM_SCAN_STATE_PEND;
254   mm->mm_scan_flags |= flags;
255   if (mm->mm_scan_flags == 0)
256     mm->mm_scan_flags = SUBSCRIPTION_IDLESCAN;
257   TAILQ_INSERT_SORTED_R(&mn->mn_scan_pend, mpegts_mux_queue,
258                         mm, mm_scan_link, mm_cmp);
259   mtimer_arm_rel(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, sec2mono(delay));
260   mpegts_network_scan_notify(mm);
261 }
262 
263 /******************************************************************************
264  * Bouquet helper
265  *****************************************************************************/
266 
267 #if ENABLE_MPEGTS_DVB
268 static ssize_t
startswith(const char * str,const char * start)269 startswith( const char *str, const char *start )
270 {
271   size_t len = strlen(start);
272   if (!strncmp(str, start, len))
273     return len;
274   return -1;
275 }
276 #endif
277 
278 void
mpegts_mux_bouquet_rescan(const char * src,const char * extra)279 mpegts_mux_bouquet_rescan ( const char *src, const char *extra )
280 {
281 #if ENABLE_MPEGTS_DVB
282   mpegts_network_t *mn;
283   mpegts_mux_t *mm;
284   ssize_t l;
285   const idclass_t *ic;
286   uint32_t freq;
287   int satpos;
288 #endif
289 
290   if (!src)
291     return;
292 #if ENABLE_MPEGTS_DVB
293   if ((l = startswith(src, "dvb-bouquet://dvbs,")) > 0) {
294     uint32_t tsid, nbid;
295     src += l;
296     if ((satpos = dvb_sat_position_from_str(src)) == INT_MAX)
297       return;
298     while (*src && *src != ',')
299       src++;
300     if (sscanf(src, ",%x,%x", &tsid, &nbid) != 2)
301       return;
302     LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
303       LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
304         if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) &&
305             mm->mm_tsid == tsid &&
306             ((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.orbital_pos == satpos)
307           mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
308     return;
309   }
310   if ((l = startswith(src, "dvb-bouquet://dvbt,")) > 0) {
311     uint32_t tsid, nbid;
312     if (sscanf(src, "%x,%x", &tsid, &nbid) != 2)
313       return;
314     ic = &dvb_mux_dvbt_class;
315     goto tsid_lookup;
316   }
317   if ((l = startswith(src, "dvb-bouquet://dvbc,")) > 0) {
318     uint32_t tsid, nbid;
319     if (sscanf(src, "%x,%x", &tsid, &nbid) != 2)
320       return;
321     ic = &dvb_mux_dvbc_class;
322 tsid_lookup:
323     LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
324       LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
325         if (idnode_is_instance(&mm->mm_id, ic) &&
326             mm->mm_tsid == tsid)
327           mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
328     return;
329   }
330   if ((l = startswith(src, "dvb-bskyb://dvbs,")) > 0 ||
331       (l = startswith(src, "dvb-freesat://dvbs,")) > 0) {
332     if ((satpos = dvb_sat_position_from_str(src + l)) == INT_MAX)
333       return;
334     /* a bit tricky, but we don't have other info */
335     if (!extra)
336       return;
337     freq = strtod(extra, NULL) * 1000;
338 
339     LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
340       LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
341         if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) &&
342             deltaU32(((dvb_mux_t *)mm)->lm_tuning.dmc_fe_freq, freq) < 2000 &&
343             ((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.orbital_pos == satpos)
344           mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
345     return;
346   }
347   if ((l = startswith(src, "dvb-fastscan://")) > 0) {
348     uint32_t pid, symbol, dvbs2;
349     char pol[2];
350     pol[1] = '\0';
351     src += l;
352 
353     if ((l = startswith(src, "DVBS2,")) > 0 || (l = startswith(src, "DVBS-2,")) > 0)
354       dvbs2 = 1;
355     else if ((l = startswith(src, "DVBS,")) > 0 || (l = startswith(src, "DVB-S,")) > 0)
356       dvbs2 = 0;
357     else
358       return;
359     src += l;
360 
361     if ((satpos = dvb_sat_position_from_str(src)) == INT_MAX)
362       return;
363     while (*src && *src != ',')
364       src++;
365     if (sscanf(src, ",%u,%c,%u,%u", &freq, &pol[0], &symbol, &pid) != 4)
366       return;
367 
368     // search for fastscan mux
369     LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
370       LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
371         if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) &&
372             deltaU32(((dvb_mux_t *)mm)->lm_tuning.dmc_fe_freq, freq) < 2000 &&
373             ((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.polarisation == dvb_str2pol(pol) &&
374             ((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.orbital_pos == satpos)
375         {
376           char buf[256];
377           mpegts_mux_nice_name(mm, buf, sizeof(buf));
378           tvhinfo(LS_MPEGTS, "fastscan mux found '%s', set scan state 'PENDING'", buf);
379           mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
380           return;
381         }
382     tvhinfo(LS_MPEGTS, "fastscan mux not found, position:%i, frequency:%i, polarisation:%c", satpos, freq, pol[0]);
383 
384     // fastscan mux not found, try to add it automatically
385     LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
386       if (mn->mn_satpos != INT_MAX && mn->mn_satpos == satpos)
387       {
388         dvb_mux_conf_t *mux;
389         mpegts_mux_t *mm = NULL;
390 
391         mux = malloc(sizeof(dvb_mux_conf_t));
392         dvb_mux_conf_init(mn, mux, dvbs2 ? DVB_SYS_DVBS2 : DVB_SYS_DVBS);
393         mux->dmc_fe_freq = freq;
394         mux->u.dmc_fe_qpsk.symbol_rate = symbol;
395         mux->u.dmc_fe_qpsk.polarisation = dvb_str2pol(pol);
396         mux->u.dmc_fe_qpsk.orbital_pos = satpos;
397         mux->u.dmc_fe_qpsk.fec_inner = DVB_FEC_AUTO;
398         mux->dmc_fe_modulation = dvbs2 ? DVB_MOD_PSK_8 : DVB_MOD_QPSK;
399         mux->dmc_fe_rolloff = DVB_ROLLOFF_AUTO;
400 
401         mm = (mpegts_mux_t*)dvb_mux_create0((dvb_network_t*)mn,
402                                             MPEGTS_ONID_NONE,
403                                             MPEGTS_TSID_NONE,
404                                             mux, NULL, NULL);
405         if (mm) {
406           char buf[256];
407           idnode_changed(&mm->mm_id);
408           mn->mn_display_name(mn, buf, sizeof(buf));
409           tvhinfo(LS_MPEGTS, "fastscan mux add to network '%s'", buf);
410         }
411         free(mux);
412       }
413     return;
414   }
415 #endif
416 }
417 
418 /******************************************************************************
419  * Subsystem setup / tear down
420  *****************************************************************************/
421 
422 void
mpegts_network_scan_init(void)423 mpegts_network_scan_init ( void )
424 {
425 }
426 
427 void
mpegts_network_scan_done(void)428 mpegts_network_scan_done ( void )
429 {
430 }
431 
432 /******************************************************************************
433  * Editor Configuration
434  *
435  * vim:sts=2:ts=2:sw=2:et
436  *****************************************************************************/
437