/**
*
*/
package com.robonobo.gui.model;
import static com.robonobo.gui.GuiUtil.*;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.tree.TreePath;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.robonobo.common.concurrent.CatchingRunnable;
import com.robonobo.common.swing.SortableTreeNode;
import com.robonobo.common.swing.SortedTreeModel;
import com.robonobo.core.RobonoboController;
import com.robonobo.core.api.*;
import com.robonobo.core.api.model.*;
import com.robonobo.gui.components.FriendTree;
import com.robonobo.gui.frames.RobonoboFrame;
import com.robonobo.gui.panels.ContentPanel;
@SuppressWarnings("serial")
public class FriendTreeModel extends SortedTreeModel implements UserListener, PlaylistListener, LibraryListener, LoginListener {
static final int MAX_FRIEND_PLAYLIST_TITLE_WIDTH = 100;
RobonoboFrame frame;
RobonoboController control;
FriendTree tree;
Map<Long, FriendTreeNode> friendNodes = new HashMap<Long, FriendTreeNode>();
Map<Long, LibraryTreeNode> libNodes = new HashMap<Long, LibraryTreeNode>();
Map<Long, Map<Long, PlaylistTreeNode>> playlistNodes = new HashMap<Long, Map<Long, PlaylistTreeNode>>();
Set<Long> playlistIds = new HashSet<Long>();
Log log = LogFactory.getLog(getClass());
SelectableTreeNode myRoot;
public FriendTreeModel(RobonoboFrame rFrame) {
super(null);
myRoot = new SelectableTreeNode("Friends");
setRoot(myRoot);
frame = rFrame;
control = frame.ctrl;
control.addUserListener(this);
control.addPlaylistListener(this);
control.addLibraryListener(this);
control.addLoginListener(this);
}
@Override
public void loginSucceeded(User me) {
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
synchronized (FriendTreeModel.this) {
getRoot().removeAllChildren();
friendNodes.clear();
playlistNodes.clear();
playlistIds.clear();
insertNodeSorted(getRoot(), new AddFriendsTreeNode(frame));
}
nodeStructureChanged(getRoot());
}
});
}
@Override
public void loginFailed(String reason) {
// Do nothing
}
public void userChanged(final User u) {
// If it's me, check to see if any of my friends are no longer friends
if (control.getMyUser().equals(u)) {
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
synchronized (FriendTreeModel.this) {
for (Long friendId : friendNodes.keySet()) {
if (!u.getFriendIds().contains(friendId)) {
removeNodeFromParent(friendNodes.get(friendId));
friendNodes.remove(friendId);
Map<Long, PlaylistTreeNode> toRm = playlistNodes.remove(friendId);
playlistIds.removeAll(toRm.keySet());
}
}
}
}
});
} else if (control.getMyUser().getFriendIds().contains(u.getUserId())) {
// It's a friend
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
synchronized (FriendTreeModel.this) {
if (friendNodes.containsKey(u.getUserId())) {
FriendTreeNode ftn = friendNodes.get(u.getUserId());
// Already have this friend - check to see if any playlists have been deleted
Iterator<Entry<Long, PlaylistTreeNode>> iter = playlistNodes.get(u.getUserId()).entrySet().iterator();
while (iter.hasNext()) {
Entry<Long, PlaylistTreeNode> entry = iter.next();
if (!u.getPlaylistIds().contains(entry.getKey())) {
PlaylistTreeNode ptn = entry.getValue();
removeNodeFromParent(ptn);
iter.remove();
playlistIds.remove(ptn.getPlaylist().getPlaylistId());
}
}
// Friend might have changed friendly names, re-order if necessary
ftn.setFriend(u);
replaceNodeSorted(getRoot(), ftn);
} else {
// New friend node
FriendTreeNode ftn = new FriendTreeNode(u);
friendNodes.put(u.getUserId(), ftn);
playlistNodes.put(u.getUserId(), new HashMap<Long, PlaylistTreeNode>());
insertNodeSorted(getRoot(), ftn);
}
}
}
});
}
}
public void playlistChanged(final Playlist p) {
// Only show playlists with at least one track
if(p.getStreamIds().size() == 0)
return;
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
synchronized (FriendTreeModel.this) {
for (FriendTreeNode ftn : friendNodes.values()) {
if (ftn.getFriend().getPlaylistIds().contains(p.getPlaylistId())) {
// This user has this playlist - do we have a node for this yet?
long friendId = ftn.getFriend().getUserId();
PlaylistTreeNode ptn = playlistNodes.get(friendId).get(p.getPlaylistId());
if (ptn == null) {
if(p.getTitle().equalsIgnoreCase("loves"))
ptn = new LovesTreeNode(p, frame);
else if(p.getTitle().equalsIgnoreCase("radio"))
ptn = new RadioTreeNode(p, frame);
else
ptn = new PlaylistTreeNode(p, frame);
playlistNodes.get(friendId).put(p.getPlaylistId(), ptn);
insertNodeSorted(ftn, ptn);
playlistIds.add(p.getPlaylistId());
firePathToRootChanged(ptn);
// There might be some comments already for this playlist, check
frame.ctrl.getExistingCommentsForPlaylist(p.getPlaylistId(), FriendTreeModel.this);
} else {
ptn.setPlaylist(p, tree.isSelectedNode(ptn));
replaceNodeSorted(ftn, ptn);
firePathToRootChanged(ptn);
}
}
}
}
}
});
}
@Override
public void gotLibraryComments(final long userId, boolean anyUnread, Map<Comment, Boolean> comments) {
if (!anyUnread)
return;
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
LibraryTreeNode ltn;
boolean had;
synchronized (FriendTreeModel.this) {
ltn = libNodes.get(userId);
if (ltn == null)
return;
// If this is the selected node, and the comments tab is showing, don't update us as having comments
if (tree.isSelectedNode(ltn)) {
ContentPanel cp = frame.mainPanel.getContentPanel("library/" + userId);
if (cp.tabPane.getSelectedIndex() == 1)
return;
}
had = ltn.hasComments;
ltn.hasComments = true;
}
if (!had)
firePathToRootChanged(ltn);
}
});
}
public void markLibraryCommentsAsRead(final long userId) {
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
LibraryTreeNode ltn;
boolean had;
synchronized (FriendTreeModel.this) {
ltn = libNodes.get(userId);
had = ltn.hasComments;
ltn.hasComments = false;
}
if (had)
firePathToRootChanged(ltn);
}
});
}
@Override
public void gotPlaylistComments(final long plId, boolean anyUnread, Map<Comment, Boolean> comments) {
if (!anyUnread)
return;
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
synchronized (FriendTreeModel.this) {
for (FriendTreeNode ftn : friendNodes.values()) {
if (ftn.getFriend().getPlaylistIds().contains(plId)) {
long friendId = ftn.getFriend().getUserId();
PlaylistTreeNode ptn = playlistNodes.get(friendId).get(plId);
if (ptn == null) {
log.warn("FTM NOT updating PTN for plid " + plId + " with friend " + friendId);
return;
}
// If this is the selected node, and the comments tab is showing, don't update us as having
// comments
if (tree.isSelectedNode(ptn)) {
ContentPanel cp = frame.mainPanel.getContentPanel("playlist/" + plId);
if (cp.tabPane.getSelectedIndex() == 1)
return;
}
ptn.hasComments = true;
firePathToRootChanged(ptn);
}
}
}
}
});
}
public void markPlaylistCommentsAsRead(final long plId) {
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
synchronized (FriendTreeModel.this) {
for (FriendTreeNode ftn : friendNodes.values()) {
if (ftn.getFriend().getPlaylistIds().contains(plId)) {
// This user has this playlist - do we have a node for this yet?
long friendId = ftn.getFriend().getUserId();
PlaylistTreeNode ptn = playlistNodes.get(friendId).get(plId);
if (ptn == null)
return;
ptn.hasComments = false;
firePathToRootChanged(ptn);
}
}
}
}
});
}
public void markPlaylistTracksAsSeen(final long plId) {
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
synchronized (FriendTreeModel.this) {
for (FriendTreeNode ftn : friendNodes.values()) {
if (ftn.getFriend().getPlaylistIds().contains(plId)) {
// This user has this playlist - do we have a node for this yet?
long friendId = ftn.getFriend().getUserId();
PlaylistTreeNode ptn = playlistNodes.get(friendId).get(plId);
if (ptn == null)
return;
ptn.numUnseenTracks = 0;
firePathToRootChanged(ptn);
}
}
}
}
});
}
@Override
public void friendLibraryReady(final long uid, final int numUnseen) {
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
LibraryTreeNode ltn;
FriendTreeNode ftn;
synchronized (FriendTreeModel.this) {
ftn = friendNodes.get(uid);
if (ftn == null) {
log.error("ERROR: library updated for userId " + uid + ", but there is no friend tree node");
return;
}
ltn = new LibraryTreeNode(frame, uid, numUnseen);
insertNodeSorted(ftn, ltn);
libNodes.put(uid, ltn);
}
firePathToRootChanged(ltn);
// There is a stupid bug in some java impls that doesn't repaint the tree nodes unless you call each one
// individually
firePathToRootChanged(ftn);
firePathToRootChanged(getRoot());
}
});
}
@Override
public void friendLibraryUpdated(final long uid, final int numUnseen, Map<String, Date> newTracks) {
runOnUiThread(new CatchingRunnable() {
public void doRun() throws Exception {
LibraryTreeNode ltn;
FriendTreeNode ftn;
synchronized (FriendTreeModel.this) {
ltn = libNodes.get(uid);
ftn = friendNodes.get(uid);
if (ltn == null) {
log.error("ERROR: library updated for userId " + uid + ", but there is no library tree node");
return;
} else {
// If they are selected, keep everything as unseen
if (tree.isSelectedNode(ltn)) {
control.getExecutor().execute(new CatchingRunnable() {
public void doRun() throws Exception {
control.markAllLibraryTracksAsSeen(uid);
}
});
} else
ltn.numUnseenTracks = numUnseen;
}
}
firePathToRootChanged(ltn);
// There is a stupid bug in some java impls that doesn't repaint the tree nodes unless you call each one
// individually
firePathToRootChanged(ftn);
firePathToRootChanged(getRoot());
}
});
}
@Override
public void myLibraryUpdated() {
// Do nothing
}
@Override
public void userConfigChanged(UserConfig cfg) {
// Do nothing
}
public TreePath getPlaylistTreePath(Long playlistId) {
// NB If the playlist is in the tree more than once (eg shared
// playlist), this will select the first instance only...
synchronized (this) {
for (Map<Long, PlaylistTreeNode> ptns : playlistNodes.values()) {
if (ptns.containsKey(playlistId))
return new TreePath(getPathToRoot(ptns.get(playlistId)));
}
}
return null;
}
public synchronized TreePath getLibraryTreePath(long uid) {
if (libNodes.containsKey(uid))
return new TreePath(getPathToRoot(libNodes.get(uid)));
return null;
}
@Override
public SortableTreeNode getRoot() {
return (SortableTreeNode) super.getRoot();
}
public synchronized boolean hasPlaylist(long playlistId) {
return playlistIds.contains(playlistId);
}
public void setTree(FriendTree tree) {
this.tree = tree;
}
@Override
public void allUsersAndPlaylistsLoaded() {
}
}