package com.limegroup.gnutella.gui; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.BindException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import javax.jmdns.JmDNS; import javax.jmdns.ServiceInfo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.FileDesc; import com.limegroup.gnutella.FileManagerEvent; import com.limegroup.gnutella.IncompleteFileDesc; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.filters.IPFilter; import com.limegroup.gnutella.settings.DaapSettings; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.FileUtils; import com.limegroup.gnutella.util.ManagedThread; import com.limegroup.gnutella.util.NetworkUtils; import com.limegroup.gnutella.xml.LimeXMLDocument; import com.limegroup.gnutella.xml.LimeXMLNames; import com.limegroup.gnutella.xml.LimeXMLReplyCollection; import com.limegroup.gnutella.xml.SchemaReplyCollectionMapper; import de.kapsi.net.daap.DaapAuthenticator; import de.kapsi.net.daap.DaapConfig; import de.kapsi.net.daap.DaapFilter; import de.kapsi.net.daap.DaapServer; import de.kapsi.net.daap.DaapServerFactory; import de.kapsi.net.daap.DaapStreamSource; import de.kapsi.net.daap.DaapThreadFactory; import de.kapsi.net.daap.DaapUtil; import de.kapsi.net.daap.Database; import de.kapsi.net.daap.Library; import de.kapsi.net.daap.Playlist; import de.kapsi.net.daap.Song; import de.kapsi.net.daap.Transaction; import de.kapsi.net.daap.TransactionListener; /** * This class handles the mDNS registration and acts as an * interface between LimeWire and DAAP. */ public final class DaapManager implements FinalizeListener { private static final Log LOG = LogFactory.getLog(DaapManager.class); private static final DaapManager INSTANCE = new DaapManager(); public static DaapManager instance() { return INSTANCE; } private SongURNMap map; private Library library; private Database database; private Playlist whatsNew; private Playlist creativecommons; private Playlist videos; private DaapServer server; private RendezvousService rendezvous; private boolean enabled = false; private int maxPlaylistSize; private DaapManager() { GUIMediator.addFinalizeListener(this); } /** * Initializes the Library */ public synchronized void init() { if (isServerRunning()) { setEnabled(enabled); } } /** * Starts the DAAP Server */ public synchronized void start() throws IOException { if (!isServerRunning()) { try { InetAddress addr = InetAddress.getLocalHost(); if (addr.isLoopbackAddress() || !(addr instanceof Inet4Address)) { addr = null; Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); if (interfaces != null) { while(addr == null && interfaces.hasMoreElements()) { NetworkInterface nif = (NetworkInterface)interfaces.nextElement(); Enumeration addresses = nif.getInetAddresses(); while(addresses.hasMoreElements()) { InetAddress address = (InetAddress)addresses.nextElement(); if (!address.isLoopbackAddress() && address instanceof Inet4Address) { addr = address; break; } } } } } if (addr == null) { stop(); // No valid IP address -- just ignore, since // it's probably the user isn't connected to the // internet. Next time they start, it might work. return; } rendezvous = new RendezvousService(addr); map = new SongURNMap(); maxPlaylistSize = DaapSettings.DAAP_MAX_LIBRARY_SIZE.getValue(); String name = DaapSettings.DAAP_LIBRARY_NAME.getValue(); int revisions = DaapSettings.DAAP_LIBRARY_REVISIONS.getValue(); boolean useLibraryGC = DaapSettings.DAAP_LIBRARY_GC.getValue(); library = new Library(name, revisions, useLibraryGC); database = new Database(name); whatsNew = new Playlist(GUIMediator.getStringResource("SEARCH_TYPE_WHATSNEW")); creativecommons = new Playlist(GUIMediator.getStringResource("LICENSE_CC")); videos = new Playlist(GUIMediator.getStringResource("MEDIA_VIDEO")); Transaction txn = library.open(false); library.add(txn, database); database.add(txn, creativecommons); database.add(txn, whatsNew); database.add(txn, videos); creativecommons.setSmartPlaylist(txn, true); whatsNew.setSmartPlaylist(txn, true); videos.setSmartPlaylist(txn, true); txn.commit(); LimeConfig config = new LimeConfig(addr); final boolean NIO = DaapSettings.DAAP_USE_NIO.getValue(); server = DaapServerFactory.createServer(library, config, NIO); server.setAuthenticator(new LimeAuthenticator()); server.setStreamSource(new LimeStreamSource()); server.setFilter(new LimeFilter()); if (!NIO) { server.setThreadFactory(new LimeThreadFactory()); } final int maxAttempts = 10; for(int i = 0; i < maxAttempts; i++) { try { server.bind(); break; } catch (BindException bindErr) { if (i < (maxAttempts-1)) { // try next port... config.nextPort(); } else { throw bindErr; } } } Thread serverThread = new ManagedThread(server, "DaapServerThread") { protected void managedRun() { try { super.managedRun(); } catch (Throwable t) { DaapManager.this.stop(); if(!handleError(t)) { GUIMediator.showError("ERROR_DAAP_RUN_ERROR"); DaapSettings.DAAP_ENABLED.setValue(false); if(t instanceof RuntimeException) throw (RuntimeException)t; throw new RuntimeException(t); } } } }; serverThread.setDaemon(true); serverThread.start(); rendezvous.registerService(); } catch (IOException err) { stop(); throw err; } } } /** * Stops the DAAP Server and releases all resources */ public synchronized void stop() { if (rendezvous != null) rendezvous.close(); if (server != null) { server.stop(); server = null; } if (map != null) map.clear(); rendezvous = null; map = null; library = null; whatsNew = null; creativecommons = null; database = null; } /** * Restarts the DAAP server and re-registers it via mDNS. * This is equivalent to:<p> * * <code> * stop(); * start(); * init(); * </code> */ public synchronized void restart() throws IOException { if (isServerRunning()) stop(); start(); init(); } /** * Shutdown the DAAP service properly. In this case * is the main focus on mDNS (Rendezvous) as in * some rare cases iTunes doesn't recognize that * LimeWire/DAAP is no longer online. */ public void doFinalize() { stop(); } /** * Updates the multicast-DNS servive info */ public synchronized void updateService() throws IOException { if (isServerRunning()) { rendezvous.updateService(); Transaction txn = library.open(false); String name = DaapSettings.DAAP_LIBRARY_NAME.getValue(); library.setName(txn, name); database.setName(txn, name); txn.commit(); server.update(); } } /** * Disconnects all clients */ public synchronized void disconnectAll() { if (isServerRunning()) { server.disconnectAll(); } } /** * Returns <tt>true</tt> if server is running */ public synchronized boolean isServerRunning() { if (server != null) { return server.isRunning(); } return false; } /** * Attempts to handle an exception. * Returns true if we could handle it correctly. */ private boolean handleError(Throwable t) { if(t == null) return false; String msg = t.getMessage(); if(msg == null || msg.indexOf("Unable to establish loopback connection") == -1) return handleError(t.getCause()); // Problem with XP SP2. -- Loopback connections are disallowed. // Why? Who knows. This patch fixes it: // http://support.microsoft.com/default.aspx?kbid=884020 if(CommonUtils.isWindowsXP()) { int answer = GUIMediator.showYesNoCancelMessage("ERROR_DAAP_LOOPBACK_FAILED"); switch(answer) { case GUIMediator.YES_OPTION: GUIMediator.openURL("http://support.microsoft.com/default.aspx?kbid=884020"); break; case GUIMediator.NO_OPTION: DaapSettings.DAAP_ENABLED.setValue(false); break; } } else { // Also a problem on non XP systems with firewalls. int answer = GUIMediator.showYesNoMessage("ERROR_DAAP_LOOPBACK_FAILED_NONXP"); if(answer == GUIMediator.NO_OPTION) DaapSettings.DAAP_ENABLED.setValue(false); } return true; } /** * Returns true if the extension of name is a supported file type. */ private static boolean isSupportedAudioFormat(String name) { return isSupportedFormat(DaapSettings.DAAP_SUPPORTED_AUDIO_FILE_TYPES.getValue(), name); } private static boolean isSupportedVideoFormat(String name) { return isSupportedFormat(DaapSettings.DAAP_SUPPORTED_VIDEO_FILE_TYPES.getValue(), name); } private static boolean isSupportedFormat(String[] types, String name) { for(int i = 0; i < types.length; i++) { if (name.endsWith(types[i])) { return true; } } return false; } /** * Handles a change event. */ private void handleChangeEvent(FileManagerEvent evt) { FileDesc oldDesc = evt.getFileDescs()[0]; Song song = map.remove(oldDesc.getSHA1Urn()); if (song != null) { FileDesc newDesc = evt.getFileDescs()[1]; map.put(song, newDesc.getSHA1Urn()); // Any changes in the meta data? if (updateSongAudioMeta(song, newDesc) || updateSongVideoMeta(song, newDesc)) { Transaction txn = library.open(true); txn.addTransactionListener(new ServerUpdater(server)); database.update(txn, song); } } } /** * Handles an add event. */ private void handleAddEvent(FileManagerEvent evt) { if (database.getMasterPlaylist().size() >= maxPlaylistSize) return; FileDesc file = evt.getFileDescs()[0]; if (!(file instanceof IncompleteFileDesc)) { String name = file.getFileName().toLowerCase(Locale.US); Song song = null; if (isSupportedAudioFormat(name)) { song = createSong(file, true); } else if (isSupportedVideoFormat(name)) { song = createSong(file, false); } if (song != null) { map.put(song, file.getSHA1Urn()); Transaction txn = library.open(true); txn.addTransactionListener(new ServerUpdater(server)); database.getMasterPlaylist().add(txn, song); whatsNew.add(txn, song); if (file.isLicensed()) { creativecommons.add(txn, song); } if (isSupportedVideoFormat(name)) { videos.add(txn, song); } } } } /** * Handles a rename event. */ private void handleRenameEvent(FileManagerEvent evt) { FileDesc oldDesc = evt.getFileDescs()[0]; Song song = map.remove(oldDesc.getSHA1Urn()); if (song != null) { FileDesc newDesc = evt.getFileDescs()[1]; map.put(song, newDesc.getSHA1Urn()); } } /** * Handles a remove event. */ private void handleRemoveEvent(FileManagerEvent evt) { FileDesc file = evt.getFileDescs()[0]; Song song = map.remove(file.getSHA1Urn()); if (song != null) { Transaction txn = library.open(true); txn.addTransactionListener(new ServerUpdater(server)); database.remove(txn, song); } } /** * Called by VisualConnectionCallback */ public synchronized void handleFileManagerEvent(FileManagerEvent evt) { if (!enabled || !isServerRunning()) return; if (evt.isChangeEvent()) handleChangeEvent(evt); else if (evt.isAddEvent()) handleAddEvent(evt); else if (evt.isRenameEvent()) handleRenameEvent(evt); else if (evt.isRemoveEvent()) handleRemoveEvent(evt); } /** * Called by VisualConnectionCallback/MetaFileManager. */ public void fileManagerLoading() { setEnabled(false); } /** * Called by VisualConnectionCallback/MetaFileManager. */ public void fileManagerLoaded() { setEnabled(true); } public synchronized boolean isEnabled() { return enabled; } private synchronized void setEnabled(boolean enabled) { this.enabled = enabled; //System.out.println("setEnabled: " + enabled); if (!enabled || !isServerRunning()) return; int size = database.getMasterPlaylist().size(); Transaction txn = library.open(false); SongURNMap tmpMap = new SongURNMap(); FileDesc[] files = RouterService.getFileManager().getAllSharedFileDescriptors(); for(int i = 0; i < files.length; i++) { FileDesc file = files[i]; if(file instanceof IncompleteFileDesc) { continue; } String name = file.getFileName().toLowerCase(Locale.US); boolean audio = isSupportedAudioFormat(name); if(!audio && !isSupportedVideoFormat(name)) { continue; } URN urn = file.getSHA1Urn(); // 1) // _Remove_ URN from the current 'map'... Song song = map.remove(urn); // Check if URN is already in the tmpMap. // If so do nothing as we don't want add // the same file multible times... if(tmpMap.contains(urn)) { continue; } // This URN was already mapped with a Song. // Save the Song (again) and update the meta // data if necessary if (song != null) { tmpMap.put(song, urn); // Any changes in the meta data? if ((audio && updateSongAudioMeta(song, file)) || updateSongVideoMeta(song, file)) { database.update(txn, song); } } else if (size < maxPlaylistSize){ // URN was unknown and we must create a // new Song for this URN... song = createSong(file, audio); tmpMap.put(song, urn); database.getMasterPlaylist().add(txn, song); if (file.isLicensed()) { creativecommons.add(txn, song); } if (isSupportedVideoFormat(name)) { videos.add(txn, song); } size++; } } // See 1) // As all known URNs were removed from 'map' only // deleted FileDesc URNs can be leftover! We must // remove the associated Songs from the Library now Iterator it = map.getSongIterator(); while(it.hasNext()) { Song song = (Song)it.next(); database.remove(txn, song); } map.clear(); map = tmpMap; // tempMap is the new 'map' txn.addTransactionListener(new ServerUpdater(server)); txn.commit(); } /** * Create a Song and sets its meta data with * the data which is retrieved from the FileDesc */ private Song createSong(FileDesc desc, boolean audio) { Song song = new Song(desc.getFileName()); song.setSize((int)desc.getFileSize()); song.setDateAdded((int)(System.currentTimeMillis()/1000)); File file = desc.getFile(); String ext = FileUtils.getFileExtension(file); if (ext != null) { // Note: This is required for formats other than MP3 // For example AAC (.m4a) files won't play if no // format is set. As far as I can tell from the iTunes // 'Get Info' dialog are Songs assumed as MP3 until // a format is set explicit. song.setFormat(ext.toLowerCase(Locale.US)); if (audio) { updateSongAudioMeta(song, desc); } else { updateSongVideoMeta(song, desc); } } return song; } private boolean updateSongVideoMeta(Song song, FileDesc desc) { SchemaReplyCollectionMapper map = SchemaReplyCollectionMapper.instance(); LimeXMLReplyCollection collection = map.getReplyCollection(LimeXMLNames.VIDEO_SCHEMA); if (collection == null) { LOG.error("LimeXMLReplyCollection is null"); return false; } LimeXMLDocument doc = collection.getDocForHash(desc.getSHA1Urn()); if (doc == null) { return false; } boolean update = false; String title = doc.getValue(LimeXMLNames.VIDEO_TITLE); //String type = doc.getValue(LimeXMLNames.VIDEO_TYPE); String year = doc.getValue(LimeXMLNames.VIDEO_YEAR); String rating = doc.getValue(LimeXMLNames.VIDEO_RATING); String length = doc.getValue(LimeXMLNames.VIDEO_LENGTH); //String comments = doc.getValue(LimeXMLNames.VIDEO_COMMENTS); //String licensetype = doc.getValue(LimeXMLNames.VIDEO_LICENSETYPE); String license = doc.getValue(LimeXMLNames.VIDEO_LICENSE); //String height = doc.getValue(LimeXMLNames.VIDEO_HEIGHT); //String width = doc.getValue(LimeXMLNames.VIDEO_WIDTH); String bitrate = doc.getValue(LimeXMLNames.VIDEO_BITRATE); //String action = doc.getValue(LimeXMLNames.VIDEO_ACTION); String director = doc.getValue(LimeXMLNames.VIDEO_DIRECTOR); //String studio = doc.getValue(LimeXMLNames.VIDEO_STUDIO); //String language = doc.getValue(LimeXMLNames.VIDEO_LANGUAGE); //String stars = doc.getValue(LimeXMLNames.VIDEO_STARS); //String producer = doc.getValue(LimeXMLNames.VIDEO_PRODUCE); //String subtitles = doc.getValue(LimeXMLNames.VIDEO_SUBTITLES); if (title != null) { String currentTitle = song.getName(); if (currentTitle == null || !title.equals(currentTitle)) { update = true; song.setName(title); } } int currentBitrate = song.getBitrate(); if (bitrate != null) { try { int num = Integer.parseInt(bitrate); if (num > 0 && num != currentBitrate) { update = true; song.setBitrate(num); } } catch (NumberFormatException err) {} } else if (currentBitrate != 0) { update = true; song.setBitrate(0); } int currentLength = song.getTime(); if (length != null) { try { // iTunes expects the song length in milliseconds int num = (int)Integer.parseInt(length)*1000; if (num > 0 && num != currentLength) { update = true; song.setTime(num); } } catch (NumberFormatException err) {} } else if (currentLength != 0) { update = true; song.setTime(0); } int currentYear = song.getYear(); if (year != null) { try { int num = Integer.parseInt(year); if (num > 0 && num != currentYear) { update = true; song.setYear(num); } } catch (NumberFormatException err) {} } else if (currentYear != 0) { update = true; song.setYear(0); } // Genre = License String currentGenre = song.getGenre(); if (license != null) { if (currentGenre == null || !license.equals(currentGenre)) { update = true; song.setGenre(license); } } else if (currentGenre != null) { update = true; song.setGenre(null); } // Artist = Director String currentArtist = song.getArtist(); if (director != null) { if (currentArtist == null || !director.equals(currentArtist)) { update = true; song.setArtist(director); } } else if (currentArtist != null) { update = true; song.setArtist(null); } // Rating = Album String currentAlbum = song.getAlbum(); if (rating != null) { if (currentAlbum == null || !rating.equals(currentAlbum)) { update = true; song.setAlbum(rating); } } else if (currentAlbum != null) { update = true; song.setAlbum(null); } return update; } /** * Sets the audio meta data */ private boolean updateSongAudioMeta(Song song, FileDesc desc) { SchemaReplyCollectionMapper map = SchemaReplyCollectionMapper.instance(); LimeXMLReplyCollection collection = map.getReplyCollection(LimeXMLNames.AUDIO_SCHEMA); if (collection == null) { LOG.error("LimeXMLReplyCollection is null"); return false; } LimeXMLDocument doc = collection.getDocForHash(desc.getSHA1Urn()); if (doc == null) return false; boolean update = false; String title = doc.getValue(LimeXMLNames.AUDIO_TITLE); String track = doc.getValue(LimeXMLNames.AUDIO_TRACK); String artist = doc.getValue(LimeXMLNames.AUDIO_ARTIST); String album = doc.getValue(LimeXMLNames.AUDIO_ALBUM); String genre = doc.getValue(LimeXMLNames.AUDIO_GENRE); String bitrate = doc.getValue(LimeXMLNames.AUDIO_BITRATE); //String comments = doc.getValue(LimeXMLNames.AUDIO_COMMENTS); String time = doc.getValue(LimeXMLNames.AUDIO_SECONDS); String year = doc.getValue(LimeXMLNames.AUDIO_YEAR); if (title != null) { String currentTitle = song.getName(); if (currentTitle == null || !title.equals(currentTitle)) { update = true; song.setName(title); } } int currentTrack = song.getTrackNumber(); if (track != null) { try { int num = Integer.parseInt(track); if (num > 0 && num != currentTrack) { update = true; song.setTrackNumber(num); } } catch (NumberFormatException err) {} } else if (currentTrack != 0) { update = true; song.setTrackNumber(0); } String currentArtist = song.getArtist(); if (artist != null) { if (currentArtist == null || !artist.equals(currentArtist)) { update = true; song.setArtist(artist); } } else if (currentArtist != null) { update = true; song.setArtist(null); } String currentAlbum = song.getAlbum(); if (album != null) { if (currentAlbum == null || !album.equals(currentAlbum)) { update = true; song.setAlbum(album); } } else if (currentAlbum != null) { update = true; song.setAlbum(null); } String currentGenre = song.getGenre(); if (genre != null) { if (currentGenre == null || !genre.equals(currentGenre)) { update = true; song.setGenre(genre); } } else if (currentGenre != null) { update = true; song.setGenre(null); } /*String currentComments = song.getComment(); if (comments != null) { if (currentComments == null || !comments.equals(currentComments)) { update = true; song.setComment(comments); } } else if (currentComments != null) { update = true; song.setComment(null); }*/ int currentBitrate = song.getBitrate(); if (bitrate != null) { try { int num = Integer.parseInt(bitrate); if (num > 0 && num != currentBitrate) { update = true; song.setBitrate(num); } } catch (NumberFormatException err) {} } else if (currentBitrate != 0) { update = true; song.setBitrate(0); } int currentTime = song.getTime(); if (time != null) { try { // iTunes expects the song length in milliseconds int num = (int)Integer.parseInt(time)*1000; if (num > 0 && num != currentTime) { update = true; song.setTime(num); } } catch (NumberFormatException err) {} } else if (currentTime != 0) { update = true; song.setTime(0); } int currentYear = song.getYear(); if (year != null) { try { int num = Integer.parseInt(year); if (num > 0 && num != currentYear) { update = true; song.setYear(num); } } catch (NumberFormatException err) {} } else if (currentYear != 0) { update = true; song.setYear(0); } // iTunes expects the date/time in seconds int mod = (int)(desc.lastModified()/1000); if (song.getDateModified() != mod) { update = true; song.setDateModified(mod); } return update; } /** * This factory creates ManagedThreads for the DAAP server */ private final class LimeThreadFactory implements DaapThreadFactory { public Thread createDaapThread(Runnable runner, String name) { Thread thread = new ManagedThread(runner, name); thread.setDaemon(true); return thread; } } /** * Handles the audio stream */ private final class LimeStreamSource implements DaapStreamSource { public FileInputStream getSource(Song song) throws IOException { URN urn = map.get(song); if (urn != null) { FileDesc fileDesc = RouterService.getFileManager().getFileDescForUrn(urn); if(fileDesc != null) return new FileInputStream(fileDesc.getFile()); } return null; } } /** * Implements the DaapAuthenticator */ private final class LimeAuthenticator implements DaapAuthenticator { public boolean requiresAuthentication() { return DaapSettings.DAAP_REQUIRES_PASSWORD.getValue(); } /** * Returns true if username and password are correct.<p> * Note: iTunes does not support usernames (i.e. it's * don't care)! */ public boolean authenticate(String username, String password) { return DaapSettings.DAAP_PASSWORD.equals(password); } } /** * The DAAP Library should be only accessable from the LAN * as we can not guarantee for the required bandwidth and it * could be used to bypass Gnutella etc. Note: iTunes can't * connect to DAAP Libraries outside of the LAN but certain * iTunes download tools can. */ private final class LimeFilter implements DaapFilter { /** * Returns true if <tt>address</tt> is a private address */ public boolean accept(InetAddress address) { byte[] addr = address.getAddress(); try { // not private & not close, not allowed. if(!NetworkUtils.isVeryCloseIP(addr) && !NetworkUtils.isPrivateAddress(addr)) return false; } catch (IllegalArgumentException err) { LOG.error(err); return false; } // Is it a annoying fellow? >:-) return IPFilter.instance().allow(addr); } } /** * A LimeWire specific implementation of DaapConfig */ private final class LimeConfig implements DaapConfig { private InetAddress addr; public LimeConfig(InetAddress addr) { this.addr = addr; // Reset PORT to default value to prevent increasing // it to infinity DaapSettings.DAAP_PORT.revertToDefault(); } public String getServerName() { return CommonUtils.getHttpServer(); } public void nextPort() { int port = DaapSettings.DAAP_PORT.getValue(); DaapSettings.DAAP_PORT.setValue(port+1); } public int getBacklog() { return 0; } public InetSocketAddress getInetSocketAddress() { int port = DaapSettings.DAAP_PORT.getValue(); return new InetSocketAddress(addr, port); } public int getMaxConnections() { return DaapSettings.DAAP_MAX_CONNECTIONS.getValue(); } } /** * Helps us to publicize and update the DAAP Service via * multicast-DNS (aka Rendezvous or Zeroconf) */ private final class RendezvousService { private static final String VERSION = "Version"; private static final String MACHINE_NAME = "Machine Name"; private static final String PASSWORD = "Password"; private final JmDNS zeroConf; private ServiceInfo service; public RendezvousService(InetAddress addr) throws IOException { zeroConf = new JmDNS(addr); } public boolean isRegistered() { return (service != null); } private ServiceInfo createServiceInfo() { String type = DaapSettings.DAAP_TYPE_NAME.getValue(); String name = DaapSettings.DAAP_SERVICE_NAME.getValue(); int port = DaapSettings.DAAP_PORT.getValue(); int weight = DaapSettings.DAAP_WEIGHT.getValue(); int priority = DaapSettings.DAAP_PRIORITY.getValue(); boolean password = DaapSettings.DAAP_REQUIRES_PASSWORD.getValue(); java.util.Hashtable props = new java.util.Hashtable(); // Greys the share and the playlist names when iTunes's // protocol version is different from this version. It's // only a nice visual effect and has no impact to the // ability to connect this server! Disabled because // iTunes 4.2 is still widespread... props.put(VERSION, Integer.toString(DaapUtil.VERSION_3)); // This is the inital share name props.put(MACHINE_NAME, name); // shows the small lock if Service is protected // by a password! props.put(PASSWORD, Boolean.toString(password)); String qualifiedName = null; // This isn't really required but as iTunes // does it in this way I'm doing it too... if (password) { qualifiedName = name + "_PW." + type; } else { qualifiedName = name + "." + type; } ServiceInfo serviceInfo = new ServiceInfo(type, qualifiedName, port, weight, priority, props); return serviceInfo; } public void registerService() throws IOException { if (isRegistered()) throw new IOException(); ServiceInfo serviceInfo = createServiceInfo(); zeroConf.registerService(serviceInfo); this.service = serviceInfo; } public void unregisterService() { if (!isRegistered()) return; zeroConf.unregisterService(service); service = null; } public void updateService() throws IOException { if (!isRegistered()) throw new IOException(); if (service.getPort() != DaapSettings.DAAP_PORT.getValue()) unregisterService(); ServiceInfo serviceInfo = createServiceInfo(); zeroConf.registerService(serviceInfo); this.service = serviceInfo; } public void close() { unregisterService(); zeroConf.close(); } } /** * A simple wrapper for a two way mapping as we have to * deal in both directions with FileManager and DaapServer * <p> * Song -> URN * URN -> Song */ private final class SongURNMap { private HashMap /* Song -> URN */ songToUrn = new HashMap(); private HashMap /* URN -> Song */ urnToSong = new HashMap(); public SongURNMap() { } public void put(Song song, URN urn) { songToUrn.put(song, urn); urnToSong.put(urn, song); } public URN get(Song song) { return (URN)songToUrn.get(song); } public Song get(URN urn) { return (Song)urnToSong.get(urn); } public Song remove(URN urn) { Song song = (Song)urnToSong.remove(urn); if (song != null) songToUrn.remove(song); return song; } public URN remove(Song song) { URN urn = (URN)songToUrn.remove(song); if (urn != null) urnToSong.remove(urn); return urn; } public boolean contains(URN urn) { return urnToSong.containsKey(urn); } public boolean contains(Song song) { return songToUrn.containsKey(song); } public Iterator getSongIterator() { return songToUrn.keySet().iterator(); } public Iterator getURNIterator() { return urnToSong.keySet().iterator(); } public void clear() { urnToSong.clear(); songToUrn.clear(); } public int size() { // NOTE: songToUrn.size() == urnToSong.size() return songToUrn.size(); } } private static class ServerUpdater implements TransactionListener { private DaapServer server; private ServerUpdater(DaapServer server) { this.server = server; } public void commit(Transaction arg0) { if (server != null) { server.update(); } } public void rollback(Transaction arg0) { } } }