/* * Created by Angel Leon (@gubatron), Alden Torres (aldenml) * Copyright (c) 2011-2014, FrostWire(R). All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.frostwire.localpeer; import java.io.IOException; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.frostwire.util.JsonUtils; /** * * Not thread safe. * * @author gubatron * @author aldenml * */ public final class LocalPeerManagerImpl implements LocalPeerManager { private static final Logger LOG = LoggerFactory.getLogger(LocalPeerManagerImpl.class); private static final String JMDNS_NAME = "LocalPeerManagerJmDNS"; private static final String SERVICE_TYPE = "_fw_local_peer._tcp.local."; private static final String PEER_PROPERTY = "peer"; private final MulticastLock lock; private final ServiceListener serviceListener; private final Map<String, LocalPeer> cache; private JmDNS jmdns; private ServiceInfo serviceInfo; private LocalPeerManagerListener listener; public LocalPeerManagerImpl(MulticastLock lock) { this.lock = lock; this.serviceListener = new JmDNSServiceListener(); this.cache = new ConcurrentHashMap<String, LocalPeer>(); } public LocalPeerManagerImpl() { this(null); } public LocalPeerManagerListener getListener() { return listener; } public void setListener(LocalPeerManagerListener listener) { this.listener = listener; } @Override public void start(InetAddress addr, LocalPeer peer) { try { cache.clear(); if (jmdns != null) { LOG.warn("JmDNS already working, review the logic"); stop(); } if (lock != null) { lock.acquire(); } if (addr != null) { jmdns = JmDNS.create(addr, JMDNS_NAME); } else { jmdns = JmDNS.create(JMDNS_NAME); } jmdns.addServiceListener(SERVICE_TYPE, serviceListener); serviceInfo = createService(peer, jmdns); jmdns.registerService(serviceInfo); } catch (Throwable e) { LOG.error("Unable to start local peer manager", e); } } @Override public void start(LocalPeer peer) { start(null, peer); } @Override public void stop() { try { if (jmdns != null) { triggerLocalServiceRemoved(); jmdns.removeServiceListener(SERVICE_TYPE, serviceListener); jmdns.unregisterAllServices(); try { jmdns.close(); } catch (IOException e) { LOG.error("Error closing JmDNS", e); } jmdns = null; } if (lock != null) { lock.release(); } cache.clear(); } catch (Throwable e) { LOG.error("Error stopping local peer manager", e); } } @Override public void update(LocalPeer peer) { try { if (jmdns != null) { serviceInfo.setText(createProps(peer, jmdns)); } } catch (Throwable e) { LOG.error("Error refreshing local peer manager", e); } } private ServiceInfo createService(LocalPeer peer, JmDNS jmdns) { return ServiceInfo.create(SERVICE_TYPE, peer.nickname, peer.port, 0, 0, false, createProps(peer, jmdns)); } private Map<String, Object> createProps(LocalPeer peer, JmDNS jmdns) { if (jmdns != null) { // fix ip address peer = peer.withAddress(getHostAddress(jmdns)).withLocal(false); } Map<String, Object> props = new HashMap<String, Object>(); props.put(PEER_PROPERTY, JsonUtils.toJson(peer)); return props; } private String getHostAddress(JmDNS jmdns) { if (jmdns == null) { throw new IllegalArgumentException("jmdns can't be null"); } try { return jmdns.getInetAddress().getHostAddress(); } catch (IOException e) { throw new RuntimeException(e); } } private void triggerLocalServiceRemoved() { if (listener != null && serviceInfo != null) { try { LocalPeer peer = cache.get(serviceInfo.getKey()); if (peer != null) { peer = peer.withLocal(true); cache.remove(serviceInfo.getKey()); listener.peerRemoved(peer); } } catch (Throwable e) { LOG.error("Error in client listener", e); } } } private final class JmDNSServiceListener implements ServiceListener { @Override public void serviceResolved(ServiceEvent event) { if (listener != null) { try { ServiceInfo info = event.getInfo(); LocalPeer peer = getPeer(info); if (peer != null) { if (isLocal(info, peer)) { peer = peer.withLocal(true); } else { peer = peer.withLocal(false); } cache.put(info.getKey(), peer); listener.peerResolved(peer); } } catch (Throwable e) { LOG.error("Error in client listener", e); } } } @Override public void serviceRemoved(ServiceEvent event) { if (listener != null) { try { ServiceInfo info = event.getInfo(); LocalPeer peer = cache.get(info.getKey()); if (peer != null) { cache.remove(info.getKey()); listener.peerRemoved(peer); } } catch (Throwable e) { LOG.error("Error in client listener", e); } } } @Override public void serviceAdded(ServiceEvent event) { if (jmdns != null) { jmdns.requestServiceInfo(event.getType(), event.getName(), 1); } } private LocalPeer getPeer(ServiceInfo info) { LocalPeer peer = null; try { String address = info.getHostAddresses()[0]; int port = info.getPort(); String json = info.getPropertyString(PEER_PROPERTY); if (json != null) { peer = JsonUtils.toObject(json, LocalPeer.class); // update peer with actual address and port peer = peer.withAddress(address).withPort(port); } } catch (Throwable e) { LOG.error("Unable to extract peer info from service event", e); } return peer; } private boolean isLocal(ServiceInfo info, LocalPeer p) { return info.getKey().equals(serviceInfo.getKey()) && p.address.equals(getHostAddress(jmdns)); } } }