/* * Digital Audio Access Protocol (DAAP) Library * Copyright (C) 2004-2010 Roger Kapsi * * 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 org.ardverk.daap; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import org.ardverk.daap.chunks.Chunk; import org.ardverk.daap.chunks.impl.DatabasePlaylists; import org.ardverk.daap.chunks.impl.DatabaseSongs; import org.ardverk.daap.chunks.impl.DeletedIdListing; import org.ardverk.daap.chunks.impl.ItemId; import org.ardverk.daap.chunks.impl.Listing; import org.ardverk.daap.chunks.impl.ListingItem; import org.ardverk.daap.chunks.impl.ReturnedCount; import org.ardverk.daap.chunks.impl.SpecifiedTotalCount; import org.ardverk.daap.chunks.impl.Status; import org.ardverk.daap.chunks.impl.UpdateType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Database is a container for Playlists and it keeps track of all Songs in * the Database whereat it is not responsible for the actual management of the * Songs (it's only interested in the Song IDs). * * @author Roger Kapsi */ public class Database { private static final Logger LOG = LoggerFactory.getLogger(Database.class); /** databaseId is an 32bit unsigned value! */ private static final AtomicLong DATABASE_ID = new AtomicLong(); /** unique id */ private final long itemId; /** unique persistent id */ private final long persistentId; /** Name of this Database */ private String name; /** * The total number of Playlists in this Database */ private int totalPlaylistCount = 0; /** * The total number of Songs in this Database */ private int totalSongCount = 0; /** A List of Playlists */ private final List<Playlist> playlists = new ArrayList<Playlist>(); /** Set of deleted playlists */ private Set<Playlist> deletedPlaylists = null; /** Set of deleted Songs */ private Set<Song> deletedSongs = null; /** master playlist */ private Playlist masterPlaylist = null; protected Database(Database database, Transaction txn) { this.itemId = database.itemId; this.persistentId = database.persistentId; this.name = database.name; if (database.deletedPlaylists != null) { this.deletedPlaylists = database.deletedPlaylists; database.deletedPlaylists = null; } Set<Song> songs = database.getSongs(); for (Playlist playlist : database.playlists) { if (txn.modified(playlist)) { if (deletedPlaylists == null || !deletedPlaylists.contains(playlist)) { Playlist clone = new Playlist(playlist, txn); playlists.add(clone); if (playlist == database.masterPlaylist) { this.masterPlaylist = clone; } } Set<Song> deletedSongs = playlist.getDeletedSongs(); if (deletedSongs != null && !deletedSongs.isEmpty()) { if (this.deletedSongs == null) { this.deletedSongs = new HashSet<Song>(deletedSongs); } else { this.deletedSongs.addAll(deletedSongs); } } } } if (deletedSongs != null) { deletedSongs.removeAll(songs); } this.totalPlaylistCount = database.playlists.size(); this.totalSongCount = songs.size(); } public Database(String name) { this(name, new Playlist(name)); } /** * Create a new Database with the name * * @param name * a name for this Database */ public Database(String name, Playlist masterPlaylist) { this.itemId = DATABASE_ID.getAndIncrement(); this.persistentId = Library.nextPersistentId(); this.name = name; this.totalPlaylistCount = 0; this.totalSongCount = 0; this.masterPlaylist = masterPlaylist; addPlaylistP(null, masterPlaylist); } /** * Returns the unique id of this Database * * @return unique id of this Database */ protected long getItemId() { return itemId; } /** * Returns the name of this Database. * * @return name of this Database */ public String getName() { return name; } /** * Sets the name of this Database. * * @param new name */ public void setName(Transaction txn, final String name) { if (txn != null) { txn.addTxn(this, new Txn() { public void commit(Transaction txn) { setNameP(txn, name); } }); } else { setNameP(txn, name); } masterPlaylist.setName(txn, name); } private void setNameP(Transaction txn, String name) { this.name = name; } /** * The persistent id of this Database. Unused at the moment! * * @return the persistent id of this Database */ protected long getPersistentId() { return persistentId; } /** * Returns the master Playlist. The master Playlist is created automatically * by the Database! There's no technical difference between a master * Playlist and a usual Playlist except that it cannot be removed from the * Database. * * @return the master Playlist */ public Playlist getMasterPlaylist() { return masterPlaylist; } /** * Returns an unmodifiable Set with all Playlists in this Database * * @return unmodifiable Set of Playlists */ public List<Playlist> getPlaylists() { return Collections.unmodifiableList(playlists); } /** * Adds playlist to this Database * * @param txn * a Transaction * @param playlist * the Playliost to add */ public void addPlaylist(Transaction txn, final Playlist playlist) { if (masterPlaylist.equals(playlist)) { throw new DaapException("You cannot add the master playlist."); } if (txn != null) { txn.addTxn(this, new Txn() { public void commit(Transaction txn) { addPlaylistP(txn, playlist); } }); txn.attach(playlist); } else { addPlaylistP(txn, playlist); } } private void addPlaylistP(Transaction txn, Playlist playlist) { if (!containsPlaylist(playlist) && playlists.add(playlist)) { totalPlaylistCount = playlists.size(); if (deletedPlaylists != null && deletedPlaylists.remove(playlist) && deletedPlaylists.isEmpty()) { deletedPlaylists = null; } } } /** * Removes playlist from this Database * * @param txn * a Transaction * @param playlist * the Playlist to remove */ public void removePlaylist(Transaction txn, final Playlist playlist) { if (masterPlaylist.equals(playlist)) { throw new DaapException("You cannot remove the master playlist."); } if (txn != null) { txn.addTxn(this, new Txn() { public void commit(Transaction txn) { removePlaylistP(txn, playlist); } }); } else { removePlaylistP(txn, playlist); } } private void removePlaylistP(Transaction txn, Playlist playlist) { if (playlists.remove(playlist)) { totalPlaylistCount = playlists.size(); if (deletedPlaylists == null) { deletedPlaylists = new HashSet<Playlist>(); } deletedPlaylists.add(playlist); } } /** * Returns the number of Playlists in this Database */ public int getPlaylistCount() { return playlists.size(); } /** * Returns true if playlist is in this Database * * @param playlist * @return true if Database contains playlist */ public boolean containsPlaylist(Playlist playlist) { return playlists.contains(playlist); } /** * Performs a select on this Database and returns something for the request * or <code>null</code> * * @param request * a DaapRequest * @return a response for the DaapRequest */ protected Object select(DaapRequest request) { if (request.isSongRequest()) { return getSong(request); } else if (request.isDatabaseSongsRequest()) { return getDatabaseSongs(request); } else if (request.isDatabasePlaylistsRequest()) { return getDatabasePlaylist(request); } else if (request.isPlaylistSongsRequest()) { Playlist playlist = getPlaylist(request); if (playlist == null) { if (LOG.isInfoEnabled()) { LOG.info("No playlist " + request.getContainerId() + " known in Database " + itemId); } return null; } return playlist.select(request); } if (LOG.isInfoEnabled()) { LOG.info("Unknown request: " + request); } return null; } @Override public String toString() { return "Database(" + getItemId() + ", " + getName() + ")"; } @Override public int hashCode() { return (int) (getItemId() & Integer.MAX_VALUE); } @Override public boolean equals(Object o) { if (!(o instanceof Database)) { return false; } return ((Database) o).getItemId() == getItemId(); } /** * Returns all Songs in this Database */ public Set<Song> getSongs() { Set<Song> songs = null; for (Playlist playlist : playlists) { if (!(playlist instanceof Folder)) { if (songs == null) { songs = new HashSet<Song>(playlist.getSongs()); } else { songs.addAll(playlist.getSongs()); } } } if (songs == null) { return Collections.emptySet(); } else { return Collections.unmodifiableSet(songs); } } /** * Returns the number of Songs in this Database */ public int getSongCount() { return getSongs().size(); } /** * Returns true if song is in this Database */ public boolean containsSong(Song song) { for (Playlist playlist : playlists) { if (playlist.containsSong(song)) { return true; } } return false; } /** * Adds Song to all Playlists * * @param txn * @param song */ public void addSong(Transaction txn, Song song) { for (Playlist playlist : playlists) { if (!(playlist instanceof Folder)) { playlist.addSong(txn, song); } } } /** * Removes Song from all Playlists * * @param txn * @param song */ public void removeSong(Transaction txn, Song song) { for (Playlist playlist : playlists) { if (!(playlist instanceof Folder)) { playlist.removeSong(txn, song); } } } public Set<Playlist> getSongPlaylists(Song song) { Set<Playlist> ret = null; for (Playlist playlist : playlists) { if (playlist.containsSong(song)) { if (ret == null) { ret = new HashSet<Playlist>(); } ret.add(playlist); } } if (ret != null) { return Collections.unmodifiableSet(ret); } return Collections.emptySet(); } /** * Gets and returns a Song by its ID * * @param songId * @return */ protected Song getSong(DaapRequest request) { for (Playlist playlist : playlists) { if (!(playlist instanceof Folder)) { Song song = playlist.getSong(request); if (song != null) { return song; } } } return null; } /** * Gets and returns a Playlist by its ID * * @param playlistId * @return */ protected Playlist getPlaylist(DaapRequest request) { long playlistId = request.getContainerId(); for (Playlist playlist : playlists) { if (playlist.getItemId() == playlistId) { return playlist; } } return null; } private DatabasePlaylists getDatabasePlaylist(DaapRequest request) { DatabasePlaylists databasePlaylists = new DatabasePlaylists(); databasePlaylists.add(new Status(200)); databasePlaylists.add(new UpdateType(request.isUpdateType() ? 1 : 0)); databasePlaylists.add(new SpecifiedTotalCount(totalPlaylistCount)); databasePlaylists.add(new ReturnedCount(playlists.size())); Listing listing = new Listing(); for (Playlist playlist : playlists) { ListingItem listingItem = new ListingItem(); for (String key : request.getMeta()) { Chunk chunk = playlist.getChunk(key); if (chunk != null) { listingItem.add(chunk); } else if (LOG.isInfoEnabled()) { LOG.info("Unknown chunk type: " + key); } } listing.add(listingItem); } databasePlaylists.add(listing); if (request.isUpdateType() && deletedPlaylists != null) { DeletedIdListing deletedListing = new DeletedIdListing(); for (Playlist playlist : deletedPlaylists) { deletedListing.add(new ItemId(playlist.getItemId())); } databasePlaylists.add(deletedListing); } return databasePlaylists; } private DatabaseSongs getDatabaseSongs(DaapRequest request) { DatabaseSongs databaseSongs = new DatabaseSongs(); databaseSongs.add(new Status(200)); databaseSongs.add(new UpdateType(request.isUpdateType() ? 1 : 0)); databaseSongs.add(new SpecifiedTotalCount(totalSongCount)); Set<Song> songs = getSongs(); databaseSongs.add(new ReturnedCount(songs.size())); Listing listing = new Listing(); for (Song song : songs) { ListingItem listingItem = new ListingItem(); for (String key : request.getMeta()) { Chunk chunk = song.getChunk(key); if (chunk != null) { listingItem.add(chunk); } else if (LOG.isInfoEnabled()) { LOG.info("Unknown chunk type: " + key); } } listing.add(listingItem); } databaseSongs.add(listing); if (request.isUpdateType() && deletedSongs != null) { DeletedIdListing deletedListing = new DeletedIdListing(); for (Song song : deletedSongs) { deletedListing.add(song.getChunk("dmap.itemid")); } databaseSongs.add(deletedListing); } return databaseSongs; } }