Package com.github.hakko.musiccabinet.service.lastfm

Source Code of com.github.hakko.musiccabinet.service.lastfm.UpdateNowPlayingService

package com.github.hakko.musiccabinet.service.lastfm;

import static com.github.hakko.musiccabinet.service.library.LibraryUtil.FINISHED_MESSAGE;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static org.joda.time.Seconds.secondsBetween;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.joda.time.DateTime;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.Message;
import org.springframework.integration.core.PollableChannel;

import com.github.hakko.musiccabinet.domain.model.aggr.Scrobble;
import com.github.hakko.musiccabinet.domain.model.library.LastFmUser;
import com.github.hakko.musiccabinet.exception.ApplicationException;
import com.github.hakko.musiccabinet.log.Logger;
import com.github.hakko.musiccabinet.ws.lastfm.UpdateNowPlayingClient;
import com.github.hakko.musiccabinet.ws.lastfm.WSResponse;

public class UpdateNowPlayingService implements InitializingBean {
 
  protected Map<LastFmUser, ConcurrentLinkedDeque<Scrobble>> userScrobbles = new HashMap<>();

  protected TaskExecutor taskExecutor;
  protected PollableChannel scrobbleChannel;
  private UpdateNowPlayingClient client;

   // minimum time (in sec) an element must have been on queue, to be considered as played
  private final static int MIN_TIME = 60;

  // songs that are shorter than MIN_TIME are considered as played if their length is at
  // least this long (in sec)
  private final static int MIN_DURATION = 30;

  private static final Logger LOG = Logger.getLogger(UpdateNowPlayingService.class);

  @SuppressWarnings("unchecked")
  public void receive() {
    while (true) {
      Message<Scrobble> message = (Message<Scrobble>) scrobbleChannel.receive();
      if (message == null || message.equals(FINISHED_MESSAGE)) {
        break;
      } else {
        try {
          LOG.debug("Try updating now playing.");
          Scrobble scrobble = message.getPayload();
          Scrobble previous = getPrevious(scrobble);
          LOG.debug("previous: " + previous + ", scrobble = " + scrobble);
          if (previous != null && tooClose(scrobble, previous) &&
            scrobble.getTrack().getId() == previous.getTrack().getId()) {
            LOG.debug("Same track was scrobbled just recently, ignore.");
          } else {
            addScrobble(scrobble);
            WSResponse wsResponse = client.updateNowPlaying(message.getPayload());
            LOG.debug("Successful: " + wsResponse.wasCallSuccessful());
            LOG.debug("Response: " + wsResponse.getResponseBody());
          }
        } catch (ApplicationException e) {
          LOG.warn("Could not update now playing at last.fm.", e);
        }
      }
    }
  }

  private Scrobble getPrevious(Scrobble scrobble) {
    LastFmUser lastFmUser = scrobble.getLastFmUser();
    if (userScrobbles.containsKey(lastFmUser)) {
      return userScrobbles.get(lastFmUser).peekLast();
    } else {
      userScrobbles.put(lastFmUser, new ConcurrentLinkedDeque<Scrobble>());
      return null;
    }
  }
 
  private void addScrobble(Scrobble scrobble) {
    ConcurrentLinkedDeque<Scrobble> deque = userScrobbles.get(scrobble.getLastFmUser());
    Scrobble tail;
    while ((tail = deque.peekLast()) != null && tooClose(tail, scrobble)) {
      // indicates the occurrence of a previous track that was played for a few
      // seconds, and that should be removed
      deque.pollLast();
    }
    deque.add(scrobble);
  }

  private boolean tooClose(Scrobble prev, Scrobble next) {
    return tooClose(prev, next.getStartTime());
  }

  private boolean tooClose(Scrobble prev, DateTime next) {
    int allowedDiff = max(prev.getTrack().getMetaData().getDuration(), MIN_DURATION);
    allowedDiff = min(allowedDiff, MIN_TIME);
    return secondsBetween(prev.getStartTime(), next).getSeconds() < allowedDiff;
  }

  private void scrobbleTracks() {
    Scrobble head;
    for (LastFmUser lastFmUser : userScrobbles.keySet()) {
      ConcurrentLinkedDeque<Scrobble> deque = userScrobbles.get(lastFmUser);
      while ((head = deque.peekFirst()) != null) {
        if (!tooClose(head, new DateTime())) {
          // update play stats
          // scrobble
          deque.pollFirst();
        }
      }
    }
  }

  public void setTaskExecutor(TaskExecutor taskExecutor) {
    this.taskExecutor = taskExecutor;
  }
 
  public void setScrobbleChannel(PollableChannel scrobbleChannel) {
    this.scrobbleChannel = scrobbleChannel;
  }

  public void setUpdateNowPlayingClient(UpdateNowPlayingClient client) {
    this.client = client;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    taskExecutor.execute(new Runnable() {
      @Override
      public void run() {
        try {
          receive();
        } catch (Throwable t) {
          LOG.error("Unexpected error caught while receiving scrobbles!", t);
        }
      }
    });
   
    ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    scheduler.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        try {
          scrobbleTracks();
        } catch (Throwable t) {
          LOG.error("Unexpected error caught while scrobbling!", t);
        }
      }
    }, 1, 1, TimeUnit.MINUTES);

   
  }

}
TOP

Related Classes of com.github.hakko.musiccabinet.service.lastfm.UpdateNowPlayingService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.