Android Open Source - shareplay Client






From Project

Back to project page shareplay.

License

The source code is released under:

Copyright (c) 2014, Benjamin Damer All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: ...

If you think the Android project shareplay listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.afqa123.shareplay.impl;
/*w  w  w .  jav a  2s  . c o m*/
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.http.HttpException;

import android.os.Environment;
import android.os.Handler;
import android.os.Message;

import com.afqa123.log.Logger;
import com.afqa123.log.LoggerFactory;
import com.afqa123.shareplay.common.AuthorizationException;
import com.afqa123.shareplay.common.DAAPException;
import com.afqa123.shareplay.common.DBHelper;
import com.afqa123.shareplay.common.Filename;
import com.afqa123.shareplay.common.StoppableThread;
import com.afqa123.shareplay.data.ContentCode;
import com.afqa123.shareplay.data.Item;
import com.afqa123.shareplay.data.Playlist;
import com.afqa123.shareplay.interfaces.Catalog;
import com.afqa123.shareplay.interfaces.IClient;

public class Client implements IClient {

  private static final Logger logger = LoggerFactory.getLogger(Client.class);
  public static final int DEFAULT_PORT = 3689;
  private static final int CLIENT_AUTHORIZATION = 1;
  private static final int CLIENT_ERROR = 2;
  private static final int CLIENT_CONNECTED = 3;
  private static final int CLIENT_CATALOG_EMPTY = 4;
  private static final int CLIENT_CATALOG_UPDATED = 5;
  private static final int CLIENT_PLAYLISTS_UPDATED = 6;
  private static final int CLIENT_CATALOG_COMPLETE = 7;
  private static final int CLIENT_CATALOG_ERROR = 8;
  private static final int CLIENT_DOWNLOAD_START = 9;
  private static final int CLIENT_DOWNLOAD_COMPLETE = 10;
  private static final int CLIENT_DOWNLOAD_ERROR = 11;
  private static final int DATABASE_ID = 1;
  private static final String DEFAULT_ALBUM = "Unknown album";
  private static final String DEFAULT_ARTIST = "Unknown artist";
  private static final boolean LOAD_SONGS = true;
  private static final boolean LOAD_PLAYLISTS = true;
  private static final int INITIAL_REVISION = 1;
  private static final Set<String> SUPPORTED_FORMATS = 
    new HashSet<String>(Arrays.asList(new String[] { "mp3", "m4a", "wav", "ogg" }));    
  private static final String SONG_PATH = "http://%s:%d/databases/%d/items/%%d.mp3?session-id=%d";
  private static final String PROTOCOL = "http";
  private static final String PARAM_AUTHORIZATION = "Authorization";
  private static final String VALUE_AUTHORIZATION = "Basic %s";
    
  /*  
   * Available meta fields:
   * daap.songbitrate,daap.songbeatsperminute,daap.songcomment,daap.songcompilation, 
   * dmap.persistentid,daap.songcomposer,daap.songdateadded,daap.songdatemodified,
   * daap.songdisccount,daap.songdiscnumber,daap.songdisabled, daap.songeqpreset,
   * daap.songformat,daap.songgenre,daap.songdescription,daap.songrelativevolume, 
   * daap.songsamplerate,daap.songsize,daap.songstarttime,daap.songstoptime,
   * daap.songtime,daap.songtrackcount,daap.songuserrating,daap.songyear,daap.songdatakind,
   * daap.songdataurl,com.apple.itunes.norm-volume
   **/
  private static final String SONG_META_FIELDS = "dmap.itemkind,dmap.itemid,dmap.itemname,daap.songalbum,daap.songartist,daap.songtracknumber,daap.songformat";
  private static final String PLAYLIST_LIST_META_FIELDS = "dmap.itemid,dmap.itemname,dmap.itemcount,daap.baseplaylist";
  private static final String PLAYLIST_META_FELDS = "dmap.itemid";
  private static final int HTTP_OK = 200;
  private static final int BUFFER_SIZE = 1024;
  
  private interface IRequest {
    final static String SERVER_INFO = "server-info";
    final static String CONTENT_CODES = "content-codes";
    final static String LOGIN = "login";
    final static String LOGOUT = "logout?session-id=%d";
    final static String UPDATE = "update?session-id=%d&revision-number=%d";
    final static String DATABASE_LIST = "databases?session-id=%d&revision-id=%d";
    final static String SONG_LIST = "databases/%d/items?type=music&meta=%s&session-id=%d&revision-id=%d";  
    final static String PLAYLIST_LIST = "databases/%d/containers?meta=%s&session-id=%d&revision-id=%d";
    final static String PLAYLIST = "/databases/%d/containers/%d/items?type=music&meta=%s&session-id=%d&revision-id=%d";
  }
  
