qavahiservicebrowsermodel.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2011 Girish Ramakrishnan (girish@forwardbias.in)
  4. **
  5. ** Use, modification and distribution is allowed without limitation,
  6. ** warranty, liability or support of any kind.
  7. **
  8. ****************************************************************************/
  9. #include "qavahiservicebrowsermodel.h"
  10. #include <QtDebug>
  11. #include <QtGui>
  12. #include <arpa/inet.h>
  13. static void QABDEBUG(const char *fmt, ...)
  14. {
  15. static const bool debug = qgetenv("DEBUG").toInt();
  16. if (debug) {
  17. char buf[1024];
  18. va_list ap;
  19. va_start(ap, fmt);
  20. vsnprintf(buf, sizeof(buf), fmt, ap);
  21. va_end(ap);
  22. qDebug("QAvahiServiceBrowserModel: %s", buf);
  23. }
  24. }
  25. QAvahiServiceBrowserModel::QAvahiServiceBrowserModel(QObject *parent)
  26. : QAbstractListModel(parent), m_client(0), m_options(0), m_browserType(NoBrowserType),
  27. m_browser(0), m_autoResolve(true)
  28. {
  29. qRegisterMetaType<QAvahiServiceBrowserModel::Service>();
  30. QHash<int, QByteArray> hash = QAbstractListModel::roleNames();
  31. hash[NameRole] = "name";
  32. hash[TypeRole] = "type";
  33. hash[DomainRole] = "domain";
  34. hash[ProtocolRole] = "protocol";
  35. hash[InterfaceRole] = "interface";
  36. hash[HostNameRole] = "hostname";
  37. hash[AddressRole] = "address";
  38. hash[PortRole] = "port";
  39. hash[TxtRole] = "txt";
  40. setRoleNames(hash);
  41. initialize();
  42. }
  43. QAvahiServiceBrowserModel::~QAvahiServiceBrowserModel()
  44. {
  45. }
  46. void QAvahiServiceBrowserModel::initialize()
  47. {
  48. const AvahiPoll *poll_api = avahi_qt_poll_get();
  49. m_client = avahi_client_new(poll_api, AVAHI_CLIENT_NO_FAIL,
  50. client_callback, this /* userdata */, &m_error);
  51. if (!m_client) {
  52. QABDEBUG("Failed to create client %s", avahi_strerror(m_error));
  53. m_errorString = avahi_strerror(m_error);
  54. emit changeNotification(Error);
  55. }
  56. }
  57. void QAvahiServiceBrowserModel::uninitialize()
  58. {
  59. if (m_browser) {
  60. if (m_browserType == ServiceBrowser) {
  61. avahi_service_browser_free(static_cast<AvahiServiceBrowser *>(m_browser));
  62. }
  63. m_browserType = NoBrowserType;
  64. m_browser = 0;
  65. }
  66. if (m_client) {
  67. avahi_client_free(m_client);
  68. m_client = 0;
  69. }
  70. }
  71. void QAvahiServiceBrowserModel::resetModel()
  72. {
  73. beginResetModel();
  74. m_services.clear();
  75. m_rowToServiceIndex.clear();
  76. endResetModel();
  77. }
  78. void QAvahiServiceBrowserModel::client_callback(AvahiClient *client, AvahiClientState state, void *userdata)
  79. {
  80. QAvahiServiceBrowserModel *browser = reinterpret_cast<QAvahiServiceBrowserModel *>(userdata);
  81. browser->clientCallback(client, state);
  82. }
  83. void QAvahiServiceBrowserModel::clientCallback(AvahiClient *client, AvahiClientState state)
  84. {
  85. switch (state) {
  86. case AVAHI_CLIENT_FAILURE:
  87. QABDEBUG("Connection failure : %s", avahi_strerror(avahi_client_errno(client)));
  88. m_error = AVAHI_CLIENT_FAILURE;
  89. m_errorString = avahi_strerror(avahi_client_errno(client));
  90. emit changeNotification(Error);
  91. // Once a client loses connection to the daemon, it doesn't seem to reconnect when the daemon
  92. // reappears. Recreate the client.
  93. uninitialize();
  94. resetModel();
  95. initialize();
  96. break;
  97. case AVAHI_CLIENT_CONNECTING:
  98. QABDEBUG("Client is connecting");
  99. emit changeNotification(ClientConnecting);
  100. break;
  101. case AVAHI_CLIENT_S_RUNNING:
  102. QABDEBUG("Server running");
  103. emit changeNotification(ServerRunning);
  104. if (!m_serviceType.isEmpty())
  105. doBrowse(client);
  106. break;
  107. case AVAHI_CLIENT_S_COLLISION:
  108. QABDEBUG("Server name Collission");
  109. emit changeNotification(ServerNameCollision);
  110. break;
  111. case AVAHI_CLIENT_S_REGISTERING:
  112. QABDEBUG("Registering");
  113. emit changeNotification(ServerRegistering);
  114. break;
  115. default:
  116. QABDEBUG("Unhandled state in clientCallback: %d", state);
  117. break;
  118. }
  119. }
  120. void QAvahiServiceBrowserModel::browser_callback(AvahiServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void *userdata)
  121. {
  122. QAvahiServiceBrowserModel *that = reinterpret_cast<QAvahiServiceBrowserModel *>(userdata);
  123. that->browserCallback(browser, interface, protocol, event, name, type, domain, flags);
  124. }
  125. int QAvahiServiceBrowserModel::serviceIndex(const char *name, const char *type, const char *domain, AvahiIfIndex interface, AvahiProtocol protocol)
  126. {
  127. for (int i = 0; i < m_services.count(); i++) {
  128. if (m_services[i].name == name && m_services[i].type == type && m_services[i].domain == domain
  129. && m_services[i].interface == interface && m_services[i].protocol == protocol)
  130. return i;
  131. }
  132. return -1;
  133. }
  134. static bool shouldAdd(int options, const QAvahiServiceBrowserModel::Service &service)
  135. {
  136. if ((options & QAvahiServiceBrowserModel::HideLocal) && service.isLocal())
  137. return false;
  138. if ((options & QAvahiServiceBrowserModel::HideSameLocalClient) && service.isSameLocalClient())
  139. return false;
  140. return true;
  141. }
  142. void QAvahiServiceBrowserModel::browserCallback(AvahiServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags)
  143. {
  144. AvahiClient *client = avahi_service_browser_get_client(browser);
  145. Service service;
  146. service.name = name;
  147. service.type = type;
  148. service.domain = domain;
  149. service.protocol = protocol;
  150. service.interface = interface;
  151. service.flags = flags;
  152. switch (event) {
  153. case AVAHI_BROWSER_FAILURE:
  154. QABDEBUG("Browser failure : %s", avahi_strerror(avahi_client_errno(client)));
  155. m_error = avahi_client_errno(client);
  156. m_errorString = avahi_strerror(m_error);
  157. emit changeNotification(Error);
  158. uninitialize();
  159. resetModel();
  160. initialize();
  161. break;
  162. case AVAHI_BROWSER_NEW: {
  163. QABDEBUG("New service name:%s type:%s domain:%s flags:0x%x", name, type, domain, flags);
  164. if (shouldAdd(m_options, service)) { // ## test if flags is reliable at this point
  165. beginInsertRows(QModelIndex(), m_rowToServiceIndex.count(), m_rowToServiceIndex.count());
  166. m_services.append(service);
  167. m_rowToServiceIndex.append(m_services.count()-1);
  168. endInsertRows();
  169. } else {
  170. m_services.append(service);
  171. }
  172. if (m_autoResolve)
  173. resolve(&m_services.last());
  174. }
  175. break;
  176. case AVAHI_BROWSER_REMOVE: {
  177. QABDEBUG("Remove service");
  178. const int idx = serviceIndex(name, type, domain, interface, protocol);
  179. const int row = m_rowToServiceIndex.indexOf(idx);
  180. if (idx == -1) {
  181. qWarning() << "Removing non-existent service";
  182. return;
  183. }
  184. if (row == -1) {
  185. m_services.removeAt(idx);
  186. } else {
  187. beginRemoveRows(QModelIndex(), row, row);
  188. m_services.removeAt(idx);
  189. QList<int>::iterator it = m_rowToServiceIndex.erase(m_rowToServiceIndex.begin() + row);
  190. for (; it != m_rowToServiceIndex.end(); ++it)
  191. --(*it);
  192. endRemoveRows();
  193. }
  194. break;
  195. }
  196. case AVAHI_BROWSER_ALL_FOR_NOW:
  197. QABDEBUG("All for now");
  198. emit done();
  199. break;
  200. case AVAHI_BROWSER_CACHE_EXHAUSTED:
  201. QABDEBUG("Cache exhausted");
  202. break;
  203. default:
  204. break;
  205. }
  206. }
  207. void QAvahiServiceBrowserModel::browse(const QString &serviceType, int options)
  208. {
  209. m_serviceType = serviceType;
  210. m_options = options;
  211. resetModel();
  212. if (m_browser) { // already browsing; remove existing browser
  213. if (m_browserType == ServiceBrowser)
  214. avahi_service_browser_free(static_cast<AvahiServiceBrowser *>(m_browser));
  215. m_browserType = NoBrowserType;
  216. m_browser = 0;
  217. }
  218. if (avahi_client_get_state(m_client) != AVAHI_CLIENT_S_RUNNING) { // server not running, we should try later
  219. QABDEBUG("Server is not running yet, will browse later. Check if avahi-daemon is running");
  220. return;
  221. }
  222. doBrowse(m_client);
  223. }
  224. static int toAvahiProtocol(int options)
  225. {
  226. if (options & QAvahiServiceBrowserModel::HideIPv4)
  227. return AVAHI_PROTO_INET6;
  228. else if (options & QAvahiServiceBrowserModel::HideIPv6)
  229. return AVAHI_PROTO_INET;
  230. else
  231. return AVAHI_PROTO_UNSPEC;
  232. }
  233. // This function takes client as argument because it's called from clientCallback. Since
  234. // clientCallback maybe called from avahi_client_new, m_client may still be 0.
  235. void QAvahiServiceBrowserModel::doBrowse(AvahiClient *client)
  236. {
  237. QABDEBUG("Creating browser");
  238. int proto = toAvahiProtocol(m_options);
  239. m_browser = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, proto, m_serviceType.toUtf8().constData(),
  240. NULL /* domain */, (AvahiLookupFlags)0, /*AVAHI_LOOKUP_USE_MULTICAST*/
  241. browser_callback, this /* userdata */);
  242. if (!m_browser) {
  243. QABDEBUG("Failed to create browser");
  244. } else {
  245. m_browserType = ServiceBrowser;
  246. }
  247. }
  248. void QAvahiServiceBrowserModel::resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event,
  249. const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address,
  250. uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata)
  251. {
  252. QAvahiServiceBrowserModel *browser = reinterpret_cast<QAvahiServiceBrowserModel *>(userdata);
  253. browser->resolverCallback(r, interface, protocol, event, name, type, domain, host_name, address, port, txt, flags);
  254. }
  255. void QAvahiServiceBrowserModel::resolverCallback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event,
  256. const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address,
  257. uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags)
  258. {
  259. int idx = serviceIndex(name, type, domain, interface, protocol);
  260. Q_ASSERT(idx != -1);
  261. Service &service = m_services[idx];
  262. int row = m_rowToServiceIndex.indexOf(idx);
  263. if (event == AVAHI_RESOLVER_FAILURE) {
  264. QABDEBUG("Failed to resolve");
  265. service.resolved = false;
  266. emit resolved(idx);
  267. } else if (event == AVAHI_RESOLVER_FOUND) {
  268. service.hostName = host_name;
  269. if (address->proto == AVAHI_PROTO_INET) {
  270. service.address = QHostAddress(ntohl(address->data.ipv4.address));
  271. } else if (address->proto == AVAHI_PROTO_INET6) {
  272. service.address = QHostAddress((quint8 *)address->data.ipv6.address);
  273. }
  274. service.port = port;
  275. service.textRecords.clear();
  276. for (AvahiStringList *l = txt; l != NULL; l = avahi_string_list_get_next(l)) {
  277. service.textRecords.append((const char *)l->text);
  278. }
  279. service.flags = flags;
  280. service.resolved = true;
  281. QABDEBUG("Service found : name:%s type:%s domain:%s port:%d address:%s flags:0x%x", name, type, domain, port, qPrintable(service.address.toString()), service.flags);
  282. QABDEBUG("TXT: %s", qPrintable(service.textRecords.join(",")));
  283. if (row != -1) {
  284. emit resolved(row);
  285. emit dataChanged(createIndex(row, 0), createIndex(row, 8));
  286. } else if (shouldAdd(m_options, service)) {
  287. QList<int>::iterator it = qLowerBound(m_rowToServiceIndex.begin(), m_rowToServiceIndex.end(), idx);
  288. const int loc = it - m_rowToServiceIndex.begin();
  289. beginInsertRows(QModelIndex(), loc, loc);
  290. m_rowToServiceIndex.insert(it, idx);
  291. endInsertRows();
  292. }
  293. } else {
  294. QABDEBUG("Unhandled event in resolverCallback : %d", event);
  295. }
  296. avahi_service_resolver_free(r);
  297. service.resolver = 0;
  298. }
  299. void QAvahiServiceBrowserModel::resolve(const QModelIndex &index)
  300. {
  301. resolve(index.row());
  302. }
  303. void QAvahiServiceBrowserModel::resolve(int idx)
  304. {
  305. Service &service = m_services[m_rowToServiceIndex.value(idx)];
  306. resolve(&service);
  307. if (!service.resolver) {
  308. m_error = avahi_client_errno(m_client);
  309. m_errorString = avahi_strerror(m_error);
  310. QABDEBUG("Cannot create resolver. %s\n", qPrintable(m_errorString));
  311. emit resolved(idx);
  312. }
  313. }
  314. void QAvahiServiceBrowserModel::resolve(Service *service)
  315. {
  316. if (service->resolver) // already resolving
  317. return;
  318. if (avahi_client_get_state(m_client) != AVAHI_CLIENT_S_RUNNING) // server not running
  319. QABDEBUG("Server is not running yet, cannot resolve. Check if avahi-daemon is running");
  320. service->resolver = avahi_service_resolver_new(m_client, service->interface, service->protocol, service->name.toUtf8().constData(),
  321. service->type.toUtf8().constData(), service->domain.toUtf8().constData(),
  322. AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolver_callback, this /*userdata*/);
  323. }
  324. int QAvahiServiceBrowserModel::rowCount(const QModelIndex &parent) const
  325. {
  326. if (parent.isValid())
  327. return 0;
  328. return m_rowToServiceIndex.count();
  329. }
  330. int QAvahiServiceBrowserModel::columnCount(const QModelIndex &parent) const
  331. {
  332. if (parent.isValid())
  333. return 0;
  334. return 9;
  335. }
  336. QVariant QAvahiServiceBrowserModel::headerData(int section, Qt::Orientation orientation, int role) const
  337. {
  338. if (orientation == Qt::Vertical || role != Qt::DisplayRole)
  339. return QVariant();
  340. switch (section) {
  341. case 0: return tr("Name");
  342. case 1: return tr("Type");
  343. case 2: return tr("Domain");
  344. case 3: return tr("Protocol");
  345. case 4: return tr("Interface");
  346. case 5: return tr("Host name");
  347. case 6: return tr("Address");
  348. case 7: return tr("Port");
  349. case 8: return tr("TXT");
  350. default: return QVariant();
  351. }
  352. }
  353. QVariant QAvahiServiceBrowserModel::data(const QModelIndex &index, int role) const
  354. {
  355. if (!index.isValid())
  356. return QVariant();
  357. const int row = m_rowToServiceIndex.value(index.row());
  358. const QAvahiServiceBrowserModel::Service &service = m_services.at(row);
  359. const int col = index.column();
  360. switch (role) {
  361. case Qt::DisplayRole:
  362. if (col == 0) return service.name;
  363. if (col == 1) return service.type;
  364. if (col == 2) return service.domain;
  365. if (col == 3) return service.protocol;
  366. if (col == 4) return service.interface;
  367. if (col == 5) return service.hostName;
  368. if (col == 6) return service.address.toString();
  369. if (col == 7) return service.port;
  370. if (col == 8) return service.textRecords.join(",");
  371. break;
  372. case NameRole: return service.name;
  373. case TypeRole: return service.type;
  374. case DomainRole: return service.domain;
  375. case ProtocolRole: return service.protocol;
  376. case InterfaceRole: return service.interface;
  377. case HostNameRole: return service.hostName;
  378. case AddressRole: return service.address.toString();
  379. case PortRole: return service.port;
  380. case TxtRole: return service.textRecords.join(",");
  381. default:
  382. break;
  383. }
  384. return QVariant();
  385. }