package edu.mit.mitmobile2.news.net; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.LinkedHashMap; import android.app.AlertDialog; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; //import android.graphics.Bitmap; //import android.graphics.BitmapFactory; import android.os.AsyncTask; //import android.support.v4.util.LruCache; import android.util.Log; import edu.mit.mitmobile2.Global; import edu.mit.mitmobile2.R; //import android.widget.ImageView; import edu.mit.mitmobile2.news.beans.NewsCategory; import edu.mit.mitmobile2.news.beans.NewsStory; //import android.support.v4.util.LruCache; public class NewsDownloader { final int MAX_STORIES_PER_CAREGORY = 200; final long STORY_CACHE_TIME = 14400000; //(4 * 3600 * 1000) 4 hours (in milliseconds) final long CATEGORY_CACHE_TIME = 86400000; //(24 * 3600 * 1000) 24 hours (in milliseconds) private final Object mSqlCacheLock = new Object(); private NewsDbHelper dbHelper; private SQLiteDatabase database; //"http://mobile-dev.mit.edu/latestStable/apis/news"; /* * http://m.mit.edu/apis/news/stories/, * http://mobile-stage.mit.edu/apis/news/stories/, * http://mobile-dev.mit.edu/latestStable/apis/news/stories/ */ public static String NEWS_PATH; private LinkedHashMap<String, String> categories = new LinkedHashMap<String, String>(); //private LruCache<String, Bitmap> mMemoryCache; private static NewsDownloader instance; public static NewsDownloader getInstance(Context context){ if(instance==null){ instance = new NewsDownloader(context); if(Global.getMobileWebDomain().equals("mobile-dev.edu")){ NEWS_PATH = "http://"+Global.getMobileWebDomain()+"/latestStable/apis/news"; }else{ NEWS_PATH = "http://"+Global.getMobileWebDomain()+"/apis/news"; } } return instance; } private NewsDownloader(Context context) { //mContext = context; dbHelper = NewsDbHelper.getInstance(context); //final int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024); //Log.d("NEWS", "Memory is: "+maxMemory); // Use 1/2th of the available memory for this memory cache. //final int cacheSize = maxMemory / 5; /*mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in kilobytes rather than // number of items. return bitmap.getRowBytes()*bitmap.getHeight() / 1024; } };*/ openDatabase(); } private void openDatabase(){ if(database == null || !database.isOpen()){ synchronized(mSqlCacheLock){ database = dbHelper.getWritableDatabase(); mSqlCacheLock.notifyAll(); } } gcCache(); } private void gcCache(){ int noRows = 0; //openDatabase(); //make sure database is open long time = (int)(System.currentTimeMillis() / 3600000); //1000 * 60 * 60 synchronized(mSqlCacheLock){ noRows = database.delete(NewsDbHelper.TABLE_NAME, NewsDbHelper.INSERTED+" < ?", new String[]{String.valueOf(time)}); mSqlCacheLock.notifyAll(); } Log.d("NEWS_CACHE","CLEARED "+noRows+" records"); } private String getCachedResponse(String url){ openDatabase(); //make sure database is open String ret = null; synchronized(mSqlCacheLock){ Cursor cursor = database.query(NewsDbHelper.TABLE_NAME, new String[] {NewsDbHelper.VALUE}, NewsDbHelper.KEY+" like ?" , new String[]{url}, null, null, null); if(cursor!=null && cursor.getCount()>0 && cursor.moveToFirst()){ ret = cursor.getString(cursor.getColumnIndex(NewsDbHelper.VALUE)); cursor.close(); mSqlCacheLock.notifyAll(); } } return ret; } private synchronized void setCachedResponse(String url, String data, long cache_time){ openDatabase(); //make sure database is open long time = (int)(System.currentTimeMillis() / 3600000); //1000 * 60 * 60 ContentValues cv = new ContentValues(); cv.put(NewsDbHelper.KEY, url); cv.put(NewsDbHelper.VALUE, data); cv.put(NewsDbHelper.INSERTED, cache_time + time); if(url!=null && data!=null){ synchronized(mSqlCacheLock){ int r = database.delete(NewsDbHelper.TABLE_NAME, NewsDbHelper.KEY+" = ?", new String[]{url}); Log.d("NEWS_CACHE","Remove rows: "+ r); database.insert(NewsDbHelper.TABLE_NAME, null, cv); mSqlCacheLock.notifyAll(); } } } private InputStream OpenHttpConnection(String urlString) throws IOException { InputStream in = null; int response = -1; URL url = new URL(urlString); URLConnection conn = url.openConnection(); if (!(conn instanceof HttpURLConnection)) throw new IOException("Not an HTTP connection"); try{ HttpURLConnection httpConn = (HttpURLConnection) conn; httpConn.setUseCaches(true); httpConn.setAllowUserInteraction(false); httpConn.setInstanceFollowRedirects(true); httpConn.setRequestMethod("GET"); httpConn.connect(); response = httpConn.getResponseCode(); if (response == HttpURLConnection.HTTP_OK) { in = httpConn.getInputStream(); }else{ throw new IOException("Bad request: "+response); } }catch (Exception ex) { Log.d("Networking", ""+ex.getLocalizedMessage()); throw new IOException("Error connecting"); } return in; } private String downloadText(String URL, boolean refresh) { String str = null; if(!refresh){ str = getCachedResponse(URL); if(str!=null){ return str; } } InputStream in = null; try { in = OpenHttpConnection(URL); if(in!=null){ BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder stringBuilder = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { stringBuilder.append(line + "\n"); } } catch (IOException e) { Log.d("Networking",""+e.getLocalizedMessage()); return null; } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } str = stringBuilder.toString(); }else{ throw new IOException("No InputStream"); } } catch (IOException e) { Log.d("Networking", ""+e.getLocalizedMessage()); return null; } setCachedResponse(URL,str,STORY_CACHE_TIME); return str; } /*private Bitmap downloadImage(String URL){ Bitmap bitmap = this.getBitmapFromMemCache(URL); if(bitmap!=null){ return bitmap; } InputStream in = null; try{ in = OpenHttpConnection(URL); if(in!=null){ bitmap = BitmapFactory.decodeStream(in); in.close(); this.addBitmapToMemoryCache(URL, bitmap); }else{ throw new IOException("No InputStream"); } }catch(IOException e1){ Log.d("NetworkingActivity",e1.getLocalizedMessage()); return null; }catch(OutOfMemoryError e2){ Log.d("NEWS",e2.getLocalizedMessage()); return null; } return bitmap; } public class DownloadImageTask extends AsyncTask<String, Bitmap, Long>{ ImageProgressListener ipl; ImageView view; public void setListener(ImageProgressListener ipl){ this.ipl = ipl; } public void setViewer(ImageView view){ this.view = view; } @Override protected Long doInBackground(String... urls){ long imagesCount = 0; for(int i=0;i<urls.length;i++){ Bitmap imageDownloaded = downloadImage(urls[i]); if(imageDownloaded!=null){ imagesCount++; publishProgress(imageDownloaded); } } return imagesCount; } @Override protected void onProgressUpdate(Bitmap... bitmap){ if(ipl!=null){ ipl.onProgressUpdateBitmap(bitmap); } if(view!=null && bitmap!=null && bitmap.length>0){ view.setImageBitmap(bitmap[0]); } } @Override protected void onPostExecute(Long imagesDownloaded){ if(ipl!=null) ipl.onPostExecuteBitmap(imagesDownloaded); } }*/ public class DownloadTextTask extends AsyncTask<String, String, Long> { private TextProgressListener jpl; private boolean refresh; public void setListener(TextProgressListener jpl){ this.jpl = jpl; refresh = false; } public void setRefresh(boolean b){ this.refresh = b; } @Override protected Long doInBackground(String... urls) { long textCount = 0; for(int i=0;i<urls.length;i++){ String textDownloaded = downloadText(urls[i], refresh); if(textDownloaded!=null){ textCount++; publishProgress(textDownloaded); } } return textCount; } @Override protected void onProgressUpdate(String... text){ jpl.onProgressUpdateText(text); } @Override protected void onPostExecute(Long textDownloaded) { jpl.onPostExecuteText(textDownloaded); } } public class DownloadCategoriesTask extends AsyncTask<Void, Void, ArrayList<NewsCategory>> { private CategoryProgressListener cpl; private boolean refresh; public DownloadCategoriesTask(CategoryProgressListener c){ this.cpl = c; this.refresh = false; } public void setRefresh(boolean b){ this.refresh = b; } @Override protected ArrayList<NewsCategory> doInBackground(Void...v) { Log.d("NEWS", "URL: "+NewsDownloader.NEWS_PATH+"/categories/"); String ret = downloadText(NEWS_PATH+"/categories/",refresh); CategoryParser cp = new CategoryParser(); ArrayList<NewsCategory> nc = cp.parseArrayFromString(ret); return nc; } @Override protected void onPostExecute(ArrayList<NewsCategory> list) { cpl.onPostExecute(list); } } public class DownloadStoryTask extends AsyncTask<String, NewsStory, Long> { private StoryProgressListener spl; private boolean refresh; public DownloadStoryTask(StoryProgressListener c){ this.spl = c; this.refresh = false; } public void setRefresh(boolean b){ this.refresh = b; } @Override protected Long doInBackground(String... ids) { long nr = 0; StoryParser sp = new StoryParser(); for(int i=0;i<ids.length;i++){ String url = NewsDownloader.NEWS_PATH+"/stories/"+ids[i]; Log.d("NEWS", "URL: "+url); String ret = downloadText(url,refresh); NewsStory st = sp.parseObjectFromString(ret); publishProgress(st); } return nr; } @Override protected void onProgressUpdate(NewsStory...st){ spl.onProgressUpdate(st); } @Override protected void onPostExecute(Long nr) { spl.onPostExecute(nr); } } public class DownloadStoriesTask extends AsyncTask<String, ArrayList<NewsStory>, Long> { StoriesProgressListener spl; /* type : * "category" we are passing category id * "search" we are passing search query * "urls" we are passing full urls (default) */ private String type = "urls"; private int offset; // default 0 private int limit; // default 20 private boolean refresh; public DownloadStoriesTask(StoriesProgressListener spl){ this(spl,"urls",0,20); } public DownloadStoriesTask(StoriesProgressListener spl,String type){ this(spl,type,0,20); } public DownloadStoriesTask(StoriesProgressListener spl,String type, int offset, int limit){ this.spl = spl; this.type = type; this.offset = offset; this.limit = limit; this.refresh = false; } public void setRefresh(boolean b){ this.refresh = b; } @SuppressWarnings("unchecked") @Override protected Long doInBackground(String... urls) { String pre_url = ""; long nr = 0; if(this.type.equals("category")){ pre_url = NewsDownloader.NEWS_PATH+"/stories/?offset="+offset+"&limit="+limit+"&category="; }else if(this.type.equals("ids")){ pre_url = NewsDownloader.NEWS_PATH+"/stories/"; }else if(this.type.equals("search")){ pre_url = NewsDownloader.NEWS_PATH+"/stories/?offset="+offset+"&limit="+limit+"&q="; } StoryParser sp = new StoryParser(); for(int i=0;i<urls.length;i++){ ArrayList<NewsStory> al = new ArrayList<NewsStory>(); Log.d("NEWS", "URL: "+pre_url + urls[i]); String textDownloaded = downloadText(pre_url + urls[i], refresh); if(textDownloaded!=null){ if(this.type.equals("ids")){ NewsStory s = sp.parseObjectFromString(textDownloaded); al.add(s); }else{ al.addAll(sp.parseArrayFromString(textDownloaded)); } } publishProgress(al); nr ++; } return nr; } @Override protected void onProgressUpdate(ArrayList<NewsStory>...list){ spl.onProgressUpdate(list); } @Override protected void onPostExecute(Long nr) { spl.onPostExecute(nr); } } /* public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null && key!=null && bitmap!=null) { mMemoryCache.put(key, bitmap); Log.d("NEWS", "Image added to memory with size(kB): "+(bitmap.getRowBytes()*bitmap.getHeight()/1024)); } } public Bitmap getBitmapFromMemCache(String key) { if(key!=null) return mMemoryCache.get(key); return null; } */ public void showOpenInBrowserDialog(final Context c, final String url){ AlertDialog.Builder builder = new AlertDialog.Builder(c); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface arg0, int arg1) { Intent br = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); c.startActivity(br); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface dialog,int which) { //do nothing :-) } }); builder.setTitle("Open in default browser?"); builder.setMessage(url); builder.show(); } public void setCategory(String key, String value){ this.categories.put(key, value); } public String getCategory(String key){ return this.categories.get(key); } public LinkedHashMap<String, String> getAllCategories(){ return this.categories; } }