  @SuppressWarnings("unused")
  private interface IResponse {
    
    /*
     * Unused: 
     * 
    asbt  short  daap.songsbeatsperminute
    asbr  short  daap.songbitrate
    ascm  string  daap.songcomment
    asco  byte  daap.songcompilation
    asda  date  daap.songdateadded
    asdm  date  daap.songdatemodified
    asdc  short  daap.songdisccount
    asdn  short  daap.songdiscnumber
    asdb  byte  daap.songdisabled
    aseq  string  daap.songeqpreset
    asgn  string  daap.songgenre
    asdt  string  daap.songdescription
    asrv  byte  daap.songrelativevolume
    assr  int    daap.songsamplerate
    assz  int    daap.songsize
    asst  int    daap.songstarttime   (in milliseconds)  
    assp  int    daap.songstoptime   (in milliseconds)
    astm  int    daap.songtime    (in milliseconds)
    astc  short  daap.songtrackcount
    asur  byte  daap.songuserrating
    asyr  short  daap.songyear
    asdk  byte  daap.songdatakind
    asul  string  daap.songdataurl

    prsv  list  daap.resolve
    arif  list  daap.resolveinfo
    
    mcon  list  dmap.container    an arbitrary container
    mcti  int    dmap.containeritemid  the id of an item in its container
    
    msts  string  dmap.statusstring
    
    mudl  list  dmap.deletedidlisting  used in updates?  (document soon)
    
    abro  list  daap.databasebrowse  
    
    abal  list  daap.browsealbumlistung    
    abar  list  daap.browseartistlisting   
    abcp  list  daap.browsecomposerlisting
    abgn  list  daap.browsegenrelisting
    
    aeNV  int    com.apple.itunes.norm-volume
    */
    
    final static int ABPL = 0x6162706c;  // daap.baseplaylist
    final static int ADBS = 0x61646273;  // daap.playlistsongs
    final static int AESP = 0x61655350;  // com.apple.itunes.smart-playlist
    final static int APLY = 0x61706c79;  // daap.databaseplaylists response to /databases/id/containers
    final static int APRO = 0x6170726f;  // daap.protocolversion
    final static int APSO = 0x6170736f;  // daap.databaseplaylistssongs response to /databases/id/containers/pid/
    final static int ASAL = 0x6173616c;  // daap.songalbum    the song ones should be self exp.
    final static int ASAR = 0x61736172;  // daap.songartist
    final static int ASDK = 0x6173646b; // daap.songdatakind
    final static int ASFM = 0x6173666d;  // daap.songformat
    final static int ASSA = 0x61737361; // daap.sortartist
    final static int ASSC = 0x61737363; // daap.sortcomposer
    final static int ASSL = 0x6173736c; // daap.sortalbumartist
    final static int ASSN = 0x6173736e;  // daap.sortname
    final static int ASSU = 0x61737375; // daap.???
    final static int ASTN = 0x6173746e;  // daap.songtracknumber
    final static int AVDB = 0x61766462;  // daap.serverdatabases  response to a /databases
    final static int MBCL = 0;  // dmap.bag
    final static int MCCR = 1835230066;  // dmap.contentcodesresponse  the response to the content-codes request
    final static int MCNA = 1835232865;  // dmap.contentcodesname the full name of the code
    final static int MCNM = 1835232877;  // dmap.contentcodesnumber the four letter code
    final static int MCTC = 1835234403;  // dmap.???    number of containers
    final static int MCTY = 1835234425;  // dmap.contentcodestype the type of the code (see appendix b for type values)
    final static int MDCL = 1835295596;  // dmap.dictionary - a dictionary entry
    final static int MIID = 1835624804;  // dmap.itemid - an item's id
    final static int MIKD = 0x6d696b64; // dmap.itemkind - the kind of item.  So far, only '2' has been seen, an audio file?
    final static int MIMC = 1835625827; // dmap.itemcount - number of items in a container
    final static int MINM = 1835626093;  // dmap.itemname - an items name
    final static int MLCL = 0x6d6c636c;  // dmap.listing    a list
    final static int MLID = 1835821412;  // dmap.sessionid    the session id for the login session
    final static int MLIT = 1835821428;  // dmap.listingitem  a single item in said list
    final static int MLOG = 0x6d6c6f67;  // dmap.loginresponse  response to a /login    
    final static int MPCO = 0x6d70636f;  // dmap.parentcontainerid
    final static int MPER = 1836082546;  // dmap.persistentid - a persistend id
    final static int MPRO = 1836085871;  // dmap.protocolversion
    final static int MRCO = 0x6d72636f;  // dmap.returnedcount  number of items returned in a request
    final static int MSAL = 1836278124;  // dmap.supportsuatologout
    final static int MSAU = 1836278133;  // dmap.authenticationmethod (should be self explanitory)
    final static int MSBR = 1836278386;  // dmap.supportsbrowse
    final static int MSDC = 1836278883;  // dmap.databasescount
    final static int MSEX = 1836279160;  // dmap.supportsextensions
    final static int MSIX = 1836280184;  // dmap.supportsindex
    final static int MSLR = 1836280946;  // dmap.loginrequired    
    final static int MSPI = 1836281961;  // dmap.supportspersistentids
    final static int MSRS = 1836282483;  // dmap.supportsresolve
    final static int MSRV = 0x6d737276;  // dmap.serverinforesponse  response to a /server-info
    final static int MSTM = 1836282989;  // dmap.timeoutinterval
    final static int MSTT = 1836282996;  // dmap.status - response status code
    final static int MSUP = 1836283248;  // dmap.supportsupdate
    final static int MSQY = 1836282233;  // dmap.supportsquery    
    final static int MTCO = 0x6d74636f;  // dmap.specifiedtotalcount number of items in response to a request
    final static int MUPD = 1836413028;  // dmap.updateresponse  response to a /update
    final static int MUSR = 1836413810;  // dmap.serverrevision  revision to use for requests
    final static int MUTY = 1836414073;  // dmap.updatetype
  };

