/* This file is part of the Android Clementine Remote.
* Copyright (C) 2013, Andreas Muttscheller <asfa194@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.qspool.clementineremote.backend;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteException;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import de.qspool.clementineremote.SharedPreferencesKeys;
import de.qspool.clementineremote.backend.elements.DownloaderResult;
import de.qspool.clementineremote.backend.elements.DownloaderResult.DownloadResult;
import de.qspool.clementineremote.backend.library.LibraryDatabaseHelper;
import de.qspool.clementineremote.backend.listener.OnLibraryDownloadListener;
import de.qspool.clementineremote.backend.pb.ClementineMessage;
import de.qspool.clementineremote.backend.pb.ClementineMessageFactory;
import de.qspool.clementineremote.backend.pb.ClementineRemoteProtocolBuffer.MsgType;
import de.qspool.clementineremote.backend.pb.ClementineRemoteProtocolBuffer.ResponseLibraryChunk;
import de.qspool.clementineremote.utils.Utilities;
public class ClementineLibraryDownloader extends
AsyncTask<ClementineMessage, Long, DownloaderResult> {
private final String TAG = "ClementineLibraryDownloader";
private Context mContext;
private SharedPreferences mSharedPref;
private ClementineSimpleConnection mClient = new ClementineSimpleConnection();
private LibraryDatabaseHelper mLibrary;
private LinkedList<OnLibraryDownloadListener> listeners
= new LinkedList<>();
private int mTotalSize;
public ClementineLibraryDownloader(Context context) {
mContext = context;
mLibrary = new LibraryDatabaseHelper();
mSharedPref = PreferenceManager.getDefaultSharedPreferences(mContext);
}
/**
* Add a OnLibraryDownloadFinishedListener. It is emitted when the download
* is finished and the library file is available
*
* @param l The listener object
*/
public void addOnLibraryDownloadListener(
OnLibraryDownloadListener l) {
listeners.add(l);
}
public void removeOnLibraryDownloadListener(OnLibraryDownloadListener l) {
listeners.remove(l);
}
public void startDownload(ClementineMessage message) {
this.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
}
@Override
protected DownloaderResult doInBackground(ClementineMessage... params) {
if (mSharedPref.getBoolean(SharedPreferencesKeys.SP_WIFI_ONLY, false)
&& !Utilities.onWifi()) {
return new DownloaderResult(0, DownloaderResult.DownloadResult.ONLY_WIFI);
}
// First create a connection
if (!connect()) {
return new DownloaderResult(0, DownloaderResult.DownloadResult.CONNECTION_ERROR);
}
// Start the download
return startDownloading(params[0]);
}
@Override
protected void onProgressUpdate(Long... progress) {
fireOnProgressUpdateListener(progress[0]);
if (progress[0] == mTotalSize) {
fireOnOptimizeLibraryListener();
}
}
@Override
protected void onCancelled() {
fireOnLibraryDownloadFinishedListener(new DownloaderResult(0,
DownloaderResult.DownloadResult.CANCELLED));
}
@Override
protected void onPostExecute(DownloaderResult result) {
// Notify the listeners
fireOnLibraryDownloadFinishedListener(result);
}
/**
* Connect to Clementine
*
* @return true if the connection was established, false if not
*/
private boolean connect() {
String ip = mSharedPref.getString(SharedPreferencesKeys.SP_KEY_IP, "");
int port;
try {
port = Integer.valueOf(mSharedPref.getString(SharedPreferencesKeys.SP_KEY_PORT,
String.valueOf(Clementine.DefaultPort)));
} catch (NumberFormatException e) {
port = Clementine.DefaultPort;
}
int authCode = mSharedPref.getInt(SharedPreferencesKeys.SP_LAST_AUTH_CODE, 0);
return mClient.createConnection(ClementineMessageFactory
.buildConnectMessage(ip, port, authCode, false, true));
}
/**
* Start the Download
*/
private DownloaderResult startDownloading(
ClementineMessage clementineMessage) {
boolean downloadFinished = false;
DownloaderResult result = new DownloaderResult(0,
DownloadResult.SUCCESSFUL);
File f = null;
FileOutputStream fo = null;
// Now request the songs
mClient.sendRequest(clementineMessage);
while (!downloadFinished) {
// Check if the user canceled the process
if (isCancelled()) {
// Close the stream and delete the incomplete file
try {
if (fo != null) {
fo.flush();
fo.close();
}
if (f != null) {
f.delete();
}
} catch (IOException e) {
}
break;
}
// Get the raw protocol buffer
ClementineMessage message = mClient.getProtoc(0);
if (message.isErrorMessage()) {
result = new DownloaderResult(0, DownloadResult.CONNECTION_ERROR);
break;
}
// Is the download forbidden?
if (message.getMessageType() == MsgType.DISCONNECT) {
result = new DownloaderResult(0, DownloadResult.FOBIDDEN);
break;
}
// Ignore other elements!
if (message.getMessageType() != MsgType.LIBRARY_CHUNK) {
continue;
}
ResponseLibraryChunk chunk = message.getMessage()
.getResponseLibraryChunk();
try {
// Check if we need to create a new file
if (f == null) {
// Check if we have enougth free space
// size times 2, because we optimise the table later and
// need space for that too!
if ((chunk.getSize() * 2) > Utilities.getFreeSpaceExternal()) {
result = new DownloaderResult(0,
DownloadResult.INSUFFIANT_SPACE);
break;
}
f = mLibrary.getLibraryDb();
// User wants to override files, so delete it here!
// The check was already done in processSongOffer()
if (f.exists()) {
f.delete();
}
f.createNewFile();
fo = new FileOutputStream(f);
mTotalSize = chunk.getSize();
}
// Write chunk to sdcard
fo.write(chunk.getData().toByteArray());
publishProgress(f.length());
// Have we downloaded all chunks?
if (chunk.getChunkCount() == chunk.getChunkNumber()) {
fo.flush();
fo.close();
f = null;
downloadFinished = true;
}
} catch (IOException e) {
result = new DownloaderResult(0,
DownloaderResult.DownloadResult.NOT_MOUNTED);
break;
}
}
// Disconnect at the end
mClient.disconnect(ClementineMessage.getMessage(MsgType.DISCONNECT));
// Optimize library table
if (result.getResult() == DownloadResult.SUCCESSFUL &&
mLibrary.getLibraryDb().exists()) {
try {
mLibrary.optimizeTable();
} catch (SQLiteException e) {
// Database is damaged, delete it
mLibrary.getLibraryDb().delete();
result = new DownloaderResult(0, DownloadResult.ERROR);
}
}
return result;
}
/*
* Fire the listeners
*/
private void fireOnLibraryDownloadFinishedListener(DownloaderResult result) {
for (OnLibraryDownloadListener l : listeners) {
l.OnLibraryDownloadFinished(result);
}
}
private void fireOnOptimizeLibraryListener() {
for (OnLibraryDownloadListener l : listeners) {
l.OnOptimizeLibrary();
}
}
private void fireOnProgressUpdateListener(long progress) {
for (OnLibraryDownloadListener l : listeners) {
l.OnProgressUpdate(progress, mTotalSize);
}
}
}