package com.zhan_dui.download.alfred.missions;
import com.zhan_dui.download.alfred.utils.AlfredUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
/**
* Created by daimajia on 14-1-30.
*/
public class Mission implements Runnable{
public static int ID = 0;
private final int MissionID;
private String Uri;
private String SaveDir;
private String SaveName;
protected int mDownloaded;
protected int mFileSize;
private String mOriginFilename;
private String mExtension;
private String mShowName;
private long mPreviousNotifyTime;
private int mProgressNotifyInterval = 1;
private TimeUnit mTimeUnit = TimeUnit.SECONDS;
private long mLastSecondDownloadTime = 0;
private long mLastSecondDownload = 0;
private int mPreviousPercentage = -1;
protected long mStartTime;
protected long mEndTime;
public enum RESULT_STATUS{
STILL_DOWNLOADING,SUCCESS,FAILED
}
private RESULT_STATUS mResultStatus = RESULT_STATUS.STILL_DOWNLOADING;
private boolean isDone = false;
private boolean isPaused = false;
private boolean isSuccess = false;
private boolean isCanceled = false;
private final Object o = new Object();
private ArrayList<MissionListener> missionListeners;
private HashMap<String,Object> extras;
/**
*
* @param uri the file url you want to download
* @param saveDir save to which directory
*/
public Mission(String uri,String saveDir){
MissionID = ID++;
Uri = uri;
SaveDir = saveDir;
mStartTime = System.currentTimeMillis();
mPreviousNotifyTime = mStartTime;
setOriginFileName(AlfredUtils.getFileName(uri));
setExtension(AlfredUtils.getFileExtension(uri));
SaveName = getOriginFileName() + "." + getExtension();
missionListeners = new ArrayList<MissionListener>();
extras = new HashMap<String, Object>();
}
/**
*
* @param uri the file url you want to download
* @param saveDir save to which directory
* @param saveFilename the file name you want to save as
*/
public Mission(String uri, String saveDir, String saveFilename){
this(uri, saveDir);
SaveName = getSafeFilename(saveFilename);
}
public Mission(String uri,String saveDir,String saveFilename,String showName){
this(uri,saveDir,saveFilename);
this.mShowName = showName;
}
public void addMissionListener(MissionListener listener){
missionListeners.add(listener);
}
public void removeMissionListener(MissionListener listener){
missionListeners.remove(listener);
}
public void setProgressNotificateInterval(TimeUnit unit,int interval){
mTimeUnit = unit;
mProgressNotifyInterval = interval;
}
@Override
public void run() {
notifyStart();
BufferedInputStream in = null;
FileOutputStream out = null;
HttpURLConnection httpURLConnection;
try
{
httpURLConnection = (HttpURLConnection) new URL(Uri).openConnection();
setFileSize(httpURLConnection.getContentLength());
notifyMetaDataReady();
in = new BufferedInputStream(httpURLConnection.getInputStream());
out = getSafeOutputStream(getSaveDir(),getSaveName());
byte data[] = new byte[1024];
int count;
notifyMetaDataReady();
while (!isCanceled() && (count=in.read(data,0,1024)) != -1){
out.write(data,0,count);;
mDownloaded+=count;
notifyPercentageChange();
notifySpeedChange();
checkPaused();
}
if(isCanceled == false){
notifyPercentageChange();
notifySuccess();
}else{
notifyCancel();
}
} catch (Exception e) {
notifyError(e);
} finally{
try {
if(in!=null) in.close();
if(out!=null) out.close();
} catch (IOException e) {
notifyError(e);
}
notifyFinish();
}
}
protected FileOutputStream getSafeOutputStream(String directory,String filename) {
String filepath;
if(directory.lastIndexOf(File.separator) != directory.length() - 1){
directory += File.separator;
}
File dir = new File(directory);
dir.mkdirs();
filepath = directory + filename;
File file = new File(filepath);
try{
file.createNewFile();
return new FileOutputStream(file.getCanonicalFile().toString());
}catch (Exception e){
e.printStackTrace();
throw new Error("Can not get an valid output stream");
}
}
protected String getSafeFilename(String name){
return name.replaceAll("[\\\\|\\/|\\:|\\*|\\?|\\\"|\\<|\\>|\\|]", "-");
}
protected void checkPaused(){
synchronized (o){
while (isPaused){
try {
notifyPause();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void pause(){
if(isPaused() || isDone()) return;
isPaused = true;
}
public void resume(){
if(!isPaused() || isDone()){
return;
}
synchronized (o){
isPaused = false;
o.notifyAll();
}
notifyResume();
}
public void cancel(){
isCanceled = true;
if(isPaused()){
resume();
}
}
protected final void notifyPercentageChange(){
if(missionListeners != null && missionListeners.size()!=0){
int currentPercentage = getPercentage();
if(mPreviousPercentage != currentPercentage){
for(MissionListener l:missionListeners){
l.onPercentageChange(this);
}
mPreviousPercentage = currentPercentage;
}
}
}
protected final void notifySpeedChange(){
if(missionListeners != null && missionListeners.size()!=0){
long currentNotifyTime = System.currentTimeMillis();
long notifyDuration = currentNotifyTime - mPreviousNotifyTime;
long spend = mTimeUnit.convert(notifyDuration,TimeUnit.MILLISECONDS);
if(spend >= mProgressNotifyInterval){
mPreviousNotifyTime = currentNotifyTime;
for(MissionListener l : missionListeners){
l.onSpeedChange(this);
}
}
long speedRecordDuration = currentNotifyTime - mLastSecondDownloadTime;
if(TimeUnit.MILLISECONDS.toSeconds(speedRecordDuration) >= 1){
mLastSecondDownloadTime = currentNotifyTime;
mLastSecondDownload = getDownloaded();
}
}
}
protected final void notifyStart(){
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onStart(this);
}
}
}
protected final void notifyPause(){
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onPause(this);
}
}
}
protected final void notifyResume(){
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onResume(this);
}
}
}
protected final void notifyCancel(){
isDone = true;
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onCancel(this);
}
}
}
protected final void notifyMetaDataReady(){
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onMetaDataPrepared(this);
}
}
}
protected final void notifySuccess(){
mResultStatus = RESULT_STATUS.SUCCESS;
isDone = true;
isSuccess = true;
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onSuccess(this);
}
}
}
protected final void notifyError(Exception e){
mResultStatus = RESULT_STATUS.FAILED;
isDone = true;
isSuccess = false;
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onError(this, e);
}
}
}
protected final void notifyFinish(){
mEndTime = System.currentTimeMillis();
isDone = true;
if(missionListeners != null && missionListeners.size()!=0){
for(MissionListener l : missionListeners){
l.onFinish(this);
}
}
}
public String getUri() {
return Uri;
}
public String getSaveDir() {
return SaveDir;
}
public String getSaveName() {
return SaveName;
}
public long getDownloaded() {
return mDownloaded;
}
public long getFilesize() {
return mFileSize;
}
protected void setFileSize(int size){
mFileSize = size;
}
public long getTimeSpend() {
if(mEndTime != 0){
return mEndTime - mStartTime;
}else{
return System.currentTimeMillis() - mStartTime;
}
}
public String getAverageReadableSpeed(){
return AlfredUtils.getReadableSpeed(getDownloaded(), getTimeSpend(), TimeUnit.MILLISECONDS);
}
public String getAccurateReadableSpeed(){
return AlfredUtils.getReadableSize(getDownloaded() - mLastSecondDownload, false) + "/s";
}
public int getPercentage(){
if(mFileSize == 0){
return 0;
}else{
return (int)(mDownloaded * 100.0f/mFileSize);
}
}
public String getReadablePercentage(){
StringBuilder builder = new StringBuilder();
builder.append(getPercentage());
builder.append("%");
return builder.toString();
}
public void setOriginFileName(String name){
mOriginFilename = name;
}
public String getOriginFileName(){
return mOriginFilename;
}
public void setExtension(String extension){
mExtension = extension;
}
public String getExtension(){
return mExtension;
}
public String getShowName(){
if(mShowName == null || mShowName.length() == 0){
return getSaveName();
}else{
return mShowName;
}
}
public int getMissionID(){
return MissionID;
}
public boolean isDone(){
return isDone;
}
public boolean isSuccess(){
return isSuccess;
}
public boolean isPaused(){return isPaused;}
public boolean isCanceled(){
return isCanceled;
}
public RESULT_STATUS getResultStatus(){
return mResultStatus;
}
public void addExtraInformation(String key, Object value){
extras.put(key,value);
}
public void removeExtraInformation(String key){
extras.remove(key);
}
public Object getExtraInformation(String key){
return extras.get(key);
}
public interface MissionListener<T extends Mission>{
/**
* when the run() start, Notice: do not call some get method, such as getFileType, getOriginFileName. Because
* these information is not ready, you can get them in onMetaDataPrepared.
* @param mission
*/
public void onStart(T mission);
/**
* when the download file meta information ready, such as the target file size,
* file type, video duration, and some other meta information.
* @param mission
*/
public void onMetaDataPrepared(T mission);
/**
* if you want to update your ui information, you can do in this function(notice: this function
* was been called in thread). You can involk getPercentage(),getAverageReadableSpeed(),and so on.
* @param mission
*/
public void onPercentageChange(T mission);
public void onSpeedChange(T mission);
/**
* when error occurs, onError() will be called;
* @param mission the mission which trigger the error function
* @param e the exception.
*/
public void onError(T mission, Exception e);
/**
* called when download successfully
* @param mission
*/
public void onSuccess(T mission);
/**
* no matter success or failed, this function will be call in the end. You can do some clean up or
* some other things.
* @param mission
*/
public void onFinish(T mission);
public void onPause(T mission);
public void onResume(T mission);
public void onCancel(T mission);
}
}