  public interface OnConnectedListener {
    void onConnected();
  };
  
  public interface OnErrorListener {
    void onError(Exception ex);
  };
  
  public interface OnAuthorizationListener {
    void onAuthorizationRequired();
  };

  public interface OnCatalogUpdateListener {
    
    void onCatalogEmpty();
    
    void onCatalogUpdated(int count, int total);
    
    void onPlaylistUpdated(int count, int total, String name);
    
    void onCatalogComplete();
    
    void onCatalogError(Exception ex);
  }
  
  public interface OnDownloadListener {
    
    void onDownloadStart();
    
    void onDownloadComplete();
    
    void onDownloadError(Exception ex);
  }
  
  private class EventHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
      case CLIENT_AUTHORIZATION:
        if (_onAuthorizationListener != null) {
          _onAuthorizationListener.onAuthorizationRequired();
        }
        break;
        
      case CLIENT_ERROR:
        if (_onErrorListener != null) {
          _onErrorListener.onError((Exception)msg.obj);
        }
        break;
        
      case CLIENT_CONNECTED:
        if (_onConnectedListener != null) {
          _onConnectedListener.onConnected();          
        }
        break;
        
      case CLIENT_CATALOG_EMPTY:
        if (_onCatalogUpdateListener != null) {
          _onCatalogUpdateListener.onCatalogEmpty();
        }
        break;        

      case CLIENT_CATALOG_UPDATED:
        if (_onCatalogUpdateListener != null) {
          _onCatalogUpdateListener.onCatalogUpdated(msg.arg1, msg.arg2);
        }
        break;        

      case CLIENT_PLAYLISTS_UPDATED:
        if (_onCatalogUpdateListener != null) {
          _onCatalogUpdateListener.onPlaylistUpdated(msg.arg1, msg.arg2, (String)msg.obj);
        }
        break;
        
      case CLIENT_CATALOG_COMPLETE:
        if (_onCatalogUpdateListener != null) {
          _onCatalogUpdateListener.onCatalogComplete();
        }
        break;        
        
      case CLIENT_CATALOG_ERROR:
        if (_onCatalogUpdateListener != null) {
          _onCatalogUpdateListener.onCatalogError((Exception)msg.obj);
        }
        break;
        
      case CLIENT_DOWNLOAD_START:
        if (_onDownloadListener != null) {
          _onDownloadListener.onDownloadStart();
        }
        break;

      case CLIENT_DOWNLOAD_COMPLETE:
        if (_onDownloadListener != null) {
          _onDownloadListener.onDownloadComplete();
        }
        break;

      case CLIENT_DOWNLOAD_ERROR:
        if (_onDownloadListener != null) {
          _onDownloadListener.onDownloadError((Exception)msg.obj);
        }
        break;

