package com.fatima.life2; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.fatima.life2.R; import com.limegroup.gnutella.Downloader; import com.limegroup.gnutella.GUID; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.downloader.AlreadyDownloadingException; import com.limegroup.gnutella.downloader.FileExistsException; import android.app.Service; import android.content.Context; import android.content.Intent; import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.PowerManager; import android.text.TextUtils; import android.util.Log; public class DownloadService extends Service { private static final int POOL_SIZE = 4; private static final long POLL_INTERVAL = 1500; private static final int BUFFER_SIZE = 4096; private static final int MIN_PROGRESS_STEP = 4096; private static final long MIN_PROGRESS_TIME = 1500; private static final String TAG = "DownloadService"; private ArrayList<DownloadInfo> mDownloads = new ArrayList<DownloadInfo>(); private ArrayList<DownloadObserver> mObservers = new ArrayList<DownloadObserver>(); private ExecutorService mPool; private ArrayList<MediaScannerNotifier> mScanners = new ArrayList<MediaScannerNotifier>(); public class LocalBinder extends Binder { public DownloadService getService() { return DownloadService.this; } } @Override public void onCreate() { Log.i(TAG, "Download service started"); mPool = Executors.newFixedThreadPool(POOL_SIZE); } public void registerDownloadObserver(DownloadObserver observer) { if (observer == null) { throw new IllegalArgumentException("The observer is null."); } synchronized(mObservers) { if (mObservers.contains(observer)) { throw new IllegalStateException("Observer " + observer + " is already registered."); } mObservers.add(observer); } } public void unregisterObserver(DownloadObserver observer) { if (observer == null) { throw new IllegalArgumentException("The observer is null."); } synchronized(mObservers) { int index = mObservers.indexOf(observer); if (index == -1) { throw new IllegalStateException("Observer " + observer + " was not registered."); } mObservers.remove(index); } } /** * Remove all registered observer */ public void unregisterAll() { synchronized(mObservers) { mObservers.clear(); } } public void notifyChanged() { synchronized(mObservers) { for (DownloadObserver o : mObservers) { o.onChange(); } } } public ArrayList<DownloadInfo> getDownloadInfos() { synchronized(mDownloads) { return new ArrayList<DownloadInfo>(mDownloads); } } // public boolean fileBeingDownloaded(SearchResult mp3) { // if (mp3 == null) // return false; // // String filename = mp3.getFileName(); // if (filename == null) // return false; // synchronized(mDownloads) { // for (DownloadInfo d : mDownloads) { // if(d instanceof SogouDownloadInfo){ // if (((SogouDownloadInfo) d).getSogouSearchResult() == null) // continue; // if (((SogouDownloadInfo) d).getSogouSearchResult().getFileName() != null && // filename.equals(((SogouDownloadInfo) d).getSogouSearchResult().getFileName())) // return true; // } // if(d instanceof P2pDownloadInfo) { // if (((P2pDownloadInfo) d).getP2pSearchResult() == null) // continue; // if (((P2pDownloadInfo) d).getP2pSearchResult().getFileName() != null && // filename.equals(((P2pDownloadInfo) d).getP2pSearchResult().getFileName())) // return true; // } // // } // return false; // } // } public boolean fileBeingDownloaded(SogouSearchResult mp3) { if (mp3 == null) return true; String title = mp3.getTitle(); String artist = mp3.getArtist(); String filesize = mp3.getDisplayFileSize(); synchronized(mDownloads) { for (DownloadInfo d : mDownloads) { if (d instanceof SogouDownloadInfo) { SogouSearchResult result = ((SogouDownloadInfo) d).getSogouSearchResult(); if(result.getTitle().equals(title) && result.getArtist().equals(artist) && result.getDisplayFileSize() == filesize) return true; } } } return false; } public boolean fileBeingDownloaded(P2pSearchResult mp3) { if (mp3 == null) return true; String filename = mp3.getFileName(); if (filename == null) return false; synchronized(mDownloads) { for (DownloadInfo d : mDownloads) { if (d instanceof P2pDownloadInfo) { if (((P2pDownloadInfo) d).getP2pSearchResult() == null) continue; if (((P2pDownloadInfo) d).getP2pSearchResult().getFileName() != null && filename.equals(((P2pDownloadInfo) d).getP2pSearchResult().getFileName())) return true; } } } return false; } public void insertDownload(P2pDownloadInfo info) { if (info == null || info.getP2pSearchResult() == null) return; synchronized(mDownloads) { for (DownloadInfo d : mDownloads) { if (d instanceof P2pDownloadInfo) { if (((P2pDownloadInfo)d).getP2pSearchResult().getFileName().equals( info.getP2pSearchResult().getFileName())) return; } } info.setFailed(false); mDownloads.add(info); } synchronized(info) { if (info.isScheduled()) { // No re-entrance. return; } else { info.setScheduled(true); } } // This should not block. mPool.execute(new Task(info)); notifyChanged(); } public void insertDownload(SogouDownloadInfo info) { if (info == null) return; if (TextUtils.isEmpty(info.getTarget())) { Log.e(TAG, "Empty source or target"); return; } synchronized(mDownloads) { // Check if the request has already been added. for (DownloadInfo d : mDownloads) { if(d instanceof SogouDownloadInfo) { if (( (SogouDownloadInfo)d ).getTarget().equals(info.getTarget())) return; } } mDownloads.add(info); // This should not block. mPool.execute(new Task(info)); } notifyChanged(); } public void retryDownload(P2pDownloadInfo info) { if (info == null) return; synchronized(info) { if (info.getP2pSearchResult() == null) return; if (info.isScheduled()) { // No re-entrance. return; } else { info.setFailed(false); info.setScheduled(true); } } // This should not block. mPool.execute(new Task(info)); notifyChanged(); } public void retryDownload(SogouDownloadInfo info) { synchronized(info) { mPool.execute(new Task(info)); } } public void resumeDownload(SogouDownloadInfo info) { synchronized(info) { mPool.execute(new Task(info)); } } public void removeDownload(DownloadInfo info) { if (info == null) return; synchronized(mDownloads) { int index = mDownloads.indexOf(info); if (index == -1) { return; } mDownloads.remove(index); } notifyChanged(); } public void clearFinished() { ArrayList<DownloadInfo> downloads = new ArrayList<DownloadInfo>(); boolean changed = false; synchronized(mDownloads) { for (DownloadInfo d : mDownloads) { if (d.getState() != Downloader.COMPLETE) { downloads.add(d); } else { changed = true; } } if (changed) { mDownloads = downloads; } } if (changed) { notifyChanged(); } } public static boolean isDownloading(int state) { return (state == Downloader.CONNECTING || state == Downloader.DOWNLOADING || state == Downloader.REMOTE_QUEUED || state == Downloader.SAVING || state == Downloader.IDENTIFY_CORRUPTION || state == Downloader.QUEUED || state == Downloader.PAUSED || state == Downloader.WAITING_FOR_RETRY || state == Downloader.WAITING_FOR_USER || state == Downloader.WAITING_FOR_RESULTS || state == Downloader.WAITING_FOR_CONNECTIONS || state == Downloader.ITERATIVE_GUESSING); } @Override public IBinder onBind(Intent intent) { return mBinder; } private final IBinder mBinder = new LocalBinder(); private class Task implements Runnable { private DownloadInfo mInfo; private void download() throws IOException, InterruptedException { if (mInfo == null) return; if (mInfo instanceof P2pDownloadInfo) { P2pDownloadInfo p2pInfo = (P2pDownloadInfo)mInfo; SearchResult rs; synchronized(p2pInfo) { if (p2pInfo.getP2pSearchResult() == null) return; rs = p2pInfo.getP2pSearchResult(); } try { Downloader d = RouterService.download(((P2pSearchResult)rs).getRFDArray(), ((P2pSearchResult)rs).getAlt(), true, new GUID(((P2pSearchResult)rs).getGuid())); Utils.D("Start downloading " + d.getFileName()); synchronized (p2pInfo) { if (!p2pInfo.isScheduled()) { // This is a hack. It means user aborts/clears the download. return; } p2pInfo.setTotalBytes(d.getContentLength()); p2pInfo.setDownloader(d); } // Polling. int bytesRead = 0; Utils.D("old state: " + d.getState()); int state = d.getState(); while (isDownloading(state)) { bytesRead = d.getAmountRead(); synchronized (p2pInfo) { if (!p2pInfo.isScheduled()) { Utils.D("Aborted"); return; // Abort. } p2pInfo.setCurrentBytes(bytesRead); notifyChanged(); } Thread.sleep(POLL_INTERVAL); state = d.getState(); Utils.D(d.getFileName() + ":" + state); } Utils.D("new state: " + d.getState()); synchronized(p2pInfo) { p2pInfo.setCurrentBytes(p2pInfo.getTotalBytes()); } if (state == Downloader.COMPLETE) { Utils.D("Scanning file: " + d.getFile().getAbsolutePath()); ScanMediaFile(d.getFile().getAbsolutePath()); } } catch (FileExistsException e) { synchronized(p2pInfo) { p2pInfo.setFailed(true); p2pInfo.setError(getString(R.string.target_exists)); } e.printStackTrace(); } catch (AlreadyDownloadingException e) { synchronized(p2pInfo) { p2pInfo.setFailed(true); p2pInfo.setError(getString(R.string.already_downloading)); } e.printStackTrace(); } } if (mInfo instanceof SogouDownloadInfo) { SogouDownloadInfo sogouInfo = (SogouDownloadInfo) mInfo; if (sogouInfo.getSogouSearchResult().getDownloadUrl() == null) { //fetch download url SogouMusicSearcher.setMusicDownloadUrl(getApplication(), sogouInfo.getSogouSearchResult()); if (sogouInfo.getSogouSearchResult().getDownloadUrl() == null) { Log.e(TAG, "Empty source or target"); sogouInfo.setStatus(Downloader.GAVE_UP); return; } } URL url; synchronized(sogouInfo) { sogouInfo.setThread(Thread.currentThread()); url = new URL(sogouInfo.getSogouSearchResult().getDownloadUrl()); } HttpURLConnection connection = (HttpURLConnection)url.openConnection(); connection.setRequestProperty("User-Agent", Constants.USER_AGENT); RandomAccessFile outFile = null; InputStream input = null; String tmpFile = sogouInfo.getTarget() + ".tmp"; try { outFile = new RandomAccessFile(tmpFile, "rw"); if (outFile.length() > 0) connection.setRequestProperty("Range", "bytes=" + outFile.length() + "-"); connection.connect(); if (connection.getResponseCode() < 200 || connection.getResponseCode() >= 300) { synchronized(sogouInfo) { //mInfo.setStatus(DownloadInfo.STATUS_FAILED); sogouInfo.setStatus(Downloader.GAVE_UP); } sogouInfo.setError("Connection error (" + connection.getResponseCode() + "): " + connection.getResponseMessage()); return; } try { input = connection.getInputStream(); } catch (FileNotFoundException e) { if (outFile.length() > 0) { // The remote server does not support Range. outFile.close(); new File(tmpFile).delete(); outFile = new RandomAccessFile(tmpFile, "rw"); connection = (HttpURLConnection)url.openConnection(); connection.setRequestProperty("User-Agent", Constants.USER_AGENT); connection.connect(); input = connection.getInputStream(); } else { throw e; } } synchronized(sogouInfo) { sogouInfo.setCurrentBytes((int)outFile.length()); sogouInfo.setTotalBytes((int)outFile.length() + connection.getContentLength()); //mInfo.setStatus(DownloadInfo.STATUS_DOWNLOADING); sogouInfo.setStatus(Downloader.DOWNLOADING); notifyChanged(); } outFile.seek(outFile.length()); byte[] buffer = new byte[BUFFER_SIZE]; int len; int bytesUnaccounted = 0; long timeLastNotification = System.currentTimeMillis(); while ((len = input.read(buffer)) >= 0) { synchronized(sogouInfo) { //if (mInfo.getStatus() == DownloadInfo.STATUS_STOPPED) { if (sogouInfo.getStatus() == Downloader.PAUSED) { return; } } outFile.write(buffer, 0, len); bytesUnaccounted += len; long now = System.currentTimeMillis(); if (bytesUnaccounted > MIN_PROGRESS_STEP && now - timeLastNotification > MIN_PROGRESS_TIME) { synchronized(sogouInfo) { sogouInfo.setCurrentBytes(sogouInfo.getCurrentBytes() + bytesUnaccounted); } bytesUnaccounted = 0; timeLastNotification = now; notifyChanged(); } } synchronized(sogouInfo) { if (sogouInfo.getCurrentBytes() < 100) { //mInfo.setStatus(DownloadInfo.STATUS_FAILED); sogouInfo.setStatus(Downloader.GAVE_UP); sogouInfo.setError("Incomplete file"); return; } sogouInfo.setCurrentBytes(sogouInfo.getTotalBytes()); //mInfo.setStatus(DownloadInfo.STATUS_FINISHED); sogouInfo.setStatus(Downloader.COMPLETE); File oldFile = new File(tmpFile); if (oldFile.renameTo(new File(sogouInfo.getTarget()))) { //mScanner.ScanMediaFile(DownloadService.this, mInfo.getTarget()); ScanMediaFile(sogouInfo.getTarget()); } } } finally { if (outFile != null) outFile.close(); if (input != null) input.close(); } } } public Task(DownloadInfo download) { mInfo = download; } @Override public void run() { if (mInfo == null) return; PowerManager.WakeLock wakeLock = null; try { PowerManager pm = (PowerManager)DownloadService.this.getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); wakeLock.acquire(); download(); } catch (Exception e) { e.printStackTrace(); synchronized(mInfo) { if(mInfo instanceof P2pDownloadInfo) { ((P2pDownloadInfo) mInfo).setFailed(true); } if(mInfo instanceof SogouDownloadInfo) { //mInfo.setStatus(DownloadInfo.STATUS_FAILED); ((SogouDownloadInfo) mInfo).setStatus(Downloader.GAVE_UP); } mInfo.setError(e.getMessage()); } } finally { if (wakeLock != null) { wakeLock.release(); wakeLock = null; } notifyChanged(); Utils.D("task finished: " + mInfo); synchronized(mInfo) { if (mInfo instanceof P2pDownloadInfo) ((P2pDownloadInfo) mInfo).setScheduled(false); notifyChanged(); } } } } private class MediaScannerNotifier implements MediaScannerConnectionClient { private MediaScannerConnection mConnection; private String mPath; private String mMimeType; public MediaScannerNotifier(String path, String mimeType) { mPath = path; mMimeType = mimeType; mConnection = new MediaScannerConnection(DownloadService.this, this); mConnection.connect(); } public void onMediaScannerConnected() { mConnection.scanFile(mPath, mMimeType); } public void onScanCompleted(String path, Uri uri) { if (mPath == null) return; if (mPath.equals(path)) { Utils.D("File scanned: " + path); mConnection.disconnect(); synchronized(mScanners) { mScanners.remove(this); } } } } private void ScanMediaFile(final String musicPath) { synchronized(mScanners) { mScanners.add(new MediaScannerNotifier(musicPath, "audio/mpeg")); } } }