2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "ZeroconfBrowserAvahi.h"
25 #include <utils/log.h>
26 #include "guilib/GUIWindowManager.h"
27 #include "guilib/GUIMessage.h"
28 #include "GUIUserMessages.h"
29 #include <avahi-common/malloc.h>
30 #include <avahi-common/error.h>
33 ///helper RAII-struct to block event loop for modifications
34 struct ScopedEventLoopBlock
36 ScopedEventLoopBlock ( AvahiThreadedPoll* fp_poll ) : mp_poll ( fp_poll )
38 avahi_threaded_poll_lock ( mp_poll );
41 ~ScopedEventLoopBlock()
43 avahi_threaded_poll_unlock ( mp_poll );
46 AvahiThreadedPoll* mp_poll;
50 CZeroconfBrowserAvahi::CZeroconfBrowserAvahi() : mp_client ( 0 ), mp_poll ( 0 ), m_shutdown(false), m_thread_id(0)
52 if ( ! ( mp_poll = avahi_threaded_poll_new() ) )
54 CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create threaded poll object" );
55 //TODO: throw exception? can this even happen?
59 if ( !createClient() )
61 CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create client" );
62 //yeah, what if not? but should always succeed (as client_no_fail or something is passed)
65 //start event loop thread
66 if ( avahi_threaded_poll_start ( mp_poll ) < 0 )
68 CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Failed to start avahi client thread" );
72 CZeroconfBrowserAvahi::~CZeroconfBrowserAvahi()
74 CLog::Log ( LOGDEBUG, "CZeroconfAvahi::~CZeroconfAvahi() Going down! cleaning up..." );
77 //normally we would stop the avahi thread here and do our work, but
78 //it looks like this does not work -> www.avahi.org/ticket/251
79 //so instead of calling
80 //avahi_threaded_poll_stop(mp_poll);
81 //we set m_shutdown=true, post an event and wait for it to stop itself
82 struct timeval tv = { 0, 0 }; //TODO: does tv survive the thread?
83 AvahiTimeout* lp_timeout;
85 ScopedEventLoopBlock l_block(mp_poll);
86 const AvahiPoll* cp_apoll = avahi_threaded_poll_get(mp_poll);
88 lp_timeout = cp_apoll->timeout_new(cp_apoll,
94 //now wait for the thread to stop
96 pthread_join(m_thread_id, NULL);
97 avahi_threaded_poll_get(mp_poll)->timeout_free(lp_timeout);
99 //free the client (frees all browsers, groups, ...)
101 avahi_client_free ( mp_client );
103 avahi_threaded_poll_free ( mp_poll );
106 bool CZeroconfBrowserAvahi::doAddServiceType ( const CStdString& fcr_service_type )
108 ScopedEventLoopBlock lock ( mp_poll );
109 tBrowserMap::iterator it = m_browsers.find ( fcr_service_type );
110 if ( it != m_browsers.end() )
113 it = m_browsers.insert ( std::make_pair ( fcr_service_type, ( AvahiServiceBrowser* ) 0 ) ).first;
115 //if the client is running, we directly create a browser for the service here
116 if ( mp_client && avahi_client_get_state ( mp_client ) == AVAHI_CLIENT_S_RUNNING )
118 AvahiServiceBrowser* browser = createServiceBrowser ( fcr_service_type, mp_client, this);
121 m_browsers.erase ( it );
126 it->second = browser;
132 CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::doAddServiceType client not available. service browsing queued" );
137 bool CZeroconfBrowserAvahi::doRemoveServiceType ( const CStdString& fcr_service_type )
139 ScopedEventLoopBlock lock ( mp_poll );
140 tBrowserMap::iterator it = m_browsers.find ( fcr_service_type );
141 if ( it == m_browsers.end() )
147 avahi_service_browser_free ( it->second );
148 m_all_for_now_browsers.erase ( it->second );
150 m_browsers.erase ( it );
151 //remove this serviceType from the list of discovered services
152 for ( tDiscoveredServices::iterator it = m_discovered_services.begin(); it != m_discovered_services.end(); ++it )
153 if ( it->first.GetType() == fcr_service_type )
154 m_discovered_services.erase ( it++ );
159 std::vector<CZeroconfBrowser::ZeroconfService> CZeroconfBrowserAvahi::doGetFoundServices()
161 std::vector<CZeroconfBrowser::ZeroconfService> ret;
162 ScopedEventLoopBlock lock ( mp_poll );
163 ret.reserve ( m_discovered_services.size() );
164 for ( tDiscoveredServices::iterator it = m_discovered_services.begin(); it != m_discovered_services.end(); ++it )
165 ret.push_back ( it->first );
169 bool CZeroconfBrowserAvahi::doResolveService ( CZeroconfBrowser::ZeroconfService& fr_service, double f_timeout )
172 //wait for lock on event-loop to schedule resolving
173 ScopedEventLoopBlock lock ( mp_poll );
174 //avahi can only resolve already discovered services, as it needs info from there
175 tDiscoveredServices::const_iterator it = m_discovered_services.find( fr_service );
176 if ( it == m_discovered_services.end() )
178 CLog::Log ( LOGERROR, "CZeroconfBrowserAvahi::doResolveService called with undiscovered service, resolving is NOT possible" );
182 m_resolving_service = fr_service;
183 m_resolved_event.Reset();
184 if ( !avahi_service_resolver_new ( mp_client, it->second.interface, it->second.protocol,
185 it->first.GetName().c_str(), it->first.GetType().c_str(), it->first.GetDomain().c_str(),
186 AVAHI_PROTO_UNSPEC, AvahiLookupFlags ( 0 ), resolveCallback, this ) )
188 CLog::Log ( LOGERROR, "CZeroconfBrowserAvahi::doResolveService Failed to resolve service '%s': %s\n", it->first.GetName().c_str(),
189 avahi_strerror ( avahi_client_errno ( mp_client ) ) );
192 } // end of this block releases lock of eventloop
194 //wait for resolve to return or timeout
195 m_resolved_event.WaitMSec(f_timeout*1000);
197 ScopedEventLoopBlock lock ( mp_poll );
198 fr_service = m_resolving_service;
199 return (!fr_service.GetIP().empty());
203 void CZeroconfBrowserAvahi::clientCallback ( AvahiClient* fp_client, AvahiClientState f_state, void* fp_data )
205 CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( fp_data );
207 //store our thread ID and check for shutdown -> check details in destructor
208 p_instance->m_thread_id = pthread_self();
209 if (p_instance->m_shutdown)
211 avahi_threaded_poll_quit(p_instance->mp_poll);
217 case AVAHI_CLIENT_S_RUNNING:
219 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::clientCallback: client is up and running" );
220 for ( tBrowserMap::iterator it = p_instance->m_browsers.begin(); it != p_instance->m_browsers.end(); ++it )
222 assert ( !it->second );
223 it->second = createServiceBrowser ( it->first, fp_client, fp_data );
227 case AVAHI_CLIENT_FAILURE:
229 CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::clientCallback: client failure. avahi-daemon stopped? Recreating client..." );
230 //We were forced to disconnect from server. now free and recreate the client object
231 avahi_client_free ( fp_client );
232 p_instance->mp_client = 0;
233 //freeing the client also frees all groups and browsers, pointers are undefined afterwards, so fix that now
234 for ( tBrowserMap::iterator it = p_instance->m_browsers.begin(); it != p_instance->m_browsers.end(); ++it )
235 it->second = ( AvahiServiceBrowser* ) 0;
236 //clean the list of discovered services and update gui (if someone is interested)
237 p_instance->m_discovered_services.clear();
238 CGUIMessage message ( GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH );
239 message.SetStringParam ( "zeroconf://" );
240 g_windowManager.SendThreadMessage ( message );
241 p_instance->createClient();
244 case AVAHI_CLIENT_S_COLLISION:
245 case AVAHI_CLIENT_S_REGISTERING:
246 //HERE WE SHOULD REMOVE ALL OF OUR SERVICES AND "RESCHEDULE" them for later addition
247 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::clientCallback: This should not happen" );
250 case AVAHI_CLIENT_CONNECTING:
251 CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::clientCallback: avahi server not available. But may become later..." );
255 void CZeroconfBrowserAvahi::browseCallback (
256 AvahiServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event,
257 const char *name, const char *type, const char *domain,
258 AvahiLookupResultFlags flags, void* fp_data )
260 CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( fp_data );
262 bool update_gui = false;
263 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
266 case AVAHI_BROWSER_FAILURE:
267 CLog::Log ( LOGERROR, "CZeroconfBrowserAvahi::browseCallback error: %s\n", avahi_strerror ( avahi_client_errno ( avahi_service_browser_get_client ( browser ) ) ) );
270 case AVAHI_BROWSER_NEW:
272 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain );
274 ZeroconfService service(name, type, domain);
275 AvahiSpecificInfos info;
276 info.interface = interface;
277 info.protocol = protocol;
278 p_instance->m_discovered_services.insert ( std::make_pair ( service, info ) );
279 //if this browser already sent the all for now message, we need to update the gui now
280 if( p_instance->m_all_for_now_browsers.find(browser) != p_instance->m_all_for_now_browsers.end() )
284 case AVAHI_BROWSER_REMOVE:
287 ZeroconfService service(name, type, domain);
288 p_instance->m_discovered_services.erase ( service );
289 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain );
290 //if this browser already sent the all for now message, we need to update the gui now
291 if( p_instance->m_all_for_now_browsers.find(browser) != p_instance->m_all_for_now_browsers.end() )
295 case AVAHI_BROWSER_CACHE_EXHAUSTED:
298 case AVAHI_BROWSER_ALL_FOR_NOW:
299 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback all for now (service = %s)", type);
300 //if this browser already sent the all for now message, we need to update the gui now
301 bool success = p_instance->m_all_for_now_browsers.insert(browser).second;
303 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback AVAHI_BROWSER_ALL_FOR_NOW sent twice?!");
309 CGUIMessage message ( GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH );
310 message.SetStringParam ( "zeroconf://" );
311 g_windowManager.SendThreadMessage ( message );
312 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback sent gui update for path zeroconf://" );
316 CZeroconfBrowser::ZeroconfService::tTxtRecordMap GetTxtRecords(AvahiStringList *txt)
318 AvahiStringList *i = NULL;
319 CZeroconfBrowser::ZeroconfService::tTxtRecordMap recordMap;
321 for( i = txt; i; i = i->next )
325 if( avahi_string_list_get_pair( i, &key, &value, NULL ) < 0 )
343 void CZeroconfBrowserAvahi::resolveCallback(
344 AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event,
345 const char *name, const char *type, const char *domain, const char *host_name,
346 const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata )
350 CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( userdata );
353 case AVAHI_RESOLVER_FAILURE:
354 CLog::Log ( LOGERROR, "CZeroconfBrowserAvahi::resolveCallback Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror ( avahi_client_errno ( avahi_service_resolver_get_client ( r ) ) ) );
356 case AVAHI_RESOLVER_FOUND:
358 char a[AVAHI_ADDRESS_STR_MAX];
359 CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::resolveCallback resolved service '%s' of type '%s' in domain '%s':\n", name, type, domain );
361 avahi_address_snprint ( a, sizeof ( a ), address );
362 p_instance->m_resolving_service.SetIP(a);
363 p_instance->m_resolving_service.SetPort(port);
364 //get txt-record list
365 p_instance->m_resolving_service.SetTxtRecords(GetTxtRecords(txt));
369 avahi_service_resolver_free ( r );
370 p_instance->m_resolved_event.Set();
373 bool CZeroconfBrowserAvahi::createClient()
378 avahi_client_free ( mp_client );
380 mp_client = avahi_client_new ( avahi_threaded_poll_get ( mp_poll ),
381 AVAHI_CLIENT_NO_FAIL, &clientCallback, this, 0 );
390 AvahiServiceBrowser* CZeroconfBrowserAvahi::createServiceBrowser ( const CStdString& fcr_service_type, AvahiClient* fp_client, void* fp_userdata)
393 AvahiServiceBrowser* ret = avahi_service_browser_new ( fp_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, fcr_service_type.c_str(),
394 NULL, ( AvahiLookupFlags ) 0, browseCallback, fp_userdata );
397 CLog::Log ( LOGERROR, "CZeroconfBrowserAvahi::createServiceBrowser Failed to create service (%s) browser: %s",
398 avahi_strerror ( avahi_client_errno ( fp_client ) ), fcr_service_type.c_str() );
404 void CZeroconfBrowserAvahi::shutdownCallback(AvahiTimeout *fp_e, void *fp_data)
406 CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*>(fp_data);
407 //should only be called on shutdown
408 if (p_instance->m_shutdown)
410 avahi_threaded_poll_quit(p_instance->mp_poll);