package edu.mit.mitmobile2.tour; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import edu.mit.mitmobile2.MobileWebApi; import edu.mit.mitmobile2.MobileWebApi.JSONArrayResponseListener; import edu.mit.mitmobile2.MobileWebApi.JSONObjectResponseListener; import edu.mit.mitmobile2.MobileWebApi.ServerResponseException; import edu.mit.mitmobile2.tour.Tour.GeoPoint; import edu.mit.mitmobile2.tour.Tour.Path; import edu.mit.mitmobile2.tour.Tour.SideTrip; import edu.mit.mitmobile2.tour.Tour.Site; import edu.mit.mitmobile2.tour.Tour.TourHeader; import edu.mit.mitmobile2.tour.Tour.TourItemContent; import android.content.Context; import android.os.Handler; public class TourModel { private static String TOUR_GUID = "mit150"; private static Tour sTour; public static Tour getTour() { return sTour; } /* * Get tour fallback on db if not avaiable in memory */ public static Tour getTour(Context context) { if (sTour == null) { // retrieve tour from DB TourDB tourDB = TourDB.getInstance(context); List<TourHeader> tourHeaders = tourDB.retrieveTourHeaders(); sTour = new Tour(tourHeaders.get(0)); tourDB.populateTourDetails(sTour); } return sTour; } public static void fetchTour(final Context context, final Handler uiHandler) { // we fetch on a thread, because saving to the database blocks for a couple seconds new Thread() { @Override public void run() { fetchTourHelper(context, uiHandler); } }.start(); } /* * this fetches the tour from the database or network * if the tour db cache has timed out it will try to cache from network * but if that fails revert to using the version cached in the DB */ private static void fetchTourHelper(final Context context, final Handler uiHandler) { final TourDB tourDB = TourDB.getInstance(context); if(tourDB.tourDetailsCached(TOUR_GUID, true)) { List<TourHeader> tourHeaders = tourDB.retrieveTourHeaders(); sTour = new Tour(tourHeaders.get(0)); tourDB.populateTourDetails(sTour); MobileWebApi.sendSuccessMessage(uiHandler); return; } // check if stale version of tour exists in db if(tourDB.tourDetailsCached(TOUR_GUID, false)) { // stale version exists, so check if stale version is recent enough MobileWebApi webApi = new MobileWebApi(false, false, null, context, null); HashMap<String, String> query = new HashMap<String, String>(); query.put("module", "tours"); query.put("command", "toursList"); webApi.requestJSONArray(query, new JSONArrayResponseListener(new TourRefreshErrorListener(tourDB, uiHandler), null) { @Override public void onResponse(JSONArray array) throws ServerResponseException, JSONException { Long lastModified = null; for(int i=0; i < array.length(); i++) { JSONObject tourSummary = array.getJSONObject(i); if(tourSummary.getString("id").equals(TOUR_GUID)) { lastModified = tourSummary.getLong("last-modified") * 1000; } } if(lastModified != null) { if(lastModified < tourDB.tourDetailsLastUpdated(TOUR_GUID)) { // cache is still fresh (use it, and mark it as fresh) tourDB.markTourFresh(TOUR_GUID); // retrieve tour from DB List<TourHeader> tourHeaders = tourDB.retrieveTourHeaders(); sTour = new Tour(tourHeaders.get(0)); tourDB.populateTourDetails(sTour); MobileWebApi.sendSuccessMessage(uiHandler); return; } } // tour is not up-to-date we need to update it. fetchTourFromNetwork(context, uiHandler); } }); return; } // no fresh or stale version of tour exists must retrieve it from network fetchTourFromNetwork(context, uiHandler); } private static void fetchTourFromNetwork(Context context, final Handler uiHandler) { final TourDB tourDB = TourDB.getInstance(context); MobileWebApi webApi = new MobileWebApi(false, true, "Tour", context, uiHandler); HashMap<String, String> query = new HashMap<String, String>(); query.put("module", "tours"); query.put("command", "tourDetails"); query.put("tourId", TOUR_GUID); webApi.requestJSONObject(query, new JSONObjectResponseListener(new MobileWebApi.DefaultErrorListener(uiHandler), null) { @Override public void onResponse(JSONObject object) throws ServerResponseException, JSONException { // save header info String title = object.getString("title"); String descriptionTop = object.getString("description-top"); String descriptionBottom = object.getString("description-bottom"); Tour tour = new Tour(TOUR_GUID, title, descriptionTop, descriptionBottom); // save footer info String feedbackSubject = object.getJSONObject("feedback").getString("subject"); tour.setFeedbackSubject(feedbackSubject); // parse all the links for the tour footer JSONArray links = object.getJSONArray("links"); for(int linkIndex = 0; linkIndex < links.length(); linkIndex++) { JSONObject linkJson = links.getJSONObject(linkIndex); tour.getFooter().addLink(linkJson.getString("title"), linkJson.getString("url")); } // parse all the sites data JSONArray sites = object.getJSONArray("sites"); for(int siteIndex = 0; siteIndex < sites.length(); siteIndex++) { JSONObject siteJson = sites.getJSONObject(siteIndex); Site site = tour.addSite( siteJson.getString("id"), siteJson.getString("title"), siteJson.getString("photo-url"), siteJson.getString("thumbnail156-url"), optString(siteJson, "audio-url"), parseLatLon(siteJson.getJSONObject("latlon")) ); if(siteJson.has("exit-directions")) { String destinationGuid; if(siteIndex + 1 < sites.length()) { destinationGuid = sites.getJSONObject(siteIndex+1).getString("id"); } else { destinationGuid = sites.getJSONObject(0).getString("id"); } site.setExitDirections( siteJson.getJSONObject("exit-directions").getString("title"), destinationGuid, siteJson.getJSONObject("exit-directions").getInt("zoom") ); JSONObject exitDirectionsJson = siteJson.getJSONObject("exit-directions"); populateContent(site.getExitDirections().getContent(), exitDirectionsJson); populatePath(site.getExitDirections().getPath(), exitDirectionsJson); site.getExitDirections().setPhotoUrl(optString(exitDirectionsJson, "photo-url")); site.getExitDirections().setAudioUrl(optString(exitDirectionsJson, "audio-url")); } populateContent(site.getContent(), siteJson); } // parse all the start locations data tour.setStartLocationsHeader(object.getJSONObject("start-locations").getString("header")); JSONArray startLocationsItems = object.getJSONObject("start-locations").getJSONArray("items"); for(int startLocationIndex=0; startLocationIndex < startLocationsItems.length(); startLocationIndex++) { JSONObject startLocationJson = startLocationsItems.getJSONObject(startLocationIndex); tour.addStartLocation( startLocationJson.getString("title"), startLocationJson.getString("id"), startLocationJson.getString("start-site"), startLocationJson.getString("content"), optString(startLocationJson, "photo-url"), parseLatLon(startLocationJson.getJSONObject("latlon")) ); } ArrayList<Tour> tours = new ArrayList<Tour>(); tours.add(tour); tourDB.saveTourHeaders(tours); tourDB.saveTourDetails(tour); sTour = tour; MobileWebApi.sendSuccessMessage(uiHandler); } }); } private static GeoPoint parseLatLon(JSONObject object) throws JSONException { return new GeoPoint( (int)Math.round(1000000 * object.getDouble("latitude")), (int)Math.round(1000000 * object.getDouble("longitude")) ); } private static void populateContent(TourItemContent content, JSONObject json) throws JSONException { JSONArray contentArray = json.getJSONArray("content"); for(int i=0; i < contentArray.length(); i++) { JSONObject contentNodeJson = contentArray.getJSONObject(i); String contentNodeType = contentNodeJson.getString("type"); if(contentNodeType.equals("inline")) { content.addHtml(contentNodeJson.getString("html")); } else if(contentNodeType.equals("sidetrip")) { // optional fields String thumbUrl = null; GeoPoint geoPoint = null; if(contentNodeJson.has("thumbnail156-url")) { thumbUrl = contentNodeJson.getString("thumbnail156-url"); } if(contentNodeJson.has("latlon")) { geoPoint = parseLatLon(contentNodeJson.getJSONObject("latlon")); } else { // sidetrips off the coast of africa geoPoint = new GeoPoint(0,0); } content.addSideTrip(new SideTrip( contentNodeJson.getString("id"), contentNodeJson.getString("title"), contentNodeJson.getString("html"), optString(contentNodeJson, "photo-url"), thumbUrl, optString(contentNodeJson, "audio-url"), geoPoint )); } } } private static void populatePath(Path path, JSONObject json) throws JSONException { JSONArray jsonPath = json.getJSONArray("path"); for(int i=0; i < jsonPath.length(); i++) { GeoPoint geoPoint = parseLatLon(jsonPath.getJSONObject(i)); path.addGeoPoint(geoPoint); } } private static String optString(JSONObject json, String keyName) throws JSONException { if(json.has(keyName)) { return json.getString(keyName); } else { return null; } } private static class TourRefreshErrorListener implements MobileWebApi.ErrorResponseListener { Handler mUIHandler; TourDB mTourDB; TourRefreshErrorListener(TourDB tourDB, Handler handler) { mUIHandler = handler; mTourDB = tourDB; } @Override public void onError() { // failed to retrieve last modified time from network, lets just assume // data in db is up-to-date // retrieve tour from DB List<TourHeader> tourHeaders = mTourDB.retrieveTourHeaders(); sTour = new Tour(tourHeaders.get(0)); mTourDB.populateTourDetails(sTour); MobileWebApi.sendSuccessMessage(mUIHandler); } } }