      default:
        super.handleMessage(msg);
        break;
      }
    }
  };
    
  private class UpdateThread extends StoppableThread {
        
    @Override
    public void run() {
      Message m;
      long then = System.currentTimeMillis();
      int count;

      try {
        _handler.obtainMessage(CLIENT_CATALOG_EMPTY).sendToTarget();
        _catalog.clear();
        _catalog.prepare();

        if (!isStopped() && LOAD_SONGS) {
          count = beginSongList(DATABASE_ID);
          logger.info("Download time: " + (System.currentTimeMillis() - then));
  
          for (int i = 0; i < count; i++) {
            if (isStopped()) {
              break;
            }
            
            if (i % 25 == 0) {
              m = _handler.obtainMessage(CLIENT_CATALOG_UPDATED);
              m.arg1 = i;
              m.arg2 = count;
              m.sendToTarget();
            }
    
            getSong(_catalog);
          }
                  
          endSongList();
        }
        
        if (!isStopped() && LOAD_PLAYLISTS) {
          List<Playlist> playlists = getPlaylists(DATABASE_ID, _catalog);

          count = playlists.size();
          for (int i = 0; i < count; i++){
            Playlist pl = playlists.get(i);
            // notify UI that we are updating playlists
            m = _handler.obtainMessage(CLIENT_PLAYLISTS_UPDATED);
            m.arg1 = i;
            m.arg2 = count;
            m.obj = pl.getName();
            m.sendToTarget();
            loadPlaylist(DATABASE_ID, pl.getId(), pl.getServerId(), _catalog);
          }
        }

        // Finalize catalog
        _catalog.commit(true);
        _server.setStale(false);
          
        logger.info("Finished catalog update: " + _catalog.getArtistCount() + " artists, " + 
            _catalog.getAlbumCount() + " albums, " + _catalog.getSongCount() + " songs, " + 
            _catalog.getPlaylistCount() + " playlists.");
        
        // need to send this as the last action, otherwise we will lock up the db
        _handler.obtainMessage(CLIENT_CATALOG_COMPLETE).sendToTarget();
      } catch (Exception ex) {
        logger.error("Error in update thread.", ex);
        if (_catalog != null) {
          _catalog.commit(false);
        }
        _handler.obtainMessage(CLIENT_CATALOG_ERROR, ex).sendToTarget();
      } finally {
        logger.info("Catalog update time: " + (System.currentTimeMillis() - then));      
      }
    }  
  };  
  
  private class DownloadThread extends StoppableThread {
    
    private Long _id;
    
    public DownloadThread(final Long id) {
      _id = id;
    }
    
    @Override
    public void run() {
      if (isStopped())
        return;
      
      try {
        _handler.obtainMessage(CLIENT_DOWNLOAD_START).sendToTarget();

        Item song = _catalog.getSongItem(_id);
        if (song == null)
          return;
                
        final String address = String.format(getPlaybackURL(), song.getId());
        final URL url = new URL(address);
    
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.connect();

            final String filename = String.format("%02d - %s - %s - %s.mp3", song.getTrack(), song.getArtist(), song.getAlbum(), song.getName());
            File SDCardRoot = Environment.getExternalStorageDirectory();
            File file = new File(SDCardRoot, Filename.clean(filename));    
            FileOutputStream fileOutput = new FileOutputStream(file);
            InputStream inputStream = urlConnection.getInputStream();
    
            byte[] buffer = new byte[1024];
            int bufferLength = 0;
            while ((bufferLength = inputStream.read(buffer)) > 0) {
              if (isStopped())
                break;
              
              fileOutput.write(buffer, 0, bufferLength);
            }
            fileOutput.close();
            
        _handler.obtainMessage(CLIENT_DOWNLOAD_COMPLETE).sendToTarget();

      } catch (Exception ex) {
        logger.error("Error downloading file.", ex);
        _handler.obtainMessage(CLIENT_DOWNLOAD_ERROR, ex).sendToTarget();
      }
    }
  }

  private static byte[] _buffer = new byte[BUFFER_SIZE];
  private static int _offset = 0;
  private static int _read = 0;                               

  private long _sessionId;
  private DataInputStream _in;
  private List<ContentCode> _contentCodes;
  private Catalog _catalog;
  private Server _server;
  private StoppableThread _worker;
  private StoppableThread _downloader;
  private boolean _connected;
  private Handler _handler;
  private OnConnectedListener _onConnectedListener;
  private OnErrorListener _onErrorListener;
  private OnAuthorizationListener _onAuthorizationListener;
  private OnCatalogUpdateListener _onCatalogUpdateListener;
  private OnDownloadListener _onDownloadListener;
  private URLConnection _connection;
  
  public void setOnConnectedListener(final OnConnectedListener listener) {
    _onConnectedListener = listener;
  }
  
  public void setOnAuthorizationListener(final OnAuthorizationListener listener) {
    _onAuthorizationListener = listener;
  }
  
  public void setOnErrorListener(final OnErrorListener listener) {
    _onErrorListener = listener;
  }
  
  public void setOnCatalogUpdateListener(final OnCatalogUpdateListener listener) {
    _onCatalogUpdateListener = listener;
  }
  
  public void setOnDowloadListener(final OnDownloadListener listener) {
    _onDownloadListener = listener;
  }
  
  public Client() {
    _handler = new EventHandler();
    _connected = false;
    _contentCodes = new ArrayList<ContentCode>();
  }
  
  @Override 
  public void connect(final DBHelper db, final Server server) {
    _server = server;

    Thread t = new Thread() {
      
      public void run() {
        
        try {
          logger.info("Connecting to: " + _server.getName());
          getServerInfo();
          
          if (_server.requiresAuthorization() && _server.getPasswordHash() == null) {
            throw new AuthorizationException();
          }
          
          login();          
          update();
          _catalog = new DatabaseCatalog(db, _server);

          _connected = true;
          _handler.obtainMessage(CLIENT_CONNECTED).sendToTarget();
          
        } catch (AuthorizationException ex) {
          _catalog = null;
          _connected = false;
          _handler.obtainMessage(CLIENT_AUTHORIZATION).sendToTarget();
        } catch (Exception ex) {
          logger.error("Error connecting client.", ex);
          _catalog = null;
          _connected = false;
          _handler.obtainMessage(CLIENT_ERROR, ex).sendToTarget();
        }
      }
    };  
    
    t.start();
  }
    
  @Override
  public boolean isConnected() {
    return _connected;
  }
  
  @Override
  public Catalog getCatalog() {
    return _catalog;
  }
  
  @Override
  public String getPlaybackURL() {
    return String.format(SONG_PATH, 
        _server.getAddress(), 
        _server.getPort(), 
        DATABASE_ID, 
        _sessionId);
  }  
  
  @Override
  public Server getServer() {
    return _server;
  }
  
  @Override
  public void updateCatalog(final boolean force) {

    if (!_server.isStale() && !force) {
      logger.debug("Catalog is fresh.");
      return;
    }
    
    if (_worker != null && _worker.isAlive()) {
      logger.debug("Worker is still running, not starting another.");
    }
    
    logger.debug("Catalog is stale.");
        
    _worker = new UpdateThread();
    _worker.start();
  }
  
  @Override
  public void cancelUpdate() {
    if (_worker != null && _worker.isAlive()) {
      logger.debug("Interrupting worker thread.");
      _worker.requestStop();
    }
  }
  
  private DataInputStream beginRequest(final String request, final int response) throws HttpException, IOException, DAAPException {
    final URL url = new URL(PROTOCOL, _server.getAddress(), _server.getPort(), request);
    _connection = url.openConnection();

    if (_server.requiresAuthorization()) {
      final String hash = _server.getPasswordHash();
      _connection.setRequestProperty(PARAM_AUTHORIZATION, String.format(VALUE_AUTHORIZATION, hash));
    }
    
    _connection.connect();
    
    InputStream is = _connection.getInputStream();
    DataInputStream in = new DataInputStream(is);
    if (response > 0) {
      matchCode(in, response);
      matchCode(in, IResponse.MSTT);
      int code = in.readInt();
      if (code != HTTP_OK) {
        throw new HttpException("Invalid response code: " + code);
      }
    }
          
    return in;
  }
  
  private void endRequest(final DataInputStream in) throws IOException {
    if (in != null) {
      in.close();
    }
  }
  
  private int matchCode(final DataInputStream in, final int code) throws DAAPException {

    try {
      final int read = in.readInt();
      if (code != read) {
        throw new DAAPException("Error matching code '" + Integer.toHexString(code) + "' - found: ", read);
      }
      return in.readInt();

    } catch (IOException ex) {      
      try {
        // try to read the next four bytes, since they are what we were looking for
        byte[] buffer = new byte[4];
        in.read(buffer);
        String dword = String.format("%02x%02x%02x%02x", buffer[0], buffer[1], buffer[2], buffer[3]);
        throw new DAAPException("Error matching code '" + Integer.toHexString(code) + "' - next DWORD '" + dword + "'. " + ex.getMessage(), ex);
      } catch (IOException ex2) {
        throw new DAAPException("Error matching code '" + Integer.toHexString(code) + "'", ex);
      }
    }      
  }

  private String readString(int size) throws IOException {
    _offset = 0;
    while ( (_read = _in.read(_buffer, _offset, size)) != size ) {
      if (_read == -1) {
        break;
      }
      _offset = _read;
      size -= _read;
    }
    return new String(_buffer, 0, size);
  }
  
  private void getServerInfo() throws Exception {
    Map<String,Object> flags = new HashMap<String,Object>();
    _in = beginRequest(IRequest.SERVER_INFO, IResponse.MSRV);
    
    while (_in.available() > 0) {
      int code = _in.readInt();
      int size = _in.readInt();
      
      switch (code) {
        case IResponse.MPRO:
          flags.put(Server.FLAG_DMAP_VERSION, _in.readInt());
          break;        
        case IResponse.APRO:
          flags.put(Server.FLAG_DAAP_VERSION, _in.readInt());
          break;
        case IResponse.MINM:
          _server.setName(readString(size));
          break;
        case IResponse.MSAU:
          flags.put(Server.FLAG_AUTHENTICATION, _in.readBoolean());
          break;
        case IResponse.MSLR:
          flags.put(Server.FLAG_LOGIN_REQUIRED, _in.readBoolean());
          break;
        case IResponse.MSTM:
          flags.put(Server.FLAG_TIMEOUT_INTERVAL, _in.readInt());
          break;
        case IResponse.MSAL:
          flags.put(Server.FLAG_AUTO_LOGOUT, _in.readBoolean());
          break;
        case IResponse.MSUP:
          flags.put(Server.FLAG_UPDATE, _in.readBoolean());
          break;
        case IResponse.MSPI:
          flags.put(Server.FLAG_PERSISTENT_IDS, _in.readBoolean());
          break;
        case IResponse.MSEX:
          flags.put(Server.FLAG_EXTENSIONS, _in.readBoolean());
          break;
        case IResponse.MSBR:
          flags.put(Server.FLAG_BROWSING, _in.readBoolean());
          break;
        case IResponse.MSQY:
          flags.put(Server.FLAG_QUERIES, _in.readBoolean());
          break;
        case IResponse.MSIX:
          flags.put(Server.FLAG_INDEXING, _in.readBoolean());
          break;
        case IResponse.MSRS:
          flags.put(Server.FLAG_RESOLVE, _in.readBoolean());
          break;
        case IResponse.MSDC:
          _server.setDatabaseCount(_in.readInt());
          break;
        default:
          //Log.d(Constants.LOG_SOURCE, "Unexpected code: " + Integer.toHexString(code));
          _in.skipBytes(size);
          break;
      }      
    }

    endRequest(_in);
    
    _server.setFlags(flags);
  }

  @SuppressWarnings("unused")
  private void getContentCodes() throws Exception {
    ContentCode cc = null;
    
    _in = beginRequest(IRequest.CONTENT_CODES, IResponse.MCCR);
    
    while (_in.available() > 0) {
      int code = _in.readInt();
      int size = _in.readInt();
      
      switch (code) {
        case IResponse.MDCL:
          if (cc != null) {
            _contentCodes.add(cc);
          }
          
          cc = new ContentCode();
          break;
        case IResponse.MCNM:
          cc.setNumber(_in.readInt());
          break;
        case IResponse.MCNA:
          byte[] buf = new byte[size];
          _in.read(buf);      
          cc.setName(new String(buf));
          break;
        case IResponse.MCTY:
          cc.setCode(_in.readShort());
          break;
        default:
          //Log.d(Constants.LOG_SOURCE, "Unexpected code: " + Integer.toHexString(code));
          _in.skipBytes(size);
          break;
      }
    }
      
    if (cc != null) {
      _contentCodes.add(cc);
    }
    
    StringBuilder sb = new StringBuilder();
    for (ContentCode x : _contentCodes) {
      sb.append(x);
      sb.append("\n");
    }
    
    logger.debug(sb.toString());
    
    endRequest(_in);
  }

  private void login() throws Exception {
    _in = beginRequest(IRequest.LOGIN, IResponse.MLOG);
    matchCode(_in, IResponse.MLID);
    
    // session id is an unsigend int
    int id = _in.readInt();
    _sessionId = id & 0xffffffffL;
    
    endRequest(_in);
  }
  
  @SuppressWarnings("unused")
  private void logout() throws Exception {
    _in = beginRequest(String.format(IRequest.LOGOUT, _sessionId), 0);
    endRequest(_in);
  }
  
  private void update() throws Exception {    
    _in = beginRequest(String.format(IRequest.UPDATE, _sessionId, INITIAL_REVISION), IResponse.MUPD);
    matchCode(_in, IResponse.MUSR);
    _server.setRevision(_in.readInt());
    endRequest(_in);
  }

  @SuppressWarnings("unused")
  private void getDatabaseList() throws Exception {

    _in = beginRequest(String.format(IRequest.DATABASE_LIST, _sessionId, _server.getRevision()),
          IResponse.AVDB);
    
    //  update type - always 0 for now
    matchCode(_in, IResponse.MUTY);
    _in.readByte();
    
    //  total number of matching records
    matchCode(_in, IResponse.MTCO);
    int totalCount = _in.readInt();
    
    //  total number of records returned
    matchCode(_in, IResponse.MRCO);
    int returnedCount = _in.readInt();
    
    //  listing of records
    matchCode(_in, IResponse.MLCL);
    
    for (int i = 0; i < returnedCount; i++) {

      // single record
      matchCode(_in, IResponse.MLIT);
      
      // database id (<dbid> in subsequent requests)
      matchCode(_in, IResponse.MIID);
      int dbid = _in.readInt();
      
      // database persistent id
      matchCode(_in, IResponse.MPER);
      long dperid = _in.readLong();

      // database name
      int size = matchCode(_in, IResponse.MINM);
      String dbname = readString(size);
      
      // number of items (songs) in the database
      matchCode(_in, IResponse.MIMC);
      int countSongs = _in.readInt();
      
      // number of containers (playlists) in the database
      matchCode(_in, IResponse.MCTC);
      int countContainers = _in.readInt();
    }
    
    endRequest(_in);
  }
    
  private int beginSongList(final int database) throws Exception {    
    logger.debug("Getting song list from server.");

    int returnedCount = 0;
    
    _in = beginRequest(String.format(IRequest.SONG_LIST, database, SONG_META_FIELDS, _sessionId, _server.getRevision()),
        IResponse.ADBS);

    matchCode(_in, IResponse.MUTY);
    @SuppressWarnings("unused")
    byte updateType = _in.readByte();

    matchCode(_in, IResponse.MTCO);
    @SuppressWarnings("unused")
    int totalCount = _in.readInt();
      
    matchCode(_in, IResponse.MRCO);
    returnedCount = _in.readInt();
      
    matchCode(_in, IResponse.MLCL);      
    
    // gobble up the first MLIT
    if (returnedCount > 0) {
      matchCode(_in, IResponse.MLIT);
    }
    
    return returnedCount;
  }
  
  private void getSong(final Catalog catalog) throws Exception {
    
    int size, id = 0, track = 0;
    String name = null, 
      album = null, 
      artist = null,
      format = null;

    boolean done = false;
    while (!done && _in.available() > 0) {
      int code = _in.readInt();
      size = _in.readInt();

      switch (code) {
        case IResponse.MLIT:
          done = true;
          break;
        case IResponse.MIID:
          id = _in.readInt();
          break;        
          case IResponse.MINM:
          name = readString(size);
          break;
        case IResponse.ASAL:
          album = readString(size);
          break;
        case IResponse.ASAR:
          artist = readString(size);
          break;
        case IResponse.ASTN:
          track = _in.readShort();
          break;
        case IResponse.ASFM:
          format = readString(size);
          if (format != null) {
            if (format.length() > 3) {
              format = format.substring(0, 3);
            }
            
            format = format.toLowerCase();
          }
          break;
        default:
          //Log.d(Constants.LOG_SOURCE, "Unexpected code: " + Integer.toHexString(code));
          _in.skipBytes(size);
          break;
      }
    }
    
    if (artist == null || artist.trim().length() == 0) {
      artist = DEFAULT_ARTIST;
    }
    
    if (album == null || album.trim().length() == 0) {
      album = DEFAULT_ALBUM;
    }
    
    if (SUPPORTED_FORMATS.contains(format)) {
      catalog.addSong(name, track, id, album, artist);
    }
  }
  
  private void endSongList() throws Exception {
    endRequest(_in);
  }
  
  private List<Playlist> getPlaylists(final int database, final Catalog catalog) throws Exception {
    logger.debug("Getting playlists from server.");
    List<Playlist> result = new ArrayList<Playlist>();
    
    _in = beginRequest(String.format(IRequest.PLAYLIST_LIST, database, PLAYLIST_LIST_META_FIELDS, _sessionId, _server.getRevision()),
          IResponse.APLY);
    
    // update type
    matchCode(_in, IResponse.MUTY);
    _in.readByte();
    // total count
    matchCode(_in, IResponse.MTCO);
    _in.readInt();
    // returned count
    matchCode(_in, IResponse.MRCO);
    _in.readInt();
    // begin list
    matchCode(_in, IResponse.MLCL);      
    // gobble up the first MLIT
    matchCode(_in, IResponse.MLIT);

    // It appears that the counts returned from rhythmbox are 
    // always 0 - just read until there is no more data...
    while (_in.available() > 0) {
      int size;
      Playlist pl = new Playlist();
      
      boolean done = false;
      while (!done && _in.available() > 0) {
        
        int code = _in.readInt();
        size = _in.readInt();
    
        switch (code) {
          case IResponse.MLIT:
            done = true;
            break;
    
          case IResponse.MIID:
            pl.setServerId((long)_in.readInt());
            break;
            
            case IResponse.MINM:
            pl.setName(readString(size));
            break;
            
            case IResponse.MIMC:
              pl.setCount(_in.readInt());
              break;
            
            case IResponse.ABPL:
              pl.setBaselist(_in.readByte() == 1);
              break;
              
          default:
            //Log.d(Constants.LOG_SOURCE, "Unexpected code: " + Integer.toHexString(code));
            _in.skipBytes(size);
            break;
        }
      }
      
      pl.setId(catalog.addPlaylist(pl.getName(), pl.getServerId(), pl.isBaselist(), pl.getCount()));
      
      if (!pl.isBaselist()) {
        result.add(pl);
      }
    }
    
    endRequest(_in);
  
    return result;
  }
  
  /**
   * Loads playlist entries.
   * 
   * @param database DAAP Database Id
   * @param playlistId Local playlist id
   * @param playlist DAAP playlist id
   * @param catalog Catalog
   * @throws Exception
   */
  @SuppressWarnings("unused")
  private void loadPlaylist(final int database, final long playlistId, final long playlist, final Catalog catalog) throws Exception {
    //Log.d(Constants.LOG_SOURCE, "Getting playlist: " + playlist);
    
    try {
      _in = beginRequest(String.format(IRequest.PLAYLIST, database, playlist, 
          PLAYLIST_META_FELDS, _sessionId, _server.getRevision()), IResponse.APSO);
    } catch (IOException e) {
      //Log.w(Constants.LOG_SOURCE, "Error getting playlist.", e);
      return; // this has been seen to occur with certain playlists from mt-daapd
    }
    
    matchCode(_in, IResponse.MUTY);
    byte updateType = _in.readByte();

    int code, size, totalCount, returnedCount;

    try {
      // Total count
      matchCode(_in, IResponse.MTCO);
      totalCount = _in.readInt();
      
      // Returned count
      matchCode(_in, IResponse.MRCO);
      returnedCount = _in.readInt();

      //Log.d(Constants.LOG_SOURCE, "Total: " + totalCount + ", returned: " + returnedCount);
      
      // begin list
      matchCode(_in, IResponse.MLCL);

    } catch (DAAPException ex) {
      // Protocol defines that we should get MTCO and MRCO at this point,
      // however, rhythmbox doesn't provide that.
      if (ex.getLastCode() == IResponse.MLCL) {
        _in.readInt();
      } else {
        throw ex;
      }
    }
    
    boolean inItem = false;
    int id = 0;

    while (_in.available() > 0) {
      code = _in.readInt();
      size = _in.readInt();

      switch (code) {
        case IResponse.MLIT:
          if (inItem)
            catalog.addPlaylistEntry(playlistId, id);
          else
            inItem = true;
          break;
  
        case IResponse.MIID:
          id = _in.readInt();
          break;
                    
        default:
          //Log.d(Constants.LOG_SOURCE, "Unexpected code: " + Integer.toHexString(code));
          _in.skipBytes(size);
          break;
      }
    }
    
    if (inItem) {
      catalog.addPlaylistEntry(playlistId, id);      
    }
    
    endRequest(_in);
  }
  
  @Override
  public void downloadSong(final long id) {
    if (_downloader != null && _downloader.isAlive()) {
      logger.debug("Download is still running, not starting another.");
    }
            
    _downloader = new DownloadThread(id);
    _downloader.start();
  }
  
  @Override
  public void cancelDownload() {
    if (_downloader != null && _downloader.isAlive()) {
      logger.debug("Interrupting download thread.");
      _downloader.requestStop();
    }
  }
  
  public long getSessionId() {
    return _sessionId;
  }
  
  public URLConnection getConnection() {
    return _connection;
  }
}




