package edu.purdue.app.dining; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.util.GregorianCalendar; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import android.content.Context; import android.os.AsyncTask; import android.util.Log; /** Handles everything related to downloading new html source code, parsing it, and adding it to the database. * Create one of these classes, then call update() on it to run the whole thing on each page you want to update. * The class will construct the proper address and spawn a SourceDownloader AsyncTask which downloads the source. * On completion, it will automatically call the parser method, and it will add it to the database when done parsing. * To access the data, simply query the database. */ public class WebUpdater { Context context; public WebUpdater(Context context) { this.context = context; } /** Primary method. Call this on each date/location pair you want to update */ public void update(GregorianCalendar date, Meal.Location loc) { String address = constructWebAddress(date, loc); DownloadSource task = new DownloadSource(date, loc); task.execute(address); } /** Constructs a web address from the date/loc passed in to update() */ private String constructWebAddress(GregorianCalendar date, Meal.Location loc) { StringBuilder address = new StringBuilder(""); address.append("http://www.housing.purdue.edu/Menus/"); switch (loc) { case earhart: address.append("ERHT/"); break; case ford: address.append("FORD/"); break; case hillenbrand: address.append("HILL/"); break; case wiley: address.append("WILY/"); break; case windsor: address.append("WIND/"); break; } address.append(date.get(GregorianCalendar.YEAR) + "/"); address.append(date.get(GregorianCalendar.MONTH) + "/"); address.append(date.get(GregorianCalendar.DAY_OF_MONTH) + ""); Log.d("menu", address.toString()); return address.toString(); } /** Uses Jsoup to parse the html source returned by the SourceDownloader Async task. * Automatically adds the things it finds to the database. */ private void parseSource(String html, GregorianCalendar date, Meal.Location loc) { Log.d("menu","The parsing Source has begun."); // Create the Jsoup document and restaurant string which will be used later. Document doc = Jsoup.parse(html); String restaurant = null; // The HTML page stores each meal under the ID tag Breakfast, Lunch, or Dinner. // no it doesn't Element breakfast = doc.getElementById("Breakfast"); Log.d("menu", breakfast.toString()); // The restaurant name and meal names are all list elements underneath Breakfast. // no they're not for (Element e : breakfast.getElementsByTag("li")) { Log.d("menu", "This actually ran"); // The restaurant line contains a list-divider, so use that to differentiate it. if (e.toString().contains("list-divider")) { // Set the restaurant string to the restaurant. // It will remain this way until a new restaurant is found in the list. restaurant = e.text(); } else if (e.toString().contains("<li>")) { // Otherwise, its a meal item, so create a new meal from all the information we have... Meal m = new Meal(date, Meal.Time.breakfast, loc, restaurant, e.text()); // And add it to the database. Log.d("menu", "Menu has been added to database!"); addToDatabase(m); } } // Repeat the procedure for Lunch and Dinner. Element lunch = doc.getElementById("Lunch"); for (Element e : lunch.getElementsByTag("li")) { Log.d("menu", "This actually ran"); if (e.toString().contains("list-divider")) { Log.d("boilermenu.data.WebSource", "Restaurant: " + e.text()); restaurant = e.text(); } else if (e.toString().contains("<li>")) { Log.d("boilermenu.data.WebSource", "Menu Item: " + e.text()); Meal m = new Meal(date, Meal.Time.breakfast, loc, restaurant, e.text()); addToDatabase(m); } } Element dinner = doc.getElementById("Dinner"); for (Element e : dinner.getElementsByTag("li")) { Log.d("menu", "This actually ran"); if (e.toString().contains("list-divider")) { Log.d("boilermenu.data.WebSource", "Restaurant: " + e.text()); restaurant = e.text(); } else if (e.toString().contains("<li>")) { Log.d("boilermenu.data.WebSource", "Menu Item: " + e.text()); Meal m = new Meal(date, Meal.Time.breakfast, loc, restaurant, e.text()); addToDatabase(m); } } } /** Adds meal m to the database */ private void addToDatabase(Meal m) { DBHelper db = new DBHelper(context, null, null, 1); db.addMeal(m); } /** DownloadSource AsyncTask. Takes in a String url on execute. On * instantiation, takes in the date and location of the meals we're downloading. * The only reason I do this is so they can eventually be passed into * parseSource() when its complete. They could be stored as member variables of this * whole class, but then we'd need to create a new WebUpdater for every single page we * want to update, which could be 10-15. So this way is less object-oriented in design, * but much easier to implement. */ private class DownloadSource extends AsyncTask<String, Void, String> { GregorianCalendar date; Meal.Location loc; /** Default constructor */ public DownloadSource(GregorianCalendar date, Meal.Location loc) { this.date = date; this.loc = loc; } /** Downloads the web page and returns it as a String */ protected String doInBackground(String... address) { URL url; InputStream is = null; BufferedReader br; StringBuilder result = new StringBuilder(); String line; try { url = new URL(address[0]); Log.d("boilermenu.data.WebSource", url.toString()); is = url.openStream(); br = new BufferedReader(new InputStreamReader(is)); while ((line = br.readLine()) != null) { result.append(line); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result.toString(); } /** When its done, execute the soruce parsing */ protected void onPostExecute(String result) { parseSource(result, date, loc); } } }