Java Source Code List

com.afqa123.log.DefaultLogger.java
com.afqa123.log.FileLogger.java
com.afqa123.log.LoggerFactory.java
com.afqa123.log.Logger.java
com.afqa123.log.NullLogger.java
com.afqa123.shareplay.DialogFactory.java
com.afqa123.shareplay.MediaPlayerService.java
com.afqa123.shareplay.SelectionActivity.java
com.afqa123.shareplay.SharePlayActivity.java
com.afqa123.shareplay.common.AuthorizationException.java
com.afqa123.shareplay.common.Base64.java
com.afqa123.shareplay.common.Constants.java
com.afqa123.shareplay.common.CustomFeedback.java
com.afqa123.shareplay.common.DAAPException.java
com.afqa123.shareplay.common.DBHelper.java
com.afqa123.shareplay.common.Feedback.java
com.afqa123.shareplay.common.Filename.java
com.afqa123.shareplay.common.ListWrapper.java
com.afqa123.shareplay.common.StoppableThread.java
com.afqa123.shareplay.common.StreamProxy.java
com.afqa123.shareplay.data.ContentCode.java
com.afqa123.shareplay.data.Item.java
com.afqa123.shareplay.data.Playlist.java
com.afqa123.shareplay.impl.Client.java
com.afqa123.shareplay.impl.DatabaseCatalog.java
com.afqa123.shareplay.impl.ServerProvider.java
com.afqa123.shareplay.impl.Server.java
com.afqa123.shareplay.interfaces.Catalog.java
com.afqa123.shareplay.interfaces.IClient.java
com.afqa123.shareplay.interfaces.IServerProvider.java